From db22e3008e1c46b3c4c907ee58a6048c41b529fa Mon Sep 17 00:00:00 2001 From: Johannes Gallmann Date: Tue, 20 Feb 2024 14:23:26 +0100 Subject: [PATCH 01/24] Use STANDARD_DECELERATE interpolator for predictive back to home Bug: 325647546 Flag: ACONFIG com.android.systemui.predictive_back_system_animations TEAMFOOD Test: Manual, i.e. verifying increased responsiveness during first phase of back gesture Change-Id: Ie31d7fba7ca307c96830e0656887cda89d9bbb1a --- .../android/quickstep/LauncherBackAnimationController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java index 5772450b62..364d9dcccf 100644 --- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java +++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java @@ -52,6 +52,7 @@ import android.window.BackMotionEvent; import android.window.BackProgressAnimator; import android.window.IOnBackInvokedCallback; +import com.android.app.animation.Interpolators; import com.android.internal.view.AppearanceRegion; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; @@ -103,7 +104,8 @@ public class LauncherBackAnimationController { private float mWindowScaleEndCornerRadius; private float mWindowScaleStartCornerRadius; private final Interpolator mCancelInterpolator; - private final Interpolator mProgressInterpolator = new DecelerateInterpolator(); + private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE; + private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator(); private final PointF mInitialTouchPos = new PointF(); private RemoteAnimationTarget mBackTarget; @@ -376,7 +378,7 @@ public class LauncherBackAnimationController { float yDirection = rawYDelta < 0 ? -1 : 1; // limit yDelta interpretation to 1/2 of screen height in either direction float deltaYRatio = Math.min(screenHeight / 2f, Math.abs(rawYDelta)) / (screenHeight / 2f); - float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio); + float interpolatedYRatio = mVerticalMoveInterpolator.getInterpolation(deltaYRatio); // limit y-shift so surface never passes 8dp screen margin float deltaY = yDirection * interpolatedYRatio * Math.max(0f, (screenHeight - height) / 2f - mWindowScaleMarginX); From 5e8811e126fe49d367142a6419ec77184ef4deb3 Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Tue, 20 Feb 2024 13:25:58 +0000 Subject: [PATCH 02/24] Make getTaskSize actaully a getter that does not modify member variables - This is a follow-up of ag/26255150 to clean-up usage of getTaskSize - Removed getTasksize and updatePivots in onLayout, as updateSizeAndPaddings has covered all scenarios - mLastComputedTaskSize will only be updated from updateSizeAndPaddings, with minor refactoring on how mTaskWidh/mTaskHeight/padding are calculated Bug: 325314248 Test: Manual on home/app->overview, rotations, fold/unfold etc. Flag: None Change-Id: I031a36e5e065a28070fb3f7f146978279f7d8886 --- .../android/quickstep/views/RecentsView.java | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index ecff6b43ad..a5a296c8e6 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -2093,22 +2093,20 @@ public abstract class RecentsView Date: Tue, 13 Feb 2024 10:47:55 -0500 Subject: [PATCH 03/24] Update KQS launch task callbacks Updated KQS to allow launching tasks through OverviewCommandHelper to allow launching the overview task without it having focus. Flag: LEGACY ENABLE_KEYBOARD_QUICK_SWITCH ENABLED Fixes: 324888738 Fixes: 324904244 Test: used enter and tap to launch tasks in KQS Change-Id: I6de38ceea22c362a52a420c8ba0165ba23e5c862 --- .../taskbar/DesktopTaskbarUIController.java | 9 +++++++ .../taskbar/FallbackTaskbarUIController.java | 9 +++++++ .../taskbar/KeyboardQuickSwitchView.java | 4 +-- .../KeyboardQuickSwitchViewController.java | 16 +++++++----- .../taskbar/LauncherTaskbarUIController.java | 7 ++++++ .../taskbar/TaskbarActivityContext.java | 4 +++ .../taskbar/TaskbarUIController.java | 25 +++++++++++++++++++ .../uioverrides/QuickstepLauncher.java | 5 ++++ .../android/quickstep/RecentsActivity.java | 5 ++++ 9 files changed, 76 insertions(+), 8 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java index 633325bf4b..2dd610c467 100644 --- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java @@ -16,7 +16,10 @@ package com.android.launcher3.taskbar; +import androidx.annotation.Nullable; + import com.android.launcher3.uioverrides.QuickstepLauncher; +import com.android.quickstep.util.TISBindHelper; /** * A data source which integrates with a Launcher instance, used specifically for a @@ -50,4 +53,10 @@ public class DesktopTaskbarUIController extends TaskbarUIController { public boolean supportsVisualStashing() { return false; } + + @Nullable + @Override + protected TISBindHelper getTISBindHelper() { + return mLauncher.getTISBindHelper(); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java index f9816102d4..c0ecc6151a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java @@ -21,11 +21,14 @@ import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASH import android.animation.Animator; +import androidx.annotation.Nullable; + import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.statemanager.StateManager; import com.android.quickstep.RecentsActivity; import com.android.quickstep.TopTaskTracker; import com.android.quickstep.fallback.RecentsState; +import com.android.quickstep.util.TISBindHelper; import com.android.quickstep.views.RecentsView; import java.util.stream.Stream; @@ -124,4 +127,10 @@ public class FallbackTaskbarUIController extends TaskbarUIController { .get(mControllers.taskbarActivityContext).getCachedTopTask(true); return topTask.isHomeTask() || topTask.isRecentsTask(); } + + @Nullable + @Override + protected TISBindHelper getTISBindHelper() { + return mRecentsActivity.getTISBindHelper(); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java index 42c423ce83..2d9e23644d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java @@ -147,7 +147,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout { KeyboardQuickSwitchTaskView taskView = (KeyboardQuickSwitchTaskView) layoutInflater.inflate( R.layout.keyboard_quick_switch_taskview, mContent, false); taskView.setId(View.generateViewId()); - taskView.setOnClickListener(v -> mViewCallbacks.launchTappedTask(index)); + taskView.setOnClickListener(v -> mViewCallbacks.launchTaskAt(index)); LayoutParams lp = new LayoutParams(width, mTaskViewHeight); // Create a left-to-right ordering of views (or right-to-left in RTL locales) @@ -186,7 +186,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout { KeyboardQuickSwitchTaskView overviewButton = (KeyboardQuickSwitchTaskView) layoutInflater.inflate( R.layout.keyboard_quick_switch_overview, this, false); - overviewButton.setOnClickListener(v -> mViewCallbacks.launchTappedTask(MAX_TASKS)); + overviewButton.setOnClickListener(v -> mViewCallbacks.launchTaskAt(MAX_TASKS)); overviewButton.findViewById(R.id.text).setText(overflowString); diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java index 3e262e5f35..c830aa8640 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java @@ -22,7 +22,6 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.animation.Animator; import android.app.ActivityOptions; import android.view.KeyEvent; -import android.view.View; import android.view.animation.AnimationUtils; import android.window.RemoteTransition; @@ -137,7 +136,6 @@ public class KeyboardQuickSwitchViewController { } // Even with a valid index, this can be null if the user tries to quick switch before the // views have been added in the KeyboardQuickSwitchView. - View taskView = mKeyboardQuickSwitchView.getTaskAt(index); GroupTask task = mControllerCallbacks.getTaskAt(index); if (task == null) { return Math.max(0, index); @@ -198,13 +196,18 @@ public class KeyboardQuickSwitchViewController { && keyCode != KeyEvent.KEYCODE_DPAD_RIGHT && keyCode != KeyEvent.KEYCODE_DPAD_LEFT && keyCode != KeyEvent.KEYCODE_GRAVE - && keyCode != KeyEvent.KEYCODE_ESCAPE) { + && keyCode != KeyEvent.KEYCODE_ESCAPE + && keyCode != KeyEvent.KEYCODE_ENTER) { return false; } if (keyCode == KeyEvent.KEYCODE_GRAVE || keyCode == KeyEvent.KEYCODE_ESCAPE) { closeQuickSwitchView(true); return true; } + if (keyCode == KeyEvent.KEYCODE_ENTER) { + launchTaskAt(mCurrentFocusIndex); + return true; + } if (!allowTraversal) { return false; } @@ -234,9 +237,10 @@ public class KeyboardQuickSwitchViewController { mCurrentFocusIndex = index; } - void launchTappedTask(int index) { - KeyboardQuickSwitchViewController.this.launchTaskAt(index); - closeQuickSwitchView(true); + void launchTaskAt(int index) { + mCurrentFocusIndex = Utilities.boundToRange( + index, 0, mKeyboardQuickSwitchView.getChildCount() - 1); + mControllers.taskbarActivityContext.launchKeyboardFocusedTask(); } void updateThumbnailInBackground(Task task, Consumer callback) { diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 50e8d0edc7..1e861d2fb5 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -52,6 +52,7 @@ import com.android.launcher3.util.OnboardingPrefs; import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.util.GroupTask; +import com.android.quickstep.util.TISBindHelper; import com.android.quickstep.views.RecentsView; import java.io.PrintWriter; @@ -428,6 +429,12 @@ public class LauncherTaskbarUIController extends TaskbarUIController { mTaskbarLauncherStateController.resetIconAlignment(); } + @Nullable + @Override + protected TISBindHelper getTISBindHelper() { + return mLauncher.getTISBindHelper(); + } + @Override public void dumpLogs(String prefix, PrintWriter pw) { super.dumpLogs(prefix, pw); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index b69f657183..a0eb13399d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -1485,6 +1485,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext { btv.post(() -> mControllers.taskbarPopupController.showForIcon(btv)); } + public void launchKeyboardFocusedTask() { + mControllers.uiController.launchKeyboardFocusedTask(); + } + public boolean isInApp() { return mControllers.taskbarStashController.isInApp(); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index 8a26054a66..8f7cebb658 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP; +import static com.android.quickstep.OverviewCommandHelper.TYPE_HIDE; import android.content.Intent; import android.graphics.drawable.BitmapDrawable; @@ -38,7 +39,9 @@ import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.SplitConfigurationOptions; +import com.android.quickstep.OverviewCommandHelper; import com.android.quickstep.util.GroupTask; +import com.android.quickstep.util.TISBindHelper; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.quickstep.views.TaskView.TaskIdAttributeContainer; @@ -359,6 +362,28 @@ public class TaskbarUIController { /** Adjusts the hotseat for the bubble bar. */ public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {} + @Nullable + protected TISBindHelper getTISBindHelper() { + return null; + } + + /** + * Launches the focused task in the Keyboard Quick Switch view through the OverviewCommandHelper + *

+ * Use this helper method when the focused task may be the overview task. + */ + public void launchKeyboardFocusedTask() { + TISBindHelper tisBindHelper = getTISBindHelper(); + if (tisBindHelper == null) { + return; + } + OverviewCommandHelper overviewCommandHelper = tisBindHelper.getOverviewCommandHelper(); + if (overviewCommandHelper == null) { + return; + } + overviewCommandHelper.addCommand(TYPE_HIDE); + } + /** * Adjusts the taskbar based on the visibility of the launcher. * @param isVisible True if launcher is visible, false otherwise. diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index f3f36c5abb..7066063af2 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -1344,6 +1344,11 @@ public class QuickstepLauncher extends Launcher { return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles()); } + @NonNull + public TISBindHelper getTISBindHelper() { + return mTISBindHelper; + } + @Override public boolean handleIncorrectSplitTargetSelection() { if (!enableSplitContextually() || !mSplitSelectStateController.isSplitSelectActive()) { diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index 22163b9614..02f9a6962e 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -498,4 +498,9 @@ public final class RecentsActivity extends StatefulActivity { OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper(); return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely(); } + + @NonNull + public TISBindHelper getTISBindHelper() { + return mTISBindHelper; + } } From def24aa88d27d74bf367fe71addd5a3861967e51 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 21 Feb 2024 16:45:43 -0800 Subject: [PATCH 04/24] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I543d305f3f7587d6459fb1a926872bea5cf6ab1d --- quickstep/res/values-uk/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml index e18ddf5dcc..c20c213e00 100644 --- a/quickstep/res/values-uk/strings.xml +++ b/quickstep/res/values-uk/strings.xml @@ -95,7 +95,7 @@ "Поділитися" "Знімок екрана" "Розділити" - "Щоб розділити екран, виберіть ще один додаток" + "Щоб розділити екран, виберіть ще один додаток." "Щоб розділити екран, виберіть ще один додаток." "Скасувати" "Вийти з режиму розділення екрана" From d7c8a65c7d4caf0dde1af76eb9e4af114af928fe Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 21 Feb 2024 16:46:08 -0800 Subject: [PATCH 05/24] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I3d8660dd1a5ce67718f8b2643212ddbc254e4d13 --- quickstep/res/values-uk/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml index 6cbf71f7bf..0d57f18fcd 100644 --- a/quickstep/res/values-uk/strings.xml +++ b/quickstep/res/values-uk/strings.xml @@ -94,9 +94,9 @@ "Поділитися" "Знімок екрана" "Розділити" - "Щоб розділити екран, виберіть ще один додаток" + "Щоб розділити екран, виберіть ще один додаток." "Вийти з режиму розділення екрана" - "Щоб розділити екран, виберіть ще один додаток" + "Щоб розділити екран, виберіть ще один додаток." "Ця дія заборонена додатком або адміністратором організації" "Пропустити посібник із навігації?" "Ви знайдете його пізніше в додатку %1$s" From 6b5991697863f6bfa8dc16281e1b7bcb61875e4d Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 21 Feb 2024 16:46:31 -0800 Subject: [PATCH 06/24] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: Ib559702a688a9ec6b776196b67f0c2efc9332c89 --- go/quickstep/res/values-my/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/go/quickstep/res/values-my/strings.xml b/go/quickstep/res/values-my/strings.xml index cbb485a6d2..5fcb5bdc34 100644 --- a/go/quickstep/res/values-my/strings.xml +++ b/go/quickstep/res/values-my/strings.xml @@ -9,11 +9,11 @@ "မလုပ်တော့" "ဆက်တင်များ" "ဖန်သားပြင်ပေါ်ရှိ စာသားကို ဘာသာပြန်ပါ (သို့) နားထောင်ပါ" - "သင့်ဖန်သားပြင်ပေါ်ရှိ စာသား၊ ဝဘ်လိပ်စာနှင့် ဖန်သားပြင်ဓာတ်ပုံများကဲ့သို့ အချက်အလက်များကို Google နှင့် မျှဝေနိုင်သည်။\n\nသင်မျှဝေသည့် အချက်အလက်များကို ပြောင်းရန် ""ဆက်တင်များ > အက်ပ်များ > မူရင်းအက်ပ်များ > ဒစ်ဂျစ်တယ် Assistant အက်ပ်"" သို့ သွားပါ။" + "သင့်ဖန်သားပြင်ပေါ်ရှိ စာသား၊ ဝဘ်လိပ်စာနှင့် ဖန်သားပြင်ဓာတ်ပုံများကဲ့သို့ အချက်အလက်များကို Google နှင့် မျှဝေနိုင်သည်။\n\nသင်မျှဝေသည့် အချက်အလက်များကို ပြောင်းရန် ""ဆက်တင်များ > အက်ပ်များ > မူရင်းအက်ပ်များ > ဒစ်ဂျစ်တယ်အထောက်အကူ အက်ပ်"" သို့ သွားပါ။" "ဤဝန်ဆောင်မှုကို အသုံးပြုရန် assistant ရွေးပါ" - "ဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် (သို့) ဘာသာပြန်ဆိုရန် ‘ဆက်တင်များ’ တွင် ဒစ်ဂျစ်တယ် assistant အက်ပ် ရွေးပါ" + "ဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် (သို့) ဘာသာပြန်ဆိုရန် ‘ဆက်တင်များ’ တွင် ဒစ်ဂျစ်တယ်အထောက်အကူ အက်ပ် ရွေးပါ" "ဤဝန်ဆောင်မှုကို သုံးရန် assistant ကို ပြောင်းပါ" - "ဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် (သို့) ဘာသာပြန်ဆိုရန် ‘ဆက်တင်များ’ တွင် ဒစ်ဂျစ်တယ် assistant အက်ပ်ကို ပြောင်းပါ" + "ဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် (သို့) ဘာသာပြန်ဆိုရန် ‘ဆက်တင်များ’ တွင် ဒစ်ဂျစ်တယ်အထောက်အကူ အက်ပ်ကို ပြောင်းပါ" "ဤဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် ဤနေရာကို တို့ပါ" "ဤဖန်သားပြင်ပေါ်ရှိ စာသားကို ဘာသာပြန်ဆိုရန် ဤနေရာကို တို့ပါ" "ဤအက်ပ်ကို မျှဝေ၍မရပါ" From 50923131e3378e86c61e53d5f31e320c43f757a6 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 21 Feb 2024 16:46:54 -0800 Subject: [PATCH 07/24] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: Ia3d3dd28d407fb8f9f4947acc40028863faa78c9 --- go/quickstep/res/values-my/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/go/quickstep/res/values-my/strings.xml b/go/quickstep/res/values-my/strings.xml index cbb485a6d2..5fcb5bdc34 100644 --- a/go/quickstep/res/values-my/strings.xml +++ b/go/quickstep/res/values-my/strings.xml @@ -9,11 +9,11 @@ "မလုပ်တော့" "ဆက်တင်များ" "ဖန်သားပြင်ပေါ်ရှိ စာသားကို ဘာသာပြန်ပါ (သို့) နားထောင်ပါ" - "သင့်ဖန်သားပြင်ပေါ်ရှိ စာသား၊ ဝဘ်လိပ်စာနှင့် ဖန်သားပြင်ဓာတ်ပုံများကဲ့သို့ အချက်အလက်များကို Google နှင့် မျှဝေနိုင်သည်။\n\nသင်မျှဝေသည့် အချက်အလက်များကို ပြောင်းရန် ""ဆက်တင်များ > အက်ပ်များ > မူရင်းအက်ပ်များ > ဒစ်ဂျစ်တယ် Assistant အက်ပ်"" သို့ သွားပါ။" + "သင့်ဖန်သားပြင်ပေါ်ရှိ စာသား၊ ဝဘ်လိပ်စာနှင့် ဖန်သားပြင်ဓာတ်ပုံများကဲ့သို့ အချက်အလက်များကို Google နှင့် မျှဝေနိုင်သည်။\n\nသင်မျှဝေသည့် အချက်အလက်များကို ပြောင်းရန် ""ဆက်တင်များ > အက်ပ်များ > မူရင်းအက်ပ်များ > ဒစ်ဂျစ်တယ်အထောက်အကူ အက်ပ်"" သို့ သွားပါ။" "ဤဝန်ဆောင်မှုကို အသုံးပြုရန် assistant ရွေးပါ" - "ဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် (သို့) ဘာသာပြန်ဆိုရန် ‘ဆက်တင်များ’ တွင် ဒစ်ဂျစ်တယ် assistant အက်ပ် ရွေးပါ" + "ဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် (သို့) ဘာသာပြန်ဆိုရန် ‘ဆက်တင်များ’ တွင် ဒစ်ဂျစ်တယ်အထောက်အကူ အက်ပ် ရွေးပါ" "ဤဝန်ဆောင်မှုကို သုံးရန် assistant ကို ပြောင်းပါ" - "ဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် (သို့) ဘာသာပြန်ဆိုရန် ‘ဆက်တင်များ’ တွင် ဒစ်ဂျစ်တယ် assistant အက်ပ်ကို ပြောင်းပါ" + "ဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် (သို့) ဘာသာပြန်ဆိုရန် ‘ဆက်တင်များ’ တွင် ဒစ်ဂျစ်တယ်အထောက်အကူ အက်ပ်ကို ပြောင်းပါ" "ဤဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် ဤနေရာကို တို့ပါ" "ဤဖန်သားပြင်ပေါ်ရှိ စာသားကို ဘာသာပြန်ဆိုရန် ဤနေရာကို တို့ပါ" "ဤအက်ပ်ကို မျှဝေ၍မရပါ" From 7b7cc7bd2ec95edb21b66150016e7f86d17df22f Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 21 Feb 2024 16:47:23 -0800 Subject: [PATCH 08/24] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I744b4a25d6da2f0226c0cc4be308664e2c9dc4a6 --- res/values-af/strings.xml | 8 +++++--- res/values-am/strings.xml | 8 +++++--- res/values-ar/strings.xml | 8 +++++--- res/values-as/strings.xml | 8 +++++--- res/values-az/strings.xml | 8 +++++--- res/values-b+sr+Latn/strings.xml | 8 +++++--- res/values-be/strings.xml | 8 +++++--- res/values-bg/strings.xml | 8 +++++--- res/values-bn/strings.xml | 8 +++++--- res/values-bs/strings.xml | 8 +++++--- res/values-ca/strings.xml | 8 +++++--- res/values-cs/strings.xml | 8 +++++--- res/values-da/strings.xml | 8 +++++--- res/values-de/strings.xml | 8 +++++--- res/values-el/strings.xml | 8 +++++--- res/values-en-rAU/strings.xml | 8 +++++--- res/values-en-rCA/strings.xml | 3 ++- res/values-en-rGB/strings.xml | 8 +++++--- res/values-en-rIN/strings.xml | 8 +++++--- res/values-en-rXC/strings.xml | 3 ++- res/values-es-rUS/strings.xml | 8 +++++--- res/values-es/strings.xml | 8 +++++--- res/values-et/strings.xml | 8 +++++--- res/values-eu/strings.xml | 8 +++++--- res/values-fa/strings.xml | 8 +++++--- res/values-fi/strings.xml | 8 +++++--- res/values-fr-rCA/strings.xml | 8 +++++--- res/values-fr/strings.xml | 8 +++++--- res/values-gl/strings.xml | 8 +++++--- res/values-gu/strings.xml | 8 +++++--- res/values-hi/strings.xml | 8 +++++--- res/values-hr/strings.xml | 8 +++++--- res/values-hu/strings.xml | 8 +++++--- res/values-hy/strings.xml | 8 +++++--- res/values-in/strings.xml | 8 +++++--- res/values-is/strings.xml | 8 +++++--- res/values-it/strings.xml | 8 +++++--- res/values-iw/strings.xml | 8 +++++--- res/values-ja/strings.xml | 8 +++++--- res/values-ka/strings.xml | 8 +++++--- res/values-kk/strings.xml | 8 +++++--- res/values-km/strings.xml | 8 +++++--- res/values-kn/strings.xml | 8 +++++--- res/values-ko/strings.xml | 8 +++++--- res/values-ky/strings.xml | 8 +++++--- res/values-lo/strings.xml | 8 +++++--- res/values-lt/strings.xml | 8 +++++--- res/values-lv/strings.xml | 8 +++++--- res/values-mk/strings.xml | 8 +++++--- res/values-ml/strings.xml | 8 +++++--- res/values-mn/strings.xml | 8 +++++--- res/values-mr/strings.xml | 8 +++++--- res/values-ms/strings.xml | 8 +++++--- res/values-my/strings.xml | 8 +++++--- res/values-nb/strings.xml | 8 +++++--- res/values-ne/strings.xml | 8 +++++--- res/values-nl/strings.xml | 8 +++++--- res/values-or/strings.xml | 8 +++++--- res/values-pa/strings.xml | 8 +++++--- res/values-pl/strings.xml | 8 +++++--- res/values-pt-rPT/strings.xml | 8 +++++--- res/values-pt/strings.xml | 10 ++++++---- res/values-ro/strings.xml | 8 +++++--- res/values-ru/strings.xml | 8 +++++--- res/values-si/strings.xml | 8 +++++--- res/values-sk/strings.xml | 8 +++++--- res/values-sl/strings.xml | 8 +++++--- res/values-sq/strings.xml | 8 +++++--- res/values-sr/strings.xml | 8 +++++--- res/values-sv/strings.xml | 8 +++++--- res/values-sw/strings.xml | 8 +++++--- res/values-ta/strings.xml | 8 +++++--- res/values-te/strings.xml | 8 +++++--- res/values-th/strings.xml | 8 +++++--- res/values-tl/strings.xml | 8 +++++--- res/values-tr/strings.xml | 8 +++++--- res/values-uk/strings.xml | 8 +++++--- res/values-ur/strings.xml | 8 +++++--- res/values-uz/strings.xml | 8 +++++--- res/values-vi/strings.xml | 8 +++++--- res/values-zh-rCN/strings.xml | 8 +++++--- res/values-zh-rHK/strings.xml | 8 +++++--- res/values-zh-rTW/strings.xml | 8 +++++--- res/values-zu/strings.xml | 8 +++++--- 84 files changed, 415 insertions(+), 249 deletions(-) diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml index 835c201700..8dd419cf57 100644 --- a/res/values-af/strings.xml +++ b/res/values-af/strings.xml @@ -40,14 +40,14 @@ "Voeg by tuisskerm" "%1$s-legstuk by tuisskerm gevoeg" "Voorstelle" - "Gee jou dag \'n hupstoot" + + "Nuus vir jou" "Jou ontspansone" "Bereik jou fiksheiddoelwitte" "Spring die weer voor" "Jy hou dalk ook van" - - + "%1$s-legstukke aan die regterkant, soektog en opsies aan die linkerkant" "{count,plural, =1{# legstuk}other{# legstukke}}" "{count,plural, =1{# kortpad}other{# kortpaaie}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privaat" "Privaat Ruimte-instellings" "Sluit/ontsluit Privaat Ruimte" + + "Privaat Ruimte-oorgang" "Installeer apps" "Installeer apps in privaat ruimte" diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index 98c41de7e4..e2834182dd 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -40,14 +40,14 @@ "ወደ መነሻ ማያ ገፅ አክል" "%1$s ምግብር ወደ መነሻ ማያ ገፅ ታክሏል" "የአስተያየት ጥቆማዎች" - "ቀንዎን ያሳምሩ" + + "ዜና ለእርስዎ" "የሚያርፉበት ቦታዎ" "የአካል ብቃት ግቦችዎን ያሳኩ" "ለአየር ሁኔታው አስቀድመው ያቅዱ" "ይህንንም ሊወዱት ይችላሉ" - - + "%1$s ምግብሮች በቀኝ በኩል፣ ፍለጋ እና አማራጮች በግራ በኩል" "{count,plural, =1{# ምግብር}one{# ምግብሮች}other{# ምግብሮች}}" "{count,plural, =1{# አቋራጭ}one{# አቋራጭ}other{# አቋራጮች}}" "%1$s%2$s" @@ -185,6 +185,8 @@ "የግል" "የግል ቦታ ቅንብሮች" "የግል ቦታን ቆልፍ/ክፈት" + + "የግል ቦታ ሽግግር" "መተግበሪያዎችን ይጫኑ" "መተግበሪያዎችን ወደ የግል ቦታ ይጫኑ" diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 91e23376a0..86a9a8748a 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -40,14 +40,14 @@ "إضافة إلى الشاشة الرئيسية" "تمت إضافة الأداة %1$s إلى الشاشة الرئيسية." "اقتراحات" - "تعزيز إنتاجية يومك" + + "أخبار مقترَحة لك" "محتوى ترفيهي مقترَح" "تحقيق أهداف اللياقة البدنية" "معرفة حالة الطقس أولاً بأول" "محتوى قد يعجبك أيضًا" - - + "تطبيقات \"%1$s\" المصغّرة على اليسار، والبحث والخيارات على اليمين" "{count,plural, =1{تطبيق مصغّر واحد}zero{# تطبيق مصغّر}two{تطبيقان مصغّران}few{# تطبيقات مصغّرة}many{# تطبيقًا مصغّرًا}other{# تطبيق مصغّر}}" "{count,plural, =1{اختصار واحد}zero{# اختصار}two{اختصاران}few{# اختصارات}many{# اختصارًا}other{# اختصار}}" "%1$s، %2$s" @@ -185,6 +185,8 @@ "المساحة الخاصة" "إعدادات المساحة الخاصة" "قفل المساحة الخاصة أو فتح قفلها" + + "النقل إلى المساحة الخاصة" "تثبيت التطبيقات" "تثبيت التطبيقات في المساحة الخاصّة" diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml index 3fa40d5549..53de80b84a 100644 --- a/res/values-as/strings.xml +++ b/res/values-as/strings.xml @@ -40,14 +40,14 @@ "গৃহ স্ক্ৰীনত যোগ কৰক" "%1$s ৱিজেটটো গৃহ স্ক্ৰীনত যোগ দিয়া হৈছে" "পৰামৰ্শ" - "আপোনাৰ দিনটো কাৰ্যকৰী কৰি তোলক" + + "আপোনাৰ বাবে বাতৰি" "আপোনাৰ পচন্দৰ স্থান" "আপোনাৰ সুস্থতাৰ লক্ষ্যত উপনীত হওক" "বতৰৰ বিষয়ে আগতীয়াকৈ জানক" "আপুনি হয়তো এইটোও পচন্দ কৰিব" - - + "%1$s ৱিজেট সোঁফালে, সন্ধান আৰু বিকল্পসমূহ বাওঁফালে" "{count,plural, =1{# টা ৱিজেট}one{# টা ৱিজেট}other{# টা ৱিজেট}}" "{count,plural, =1{# টা শ্বৰ্টকাট}one{# টা শ্বৰ্টকাট}other{# টা শ্বৰ্টকাট}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "ব্যক্তিগত" "ব্যক্তিগত স্পে’চৰ ছেটিং" "ব্যক্তিগত স্পে’চ লক/আনলক কৰক" + + "ব্যক্তিগত স্পে’চৰ স্থানান্তৰণ" "এপ্‌ ইনষ্টল কৰক" "এপ্‌সমূহ প্ৰাইভেট স্পেচত ইনষ্টল কৰক" diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index 2848606c61..3dce943cdb 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -40,14 +40,14 @@ "Əsas ekrana əlavə edin" "%1$s vidceti əsas ekrana əlavə edildi" "Təkliflər" - "Günü canlandırın" + + "Sizin üçün xəbərlər" "İstirahət zonası" "Fitnes hədəflərinə nail olun" "Hava barədə məlumatlı olun" "Tövsiyələr" - - + "%1$s vidcetləri sağda, axtarış və seçimlər solda" "{count,plural, =1{# vidcet}other{# vidcet}}" "{count,plural, =1{# qısayol}other{# qısayol}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Şəxsi" "Şəxsi məkan ayarları" "Şəxsi məkanı kilidləyin/kiliddən çıxarın" + + "Şəxsi məkana keçid" "Tətbiqlər quraşdırın" "Tətbiqləri şəxsi sahədə quraşdırın" diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml index f75dd60682..ef21114a57 100644 --- a/res/values-b+sr+Latn/strings.xml +++ b/res/values-b+sr+Latn/strings.xml @@ -40,14 +40,14 @@ "Dodaj na početni ekran" "Dodali ste vidžet %1$s na početni ekran" "Predlozi" - "Poboljšajte dan" + + "Vesti za vas" "Zona za opuštanje" "Ostvarite fitnes ciljeve" "Budite u toku sa vremenskim prilikama" "Možda će vam se dopasti i" - - + "Vidžeti %1$s sa desne strane, pretraga i opcije sa leve strane" "{count,plural, =1{# vidžet}one{# vidžet}few{# vidžeta}other{# vidžeta}}" "{count,plural, =1{# prečica}one{# prečica}few{# prečice}other{# prečica}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privatno" "Podešavanja privatnog prostora" "Zaključaj/otključaj privatni prostor" + + "Prenos privatnog prostora" "Instalirajte aplikacije" "Instaliraj aplikacije u privatan prostor" diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index 4b3780b50c..760cd2c40f 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -40,14 +40,14 @@ "Дадаць на галоўны экран" "Віджэт \"%1$s\" дададзены на галоўны экран" "Прапановы" - "Прадукцыйны дзень" + + "Навіны для вас" "Зона адпачынку" "Вашы фітнэс-мэты" "Прагноз надвор\'я" "Іншыя рэкамендацыі" - - + "Віджэты праграмы \"%1$s\" справа, пошук і параметры злева" "{count,plural, =1{# віджэт}one{# віджэт}few{# віджэты}many{# віджэтаў}other{# віджэта}}" "{count,plural, =1{# ярлык}one{# ярлык}few{# ярлыкі}many{# ярлыкоў}other{# ярлыка}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Прыватная" "Налады прыватнай вобласці" "Заблакіраваць (разблакіраваць) прыватную вобласць" + + "Пераход у прыватную вобласць" "Усталяваць праграмы" "Усталяваць праграмы ў прыватнай прасторы" diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index 40c9c6a370..9d69b65cae 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -40,14 +40,14 @@ "Добавяне към началния екран" "Приспособлението %1$s е добавено към началния екран" "Предложения" - "Подобрете ежедневието си" + + "Новини за вас" "Зоната ви за разпускане" "Постигнете фитнес целите си" "Бъдете една крачка напред с прогнозата за времето" "Може също да харесате" - - + "Приспособленията за %1$s са отдясно, търсенето и опциите – отляво" "{count,plural, =1{# приспособление}other{# приспособления}}" "{count,plural, =1{# пряк път}other{# преки пътя}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Лично" "Настройки за личното пространство" "Заключване/отключване на личното пространство" + + "Преминаване към личното пространство" "Инсталиране на приложения" "Инсталиране на приложения в частно пространство" diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml index c34b64131a..2b0c6380ad 100644 --- a/res/values-bn/strings.xml +++ b/res/values-bn/strings.xml @@ -40,14 +40,14 @@ "হোম স্ক্রিনে যোগ করুন" "%1$s উইজেট হোম স্ক্রিনে যোগ করা হয়েছে" "সাজেশন" - "আপনার দিন আরও ভালো করুন" + + "আপনার জন্য খবর" "আপনার চিল জোন" "আপনার ফিটনেস সংক্রান্ত লক্ষ্যে পৌঁছান" "আবহাওয়া সম্পর্কে আগেই খবর পান" "আপনার এগুলিও পছন্দ হতে পারে" - - + "%1$s উইজেট ডানদিকে, সার্চ ও বিকল্প বাঁদিকে রয়েছে" "{count,plural, =1{#টি উইজেট}one{#টি উইজেট}other{#টি উইজেট}}" "{count,plural, =1{#টি শর্টকাট}one{#টি শর্টকাট}other{#টি শর্টকাট}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "ব্যক্তিগত" "ব্যক্তিগত স্পেসের সেটিংস" "ব্যক্তিগত স্পেস লক/আনলক করুন" + + "ব্যক্তিগত স্পেস ট্রানজিট করা" "অ্যাপ ইনস্টল করুন" "প্রাইভেট স্পেসে অ্যাপ ইনস্টল করুন" diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index e2955c3741..e1d2da5dd7 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -40,14 +40,14 @@ "Dodaj na početni ekran" "Vidžet %1$s je dodan na početni ekran" "Prijedlozi" - "Podignite raspoloženje danas" + + "Vijesti za vas" "Vaša zona opuštanja" "Postignite svoje ciljeve fitnesa" "Ne dajte da vas uhvati oluja" "Možda vam se svidi i ovo" - - + "Vidžeti aplikacije %1$s su na desnoj, a pretraživanje i opcije na lijevoj strani" "{count,plural, =1{# vidžet}one{# vidžet}few{# vidžeta}other{# vidžeta}}" "{count,plural, =1{# prečica}one{# prečica}few{# prečice}other{# prečica}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privatno" "Postavke privatnog prostora" "Zaključavanje/otključavanje privatnog prostora" + + "Prelazak u privatan prostor" "Instaliranje aplikacija" "Instaliranje aplikacija u privatni prostor" diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index a8bae8b216..dffa857943 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -40,14 +40,14 @@ "Afegeix a la pantalla d\'inici" "El widget %1$s s\'ha afegit a la pantalla d\'inici" "Suggeriments" - "Millora el teu dia" + + "Notícies per a tu" "La teva zona de relax" "Assoleix els teus objectius de fitnes" "Que no et sorprengui el temps" "També et pot agradar" - - + "Widgets de %1$s a la dreta, cerca i opcions a l\'esquerra" "{count,plural, =1{# widget}other{# widgets}}" "{count,plural, =1{# drecera}other{# dreceres}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privat" "Configuració d\'Espai privat" "Bloqueja o desbloqueja Espai privat" + + "Canvia a Espai privat" "Instal·la aplicacions" "Instal·la les aplicacions a Espai privat" diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 312b57b047..12e8a9f92f 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -40,14 +40,14 @@ "Přidat na plochu" "Widget %1$s byl přidán na plochu" "Návrhy" - "Buďte produktivnější" + + "Zprávy pro vás" "Vaše klidová zóna" "Dosažení kondičních cílů" "Mějte přehled o počasí" "Také by se vám mohlo líbit" - - + "Widgety %1$s vpravo, vyhledávání a možnosti vlevo" "{count,plural, =1{ # widget}few{# widgety}many{# widgetu}other{# widgetů}}" "{count,plural, =1{# zkratka}few{# zkratky}many{# zkratky}other{# zkratek}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Soukromé" "Nastavení soukromého prostoru" "Zamknout/odemknout soukromý prostor" + + "Převádění soukromého prostoru" "Instalace aplikací" "Instalovat aplikace do soukromého prostoru" diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 967a720fac..fee0ddb4d4 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -40,14 +40,14 @@ "Føj til startskærm" "Widgetten %1$s blev føjet til startskærmen" "Forslag" - "Boost din dag" + + "Nyheder til dig" "Dit afslapningshjørne" "Nå dine fitnessmål" "Vær på forkant med vejret" "Du kan måske også lide" - - + "%1$s-widgets til højre, søgning og valgmuligheder til venstre" "{count,plural, =1{# widget}one{# widget}other{# widgets}}" "{count,plural, =1{# genvej}one{# genvej}other{# genveje}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privat" "Indstillinger for privat rum" "Lås/oplås det private område" + + "Ændringer af tilstanden for det private område" "Installer apps" "Installer apps i privat område" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 478aabc3fa..1d263c8928 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -40,14 +40,14 @@ "Zum Startbildschirm hinzufügen" "%1$s-Widget zum Startbildschirm hinzugefügt" "Vorschläge" - "Deine Alltagshelfer" + + "Neuigkeiten für dich" "Zum Entspannen" "Erreiche deine Fitnessziele" "Dem Wetter einen Schritt voraus" "Das könnte dir auch gefallen" - - + "%1$s-Widgets rechts, Suche und Optionen links" "{count,plural, =1{# Widget}other{# Widgets}}" "{count,plural, =1{# Verknüpfung}other{# Verknüpfungen}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privat" "Einstellungen für privaten Bereich" "Privaten Bereich sperren/entsperren" + + "Sperrzustand des privaten Bereichs wird gerade geändert" "Apps installieren" "Apps im privaten Bereich installieren" diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 092f8d81aa..e67bbc6060 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -40,14 +40,14 @@ "Προσθήκη στην αρχική οθόνη" "Το γραφικό στοιχείο %1$s προστέθηκε στην αρχική οθόνη." "Προτάσεις" - "Ενισχύστε την απόδοσή σας σήμερα" + + "Ειδήσεις για εσάς" "Ο δικός σας τρόπος χαλάρωσης" "Επιτύχετε τους στόχους που έχετε θέσει για τη φυσική σας κατάσταση" "Ετοιμαστείτε για κάθε καιρό" "Μπορεί να σας αρέσουν επίσης" - - + "Γραφικά στοιχεία %1$s στα δεξιά, αναζήτηση και επιλογές στα αριστερά" "{count,plural, =1{# γραφικό στοιχείο}other{# γραφικά στοιχεία}}" "{count,plural, =1{# συντόμευση}other{# συντομεύσεις}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Ιδιωτικό" "Ρυθμίσεις Ιδιωτικού χώρου" "Κλείδωμα/Ξεκλείδωμα Ιδιωτικού χώρου" + + "Μετάβαση στον Ιδιωτικό χώρο" "Εγκατάσταση εφαρμογών" "Εγκατάσταση εφαρμογών στον απόρρητο χώρο" diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml index c88d5e6ed5..e9339c7506 100644 --- a/res/values-en-rAU/strings.xml +++ b/res/values-en-rAU/strings.xml @@ -40,14 +40,14 @@ "Add to home screen" "%1$s widget added to home screen" "Suggestions" - "Boost your day" + + "News for you" "Your chill zone" "Reach your fitness goals" "Stay ahead of the weather" "You might also like" - - + "%1$s widgets on right, search and options on left" "{count,plural, =1{# widget}other{# widgets}}" "{count,plural, =1{# shortcut}other{# shortcuts}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Private" "Private Space Settings" "Lock/Unlock Private Space" + + "Private Space transitioning" "Install apps" "Install apps to private space" diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml index fc611f3c78..14be657733 100644 --- a/res/values-en-rCA/strings.xml +++ b/res/values-en-rCA/strings.xml @@ -40,7 +40,7 @@ "Add to home screen" "%1$s widget added to home screen" "Suggestions" - "Boost your day" + "Your Daily Essentials" "News For You" "Your Chill Zone" "Reach Your Fitness Goals" @@ -184,6 +184,7 @@ "Private" "Private Space Settings" "Lock/Unlock Private Space" + "Lock" "Private Space Transitioning" "Install apps" "Install apps to Private Space" diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index c88d5e6ed5..e9339c7506 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -40,14 +40,14 @@ "Add to home screen" "%1$s widget added to home screen" "Suggestions" - "Boost your day" + + "News for you" "Your chill zone" "Reach your fitness goals" "Stay ahead of the weather" "You might also like" - - + "%1$s widgets on right, search and options on left" "{count,plural, =1{# widget}other{# widgets}}" "{count,plural, =1{# shortcut}other{# shortcuts}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Private" "Private Space Settings" "Lock/Unlock Private Space" + + "Private Space transitioning" "Install apps" "Install apps to private space" diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml index c88d5e6ed5..e9339c7506 100644 --- a/res/values-en-rIN/strings.xml +++ b/res/values-en-rIN/strings.xml @@ -40,14 +40,14 @@ "Add to home screen" "%1$s widget added to home screen" "Suggestions" - "Boost your day" + + "News for you" "Your chill zone" "Reach your fitness goals" "Stay ahead of the weather" "You might also like" - - + "%1$s widgets on right, search and options on left" "{count,plural, =1{# widget}other{# widgets}}" "{count,plural, =1{# shortcut}other{# shortcuts}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Private" "Private Space Settings" "Lock/Unlock Private Space" + + "Private Space transitioning" "Install apps" "Install apps to private space" diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml index 5ce58581f8..985174a808 100644 --- a/res/values-en-rXC/strings.xml +++ b/res/values-en-rXC/strings.xml @@ -40,7 +40,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‏‏‎Add to home screen‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‎‎‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ widget added to home screen‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‏‎‎Suggestions‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎Boost your day‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‏‏‏‎‎Your Daily Essentials‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‎‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‏‎‎‎‏‏‏‏‎‎News For You‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‏‏‏‎Your Chill Zone‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‏‏‏‏‏‏‎Reach Your Fitness Goals‎‏‎‎‏‎" @@ -184,6 +184,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎Private‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‎‎‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‏‏‎‎‎‎‎‏‏‏‏‏‎Private Space Settings‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‎‎‏‎‏‏‎Lock/Unlock Private Space‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‏‎‏‎‎Lock‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‎‎Private Space Transitioning‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‎Install apps‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‏‏‏‏‏‏‎‏‏‎‎‎Install apps to Private Space‎‏‎‎‏‎" diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index dc087f22c7..90b27cc715 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -40,14 +40,14 @@ "Agregar a pantalla principal" "Se agregó el widget de %1$s a la pantalla principal" "Sugerencias" - "Aprovecha mejor tu día" + + "Noticias para ti" "Zona de descanso" "Logra tus objetivos de fitness" "Mantente al tanto del clima" "Puede que también te guste" - - + "Widgets de %1$s a la derecha, búsqueda y opciones a la izquierda" "{count,plural, =1{# widget}other{# widgets}}" "{count,plural, =1{# acceso directo}other{# accesos directos}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privado" "Configuración de Espacio privado" "Bloquear o desbloquear Espacio privado" + + "Pasar a Espacio privado" "Instala apps" "Instala las apps en el espacio privado" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 94d83bd439..4bf5516b90 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -40,14 +40,14 @@ "Añadir a pantalla de inicio" "Widget %1$s añadido a la pantalla de inicio" "Sugerencias" - "Mejora tu día" + + "Noticias para ti" "Tu zona de descanso" "Logra tus objetivos de actividad física" "Infórmate sobre el tiempo" "También te puede interesar" - - + "Widgets de %1$s a la derecha, búsqueda y opciones a la izquierda" "{count,plural, =1{# widget}other{# widgets}}" "{count,plural, =1{# acceso directo}other{# accesos directos}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privado" "Ajustes del espacio privado" "Bloquear/Desbloquear espacio privado" + + "Cambiar a espacio privado" "Descargar aplicaciones" "Descargar aplicaciones en el espacio privado" diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index 4b30dce8e5..26277af4d5 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -40,14 +40,14 @@ "Lisa avakuvale" "Vidin %1$s lisati avakuvale" "Soovitused" - "Saavutage päeva jooksul rohkem" + + "Uudised teile" "Teie lõõgastumiskoht" "Saavutage oma treeningueesmärgid" "Olge ilmateatega kursis" "Teile võivad meeldida ka need" - - + "Teenuse %1$s vidinad paremal, otsing ja valikud vasakul" "{count,plural, =1{# vidin}other{# vidinat}}" "{count,plural, =1{# otsetee}other{# otseteed}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privaatne" "Privaatse ruumi seaded" "Privaatse ruumi lukustamine/avamine" + + "Privaatse ruumi üleviimine" "Rakenduste installimine" "Rakenduste installimine privaatses ruumis" diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index 3c708f8f9b..435d057943 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -40,14 +40,14 @@ "Gehitu hasierako pantailan" "%1$s widgeta hasierako pantailan gehitu da" "Iradokizunak" - "Atera etekin handiagoa egunari" + + "Zuretzako albisteak" "Lasaitzeko gunea" "Erdietsi zure fitness-helburuak" "Hartu aurrea eguraldiari" "Gustatuko zaizkizulakoan" - - + "%1$s zerbitzuaren widgetak eskuinean, bilaketa eta aukerak ezkerrean" "{count,plural, =1{# widget}other{# widget}}" "{count,plural, =1{# lasterbide}other{# lasterbide}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Pribatua" "Eremu pribatuaren ezarpenak" "Blokeatu/Desblokeatu eremu pribatua" + + "Eremu pribaturako trantsizioa" "Aplikazioak instalatu" "Instalatu aplikazioak eremu pribatuan" diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index f9ed4a0729..6c71677a1c 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -40,14 +40,14 @@ "افزودن به صفحه اصلی" "ابزارک %1$s به صفحه اصلی اضافه شد" "پیشنهادها" - "روزتان را پربار کنید" + + "اخبار برای شما" "منطقه آرامش شما" "دستیابی به اهداف تناسب اندام" "آب‌وهوا را پیش‌بینی کنید" "شاید این را هم بپسندید" - - + "ابزارک‌های %1$s در سمت چپ، جستجو و گزینه‌ها در سمت راست" "{count,plural, =1{‏# ابزارک}one{‏# ابزارک}other{‏# ابزارک}}" "{count,plural, =1{‏# میان‌بر}one{‏# میان‌بر}other{‏# میان‌بر}}" "%1$s،%2$s" @@ -185,6 +185,8 @@ "خصوصی" "تنظیمات «فضای خصوصی»" "قفل/ باز کردن «فضای خصوصی»" + + "انتقال «فضای خصوصی»" "نصب برنامه‌ها" "نصب برنامه‌ها در «فضای خصوصی»" diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 6d9cf77e8c..1109611828 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -40,14 +40,14 @@ "Lisää aloitusnäytölle" "Widget lisätty aloitusnäytölle: %1$s" "Ehdotukset" - "Tehosta päivääsi" + + "Uutisia sinulle" "Ota rennosti" "Saavuta kuntoilutavoitteet" "Pysy ajan tasalla säästä" "Saatat pitää myös näistä" - - + "%1$s widgetit oikealla, haku ja vaihtoehdot vasemmalla" "{count,plural, =1{# widget}other{# widgetiä}}" "{count,plural, =1{# pikakuvake}other{# pikakuvaketta}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Yksityinen" "Yksityisen tilan asetukset" "Lukitse yksityinen tila / avaa sen lukitus" + + "Yksityisen tilan siirtäminen" "Asenna sovelluksia" "Asenna sovelluksia yksityiseen tilaan" diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index a3ed04c11f..bb7055128b 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -40,14 +40,14 @@ "Ajouter à l\'écran d\'accueil" "Le widget %1$s a été ajouté à l\'écran d\'accueil" "Suggestions" - "Productivité assurée" + + "Actualités personnalisées" "Zone de divertissement" "Objectifs de mise en forme" "À l\'affût de la météo" "Autres recommandations" - - + "Widgets %1$s à droite, recherche et options à gauche" "{count,plural, =1{# widget}one{# widget}other{# widgets}}" "{count,plural, =1{# raccourci}one{# raccourci}other{# raccourcis}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privé" "Paramètres de l\'Espace privé" "Verrouiller/Déverrouiller l\'Espace privé" + + "Transition vers l\'Espace privé" "Installer des applications" "Installer des applications dans l\'Espace privé" diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 80e3059a79..62844b09ed 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -40,14 +40,14 @@ "Ajouter à l\'écran d\'accueil" "Widget %1$s ajouté à l\'écran d\'accueil" "Suggestions" - "Boostez votre journée" + + "Actualités personnalisées" "Votre espace détente" "Atteignez vos objectifs forme" "Soyez au fait de la météo" "Découvrez également" - - + "Widgets %1$s à droite, recherche et options à gauche" "{count,plural, =1{# widget}one{# widget}other{# widgets}}" "{count,plural, =1{# raccourci}one{# raccourci}other{# raccourcis}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privé" "Paramètres d\'Espace privé" "Verrouiller/Déverrouiller Espace privé" + + "Transition vers Espace privé" "Installer des applis" "Installer des applis dans l\'espace privé" diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index 677cfe300c..82cdf3ab0b 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -40,14 +40,14 @@ "Engadir á pantalla de inicio" "Engadiuse o widget %1$s á pantalla de inicio" "Suxestións" - "Mellora o teu día" + + "Novidades para ti" "Reláxate" "Acada os teus obxectivos para estar en forma" "Adiántate á meteoroloxía" "Tamén che pode interesar…" - - + "Widgets de %1$s á dereita, busca e opcións á esquerda" "{count,plural, =1{# widget}other{# widgets}}" "{count,plural, =1{# atallo}other{# atallos}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privado" "Configuración do espazo privado" "Bloquear ou desbloquear o espazo privado" + + "Transición ao espazo privado" "Instalar as aplicacións" "Instalar as aplicacións no espazo privado" diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index a77cbfd2c2..4a5ac831ef 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -40,14 +40,14 @@ "હોમ સ્ક્રીનમાં ઉમેરો" "હોમ સ્ક્રીન પર %1$s વિજેટ ઉમેર્યુ" "સૂચનો" - "તમારા દિવસને બૂસ્ટ કરો" + + "તમારા માટે સમાચાર" "તમારો આરામદાયક ઝોન" "તમારા ફિટનેસ લક્ષ્યો પૂરા કરો" "હવામાન વિશે અપ ટૂ ડેટ રહો" "કદાચ તમને આ પણ પસંદ હોય" - - + "%1$sની વિજેટ જમણે, શોધ અને વિકલ્પો ડાબે" "{count,plural, =1{# વિજેટ}one{# વિજેટ}other{# વિજેટ}}" "{count,plural, =1{# શૉર્ટકટ}one{# શૉર્ટકટ}other{# શૉર્ટકટ}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "ખાનગી" "ખાનગી સ્પેસના સેટિંગ" "ખાનગી સ્પેસને લૉક/અનલૉક કરો" + + "ખાનગી સ્પેસ પર સ્થાનાંતરણ" "ઍપ ઇન્સ્ટૉલ કરો" "ખાનગી સ્પેસમાં ઍપ ઇન્સ્ટૉલ કરો" diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index b08230e110..319939afde 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -40,14 +40,14 @@ "होम स्क्रीन पर जोड़ें" "%1$s विजेट को होम स्क्रीन पर जोड़ा गया" "सुझाव" - "अपना दिन बेहतर बनाएं" + + "आपके लिए खबरें" "आपके मनोरंजन के लिए" "फ़िटनेस के लक्ष्य हासिल करें" "मौसम की अप-टू-डेट जानकारी पाएं" "शायद आपको ये भी पसंद आएं" - - + "%1$s के विजेट दाईं ओर, खोज का विजेट और अन्य विकल्प बाईं ओर" "{count,plural, =1{# विजेट}one{# विजेट}other{# विजेट}}" "{count,plural, =1{# शॉर्टकट}one{# शॉर्टकट}other{# शॉर्टकट}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "निजी" "प्राइवेट स्पेस सेटिंग" "प्राइवेट स्पेस को लॉक करें/अनलॉक करें" + + "प्राइवेट स्पेस की सेटिंग में बदलाव किया जा रहा है" "ऐप्लिकेशन इंस्टॉल करें" "प्राइवेट स्पेस में ऐप्लिकेशन इंस्टॉल करें" diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 1d85b4c59f..46b0abf757 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -40,14 +40,14 @@ "Dodaj na početni zaslon" "Widget %1$s dodan je na početni zaslon" "Prijedlozi" - "Unaprijedite svoj dan" + + "Vijesti za vas" "Vaša zona za opuštanje" "Postignite svoje ciljeve u fitnesu" "Budite korak ispred vremenskih prilika" "Možda će vam se svidjeti i ovo" - - + "%1$s –widgeti zdesna, pretraživanje i opcije slijeva" "{count,plural, =1{# widget}one{# widget}few{# widgeta}other{# widgeta}}" "{count,plural, =1{# prečac}one{# prečac}few{# prečaca}other{# prečaca}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privatno" "Postavke privatnog prostora" "Zaključavanje/otključavanje privatnog prostora" + + "Prelazak na privatni prostor" "Instaliranje aplikacija" "Instaliranje aplikacija u privatni prostor" diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 3ee1143a8c..06d17f5f61 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -40,14 +40,14 @@ "Hozzáadás a kezdőképernyőhöz" "%1$s modul hozzáadva a kezdőképernyőhöz" "Javaslatok" - "Felturbózhatja a napját" + + "Hírek Önnek" "Az Ön relaxáló zónája" "Elérheti kitűzött erőnléti céljait" "Mindig friss időjárás-információk" "Lehet, hogy ez is tetszeni fog" - - + "A %1$s-modulok a jobb, a kereső és a beállítások pedig a bal oldalon találhatók" "{count,plural, =1{# modul}other{# modul}}" "{count,plural, =1{# gyorsparancs}other{# gyorsparancs}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privát" "Privát terület beállításai" "Privát terület zárolása/zárolásának feloldása" + + "Átállás privát területre…" "Alkalmazástelepítés" "Alkalmazások telepítése magánterületre" diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index 4f69c17b76..0e4a120713 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -40,14 +40,14 @@ "Ավելացնել հիմնական էկրանին" "%1$s վիջեթն ավելացվել է հիմնական էկրանին" "Առաջարկներ" - "Ակտիվացրեք ձեր օրը" + + "Նորություններ ձեզ համար" "Ձեր հանգստի գոտին" "Հասեք ձեր ֆիթնես նպատակներին" "Եղեք տեղեկացված եղանակի մասին" "Ձեզ կարող է դուր գալ" - - + %1$s» հավելվածի վիջեթներն աջ կողմում են, իսկ որոնման դաշտը և կարգավորումները՝ ձախ կողմում" "{count,plural, =1{# վիջեթ}one{# վիջեթ}other{# վիջեթ}}" "{count,plural, =1{# դյուրանցում}one{# դյուրանցում}other{# դյուրանցում}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Անձնական" "Անձնական տարածքի կարգավորումներ" "Կողպել/ապակողպել անձնական տարածքը" + + "Անցում անձնական տարածք" "Հավելվածների տեղադրում" "Հավելվածների տեղադրում անձնական տարածքում" diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index a1bd81ea3a..3e325b320d 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -40,14 +40,14 @@ "Tambahkan ke layar utama" "Widget %1$s ditambahkan ke layar utama" "Saran" - "Tingkatkan hari Anda" + + "Berita untuk Anda" "Zona Nyaman Anda" "Capai Target Kebugaran Anda" "Tetap Waspada Menghadapi Cuaca" "Anda Mungkin Juga Suka" - - + "Widget %1$s di bagian kanan, penelusuran dan opsi di bagian kiri" "{count,plural, =1{# widget}other{# widget}}" "{count,plural, =1{# pintasan}other{# pintasan}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Pribadi" "Setelan Ruang Pribadi" "Kunci/Buka Kunci Ruang Pribadi" + + "Ruang Pribadi Bertransisi" "Menginstal aplikasi" "Instal aplikasi ke Ruang Pribadi" diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index 034362eddb..b2e91d47d2 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -40,14 +40,14 @@ "Bæta á heimaskjá" "%1$s græju bætt við heimaskjá" "Tillögur" - "Gerðu daginn betri" + + "Fréttir fyrir þig" "Slakaðu á" "Náðu hreyfingarmarkmiðunum þínum" "Vertu einu skrefi á undan veðrinu" "Þú gætir einnig haft áhuga á" - - + "%1$s-græjur til hægri, leit og valkostir til vinstri" "{count,plural, =1{# græja}one{# græja}other{# græjur}}" "{count,plural, =1{# flýtileið}one{# flýtileið}other{# flýtileiðir}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Lokað" "Stillingar einkarýmis" "Læsaeinkarými/taka einkarými úr lás" + + "Einkarými að breytast" "Setja upp forrit" "Setja upp forrit í einkarými" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 808ee5bc78..f259595162 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -40,14 +40,14 @@ "Aggiungi alla schermata Home" "Widget %1$s aggiunto alla schermata Home" "Suggerimenti" - "Dai la carica alla tua giornata" + + "Notizie per te" "Il tuo angolo di tranquillità" "Raggiungi i tuoi obiettivi di fitness" "Non perderti le previsioni meteo" "Ti potrebbero anche piacere" - - + "Widget di %1$s a destra, ricerca e opzioni a sinistra" "{count,plural, =1{# widget}other{# widget}}" "{count,plural, =1{# scorciatoia}other{# scorciatoie}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privato" "Impostazioni dello Spazio privato" "Blocca/sblocca Spazio privato" + + "Transizione dello Spazio privato in corso…" "Installa app" "Installa le app su spazi privati" diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index b0e4fc6d68..d22e937f02 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -40,14 +40,14 @@ "הוספה למסך הבית" "הווידג\'ט %1$s נוסף למסך הבית" "הצעות" - "משפרים את היום" + + "חדשות בשבילך" "המקום שלך לרגיעה" "השגת יעדי הכושר שלך" "התעדכנות במזג האוויר" "אולי יעניין אותך גם" - - + "‫%1$s ווידג\'טים מימין, חיפוש ואפשרויות משמאל" "{count,plural, =1{ווידג\'ט אחד}one{# ווידג\'טים}two{# ווידג\'טים}other{# ווידג\'טים}}" "{count,plural, =1{קיצור דרך אחד}one{# קיצורי דרך}two{# קיצורי דרך}other{# קיצורי דרך}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "פרטי" "הגדרות המרחב הפרטי" "נעילה או ביטול הנעילה של המרחב הפרטי" + + "מעבר למרחב הפרטי" "התקנת אפליקציות" "התקנת אפליקציות במרחב הפרטי" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 9cb3fd57ae..769af4b698 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -40,14 +40,14 @@ "ホーム画面に追加" "「%1$s」ウィジェットをホーム画面に追加しました" "候補" - "生産性を向上" + + "あなたへのおすすめニュース" "休憩エリア" "フィットネスの目標を達成" "天気予報" "あなたへのおすすめ" - - + "%1$s のウィジェットは右側に、検索とオプションは左側にあります" "{count,plural, =1{# 件のウィジェット}other{# 件のウィジェット}}" "{count,plural, =1{# 件のショートカット}other{# 件のショートカット}}" "%1$s%2$s" @@ -185,6 +185,8 @@ "プライベート" "プライベート スペースの設定" "プライベート スペースをロック / ロック解除する" + + "プライベート スペース移行中" "アプリをインストールする" "プライベート スペースにアプリをインストールします" diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index d765680b84..43249d835e 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -40,14 +40,14 @@ "მთავარ ეკრანზე დამატება" "%1$s ვიჯეტი დამატებულია მთავარ ეკრანზე" "შეთავაზებები" - "გაიუმჯობესეთ დღე" + + "News თქვენთვის" "განტვირთვის ადგილი" "მიაღწიეთ ფიტნეს-მიზნებს" "მიიღეთ ინფორმაცია წინასწარ ამინდის შესახებ" "ასევე შეიძლება მოგეწონოთ" - - + "%1$s ვიჯეტები მდებარეობს მარჯვნივ, ძებნა და პარამეტრები — მარცხნივ" "{count,plural, =1{# ვიჯეტი}other{# ვიჯეტი}}" "{count,plural, =1{# მალსახმობი}other{# მალსახმობი}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "პირადი" "პირადი სივრცის პარამეტრები" "პირადი სივრცის ჩაკეტვა/განბლოკვა" + + "პირად სივრცეზე გადასვლა" "აპების ინსტალაცია" "კერძო სივრცეში აპების ინსტალაცია" diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml index e0f14ba0ee..005f86079e 100644 --- a/res/values-kk/strings.xml +++ b/res/values-kk/strings.xml @@ -40,14 +40,14 @@ "Негізгі экранға қосу" "%1$s виджеті негізгі экранға енгізілді." "Ұсыныстар" - "Күні бойы қуатты болыңыз" + + "Сізге арналған жаңалықтар" "Жанға жайлы жер" "Денені шынықтыру бойынша қойған мақсаттарыңызға жетіңіз" "Ауа райын алдын ала біліп отырыңыз" "Сізге мыналар да ұнауы мүмкін" - - + "%1$s виджеттері оң жақта, іздеу мен опциялар сол жақта" "{count,plural, =1{# виджет}other{# виджет}}" "{count,plural, =1{# таңбаша}other{# таңбаша}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Жеке" "Жеке бөлме параметрлері" "Жеке бөлмені құлыптау/оның құлпын ашу" + + "Жеке бөлмеге өту" "Қолданбалар орнату" "Қолданбаларды \"Құпия кеңістікке\" орнатыңыз." diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index c68194afa5..abce94a805 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -40,14 +40,14 @@ "បញ្ចូល​ទៅក្នុង​អេក្រង់​ដើម" "បានបញ្ចូល​ធាតុក្រាហ្វិក %1$s ទៅ​អេក្រង់ដើម" "ការណែនាំ" - "​ពង្រឹង​សុខុមាលភាពប្រចាំថ្ងៃរបស់អ្នក" + + "ព័ត៌មាន​សម្រាប់​អ្នក" "តំបន់បន្ធូរ​អារម្មណ៍របស់អ្នក" "សម្រេច​គោលដៅ​ហាត់ប្រាណ​របស់អ្នក" "ទទួលបានដំណឹងជាមុនអំពីអាកាសធាតុ" "អ្នក​ក៏​អាច​នឹង​ចូលចិត្ត" - - + "ធាតុក្រាហ្វិក %1$s នៅខាងស្ដាំ ការស្វែងរក និងជម្រើសនៅខាងឆ្វេង" "{count,plural, =1{ធាតុ​ក្រាហ្វិក #}other{ធាតុ​ក្រាហ្វិក #}}" "{count,plural, =1{ផ្លូវកាត់ #}other{ផ្លូវកាត់ #}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "ឯកជន" "ការកំណត់ Private Space" "ចាក់សោ/ដោះសោ Private Space" + + "ការផ្លាស់ប្ដូរ Private Space" "ដំឡើង​កម្មវិធី" "ដំឡើងកម្មវិធីទៅលំហឯកជន" diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml index c82288fb75..4815371e08 100644 --- a/res/values-kn/strings.xml +++ b/res/values-kn/strings.xml @@ -40,14 +40,14 @@ "ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಸೇರಿಸಿ" "ಹೋಮ್‌ಸ್ಕ್ರೀನ್‌ಗೆ %1$s ವಿಜೆಟ್ ಅನ್ನು ಸೇರಿಸಲಾಗಿದೆ" "ಸಲಹೆಗಳು" - "ನಿಮ್ಮ ದಿನವನ್ನು ಇನ್ನಷ್ಟು ಉತ್ಸಾಹಗೊಳಿಸಿ" + + "ನಿಮಗಾಗಿ ಸುದ್ದಿ" "ನೀವು ವಿಶ್ರಾಂತಿ ಪಡೆಯುವ ಸ್ಥಳ" "ನಿಮ್ಮ ಫಿಟ್‌ನೆಸ್ ಗುರಿಗಳನ್ನು ಸಾಧಿಸಿ" "ಹವಾಮಾನದ ಕುರಿತು ಮುಂಚೆಯೇ ಅಪ್‌ಡೇಟ್‌ ಆಗಿರಿ" "ನಿಮಗೆ ಇವು ಕೂಡ ಇಷ್ಟವಾಗಬಹುದು" - - + "ಬಲಭಾಗದಲ್ಲಿ %1$s ವಿಜೆಟ್‌ಗಳು, ಎಡಭಾಗದಲ್ಲಿ ಹುಡುಕಾಟ ಮತ್ತು ಆಯ್ಕೆಗಳು" "{count,plural, =1{# ವಿಜೆಟ್}one{# ವಿಜೆಟ್‌ಗಳು}other{# ವಿಜೆಟ್‌ಗಳು}}" "{count,plural, =1{# ಶಾರ್ಟ್‌ಕಟ್}one{# ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು}other{# ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "ಖಾಸಗಿ" "ಖಾಸಗಿ ಸ್ಪೇಸ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು" "ಖಾಸಗಿ ಸ್ಪೇಸ್ ಅನ್ನು ಲಾಕ್/ಅನ್‌ಲಾಕ್ ಮಾಡಿ" + + "ಖಾಸಗಿ ಸ್ಪೇಸ್ ಪರಿವರ್ತನೆಯಾಗುತ್ತಿದೆ" "ಆ್ಯಪ್‌ಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ" "ಆ್ಯಪ್‌ಗಳನ್ನು ಪ್ರೈವೇಟ್ ಸ್ಪೇಸ್‌ನಲ್ಲಿ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ" diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index d8e64d6b3c..fc0d8bb93d 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -40,14 +40,14 @@ "홈 화면에 추가" "%1$s 위젯이 홈 화면에 추가됨" "추천" - "생산성 향상" + + "추천 뉴스" "휴식 공간" "피트니스 목표 달성" "사전에 날씨 확인" "좋아할 만한 항목" - - + "오른쪽에 %1$s 위젯, 왼쪽에 검색 및 옵션" "{count,plural, =1{위젯 #개}other{위젯 #개}}" "{count,plural, =1{바로가기 #개}other{바로가기 #개}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "비공개" "비공개 스페이스 설정" "비공개 스페이스 잠금/잠금 해제" + + "비공개 스페이스 전환" "앱 설치" "비공개 스페이스에 앱 설치" diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml index 8b2fef4bdd..dff3f00e94 100644 --- a/res/values-ky/strings.xml +++ b/res/values-ky/strings.xml @@ -40,14 +40,14 @@ "Башкы экранга кошуу" "%1$s виджети башкы экранга кошулду" "Сунуштар" - "Майнаптуу күн" + + "Сиз үчүн жаңылыктар" "Чер жазуу" "Фитнес максаттарыңызга жетиңиз" "Аба ырайы тууралуу маалымат" "Төмөнкүлөр да жагышы мүмкүн" - - + "%1$s виджеттери оң, ал эми издөө жана параметрлер сол жакта" "{count,plural, =1{# виджет}other{# виджет}}" "{count,plural, =1{# ыкчам баскыч}other{# ыкчам баскыч}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Жеке" "Жеке чөйрөнүн параметрлери" "Жеке чөйрөнү кулпулоо/кулпусун ачуу" + + "Жеке чөйрөгө өтүү" "Колдонмолорду орнотуу" "Колдонмолорду Жеке мейкиндикке орнотуe" diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml index 99f30424a1..328d108697 100644 --- a/res/values-lo/strings.xml +++ b/res/values-lo/strings.xml @@ -40,14 +40,14 @@ "ເພີ່ມໃສ່ໂຮມສະກຣີນ" "ເພີ່ມວິດເຈັດ %1$s ໃສ່ໂຮມສະກຣີນແລ້ວ" "ການແນະນຳ" - "ເຮັດໃຫ້ເປັນມື້ທີ່ມີປະສິດທິພາບ" + + "ຂ່າວສຳລັບທ່ານ" "ພື້ນທີ່ພັກຜ່ອນຂອງທ່ານ" "ບັນລຸເປົ້າໝາຍການອອກກຳລັງກາຍຂອງທ່ານ" "ຮູ້ສະພາບອາກາດລ່ວງໜ້າ" "ທ່ານອາດຈະມັກ" - - + "ວິດເຈັດ %1$s ຢູ່ທາງຂວາ, ການຊອກຫາ ແລະ ຕົວເລືອກຢູ່ທາງຊ້າຍ" "{count,plural, =1{# ວິດເຈັດ}other{# ວິດເຈັດ}}" "{count,plural, =1{# ທາງລັດ}other{# ທາງລັດ}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "ສ່ວນຕົວ" "ການຕັ້ງຄ່າພື້ນທີ່ສ່ວນຕົວ" "ລັອກ/ປົດລັອກພື້ນທີ່ສ່ວນຕົວ" + + "ການປ່ຽນແປງພື້ນທີ່ສ່ວນຕົວ" "ຕິດຕັ້ງແອັບ" "ຕິດຕັ້ງແອັບໄປໃສ່ພື້ນທີ່ສ່ວນບຸກຄົນ" diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 6579674d3b..db18d7bf39 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -40,14 +40,14 @@ "Pridėti prie pagrindinio ekrano" "Valdiklis „%1$s“ pridėtas prie pagrindinio ekrano" "Pasiūlymai" - "Padidinkite dienos produktyvumą" + + "Naujienos jums" "Jūsų atsipalaidavimo zona" "Pasiekite mankštos tikslus" "Visada žinokite, kokie bus orai" "Jums taip pat gali patikti" - - + "%1$s valdikliai dešinėje, paieška ir parinktys kairėje" "{count,plural, =1{# valdiklis}one{# valdiklis}few{# valdikliai}many{# valdiklio}other{# valdiklių}}" "{count,plural, =1{# spartusis klavišas}one{# spartusis klavišas}few{# spartieji klavišai}many{# sparčiojo klavišo}other{# sparčiųjų klavišų}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privatus" "Privačios erdvės nustatymai" "Užrakinti ir (arba) atrakinti privačią erdvę" + + "Privačios erdvės perkėlimas" "Programų diegimas" "Įdiegti programas privačioje erdvėje" diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index 7df06a9232..f0e58df749 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -40,14 +40,14 @@ "Pievienot sākuma ekrānam" "Logrīks “%1$s” ir pievienots sākuma ekrānam" "Ieteikumi" - "Produktīvākai dienai" + + "Ziņas jums" "Jūsu atpūtas stūrītis" "Sasniedziet fitnesa mērķus" "Neļaujiet laikapstākļiem jūs pārsteigt" "Jums varētu patikt arī…" - - + "Pa labi logrīki %1$s, pa kreisi meklēšana un iespējas" "{count,plural, =1{# logrīks}zero{# logrīku}one{# logrīks}other{# logrīki}}" "{count,plural, =1{# saīsne}zero{# saīšņu}one{# saīsne}other{# saīsnes}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privātā mape" "Privātās mapes iestatījumi" "Bloķēt/atbloķēt privāto mapi" + + "Pāriet uz privāto mapi" "Lietotņu instalēšana" "Instalējiet lietotnes privātajā telpā." diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index 7dd8926c58..51b2b96bc6 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -40,14 +40,14 @@ "Додај на почетниот екран" "Виџетот %1$s е додаден на почетниот екран" "Предлози" - "Подобрете го денот" + + "Вести за вас" "Вашата зона за релаксација" "Достигнете ги целите за фитнес" "Бидете во тек со временската прогноза" "Можеби ќе ви се допадне и" - - + "%1$s виџети оддесно, „Пребарување“ и „Опции“ одлево" "{count,plural, =1{# виџет}one{# виџет}other{# виџети}}" "{count,plural, =1{# кратенка}one{# кратенка}other{# кратенки}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Приватен" "Поставки за „Приватен простор“" "Заклучување/отклучување на „Приватен простор“" + + "Префрлање на „Приватен простор“" "Инсталирање апликации" "Инсталирање апликации во „Приватен простор“" diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index 2abab01a74..3f515b8630 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -40,14 +40,14 @@ "ഹോം സ്‌ക്രീനിലേക്ക് ചേർക്കുക" "%1$s വിജറ്റ് ഹോം സ്‌ക്രീനിലേക്ക് ചേർത്തു" "നിർദ്ദേശങ്ങൾ" - "കൂടുതൽ കാര്യക്ഷമമാകൂ" + + "നിങ്ങൾക്കായുള്ള വാർത്ത" "നിങ്ങൾക്ക് സുഖപ്രദമായ സ്ഥലം" "ശാരീരികക്ഷമതയുമായി ബന്ധപ്പെട്ട ലക്ഷ്യങ്ങൾ കൈവരിക്കൂ" "കാലാവസ്ഥ മുൻകൂട്ടി മനസ്സിലാക്കുക" "നിങ്ങൾക്ക് ഇനിപ്പറയുന്നവ ഇഷ്ടമായേക്കാം" - - + "വലതുവശത്ത് %1$s വിജറ്റുകളും ഇടതുവശത്ത് തിരയൽ, ഓപ്ഷനുകൾ എന്നിവയും" "{count,plural, =1{# വിജറ്റ്}other{# വിജറ്റുകൾ}}" "{count,plural, =1{# കുറുക്കുവഴി}other{# കുറുക്കുവഴികൾ}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "സ്വകാര്യം" "സ്വകാര്യ സ്‌പേസ് ക്രമീകരണം" "സ്വകാര്യ സ്‌പേസ് ലോക്ക് ചെയ്യുക/അൺലോക്ക് ചെയ്യുക" + + "പ്രൈവറ്റ് സ്‌പേസ് ട്രാൻസിഷനിംഗ്" "ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യുക" "സ്വകാര്യ സ്പേസിലേക്ക് ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യുക" diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml index 71b7d4f08d..0dd648aecf 100644 --- a/res/values-mn/strings.xml +++ b/res/values-mn/strings.xml @@ -40,14 +40,14 @@ "Үндсэн нүүрэнд нэмэх" "%1$s виджетийг үндсэн нүүрэнд нэмсэн" "Зөвлөмжүүд" - "Өдрөө сайхан болгоорой" + + "Танд зориулсан мэдээ" "Таны амралтын бүс" "Фитнесийн зорилгодоо хүрээрэй" "Цаг агаарын урьдчилсан мэдээлэлтэй байгаарай" "Танд таалагдаж магадгүй" - - + "Баруун талд %1$s-н виджет, зүүн талд хайлт болон сонгуултууд байна" "{count,plural, =1{# виджет}other{# виджет}}" "{count,plural, =1{# товчлол}other{# товчлол}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Хувийн" "Private Space-н тохиргоо" "Private Space-г түгжих/түгжээг тайлах" + + "Private Space-н шилжилт" "Аппуудыг суулгах" "Хувийн орон зайд аппууд суулгана уу" diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index 7b1ceb975a..9d0f2bf510 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -40,14 +40,14 @@ "होम स्क्रीनवर जोडा" "%1$s हे विजेट तुमच्या होम स्क्रीनवर जोडले आहे" "सूचना" - "तुमचा दिवस आणखी उत्साहवर्धक करा" + + "तुमच्यासाठी बातम्या" "तुमचा आरामदायक झोन" "तुमची फिटनेस ध्येये गाठा" "हवामानासंबंधित बातम्या आगामी मिळवा" "तुम्हाला हेदेखील आवडू शकते" - - + "उजवीकडे %1$s विजेट, डावीकडे शोध आणि पर्याय" "{count,plural, =1{# विजेट}other{# विजेट}}" "{count,plural, =1{# शॉर्टकट}other{# शॉर्टकट}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "खाजगी" "खाजगी स्पेस ची सेटिंग्ज" "खाजगी स्पेस लॉक/अनलॉक करा" + + "खाजगी स्पेस वर स्विच करणे" "अ‍ॅप्स इंस्टॉल करा" "अ‍ॅप्स खाजगी स्पेस मध्ये इंस्टॉल करा" diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index 94e89e0e50..83fd3eea6d 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -40,14 +40,14 @@ "Tambahkan pada skrin utama" "Widget %1$s ditambahkan pada skrin utama" "Cadangan" - "Tingkatkan hari anda" + + "Berita Untuk Anda" "Zon Santai Anda" "Capai Matlamat Kecergasan Anda" "Ketahui Perkembangan Terkini Cuaca" "Anda Mungkin Turut Menyukai" - - + "Widget %1$s pada sebelah kanan, carian dan pilihan pada sebelah kiri" "{count,plural, =1{# widget}other{# widget}}" "{count,plural, =1{# pintasan}other{# pintasan}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Peribadi" "Tetapan Ruang Peribadi" "Kunci/Buka kunci Ruang Peribadi" + + "Peralihan Ruang Peribadi" "Pasang apl" "Pasang apl pada Ruang Peribadi" diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index c10ddcd10b..c832434ad3 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -40,14 +40,14 @@ "ပင်မစာမျက်နှာတွင် ထည့်ရန်" "%1$s ဝိဂျက်ကို ပင်မစာမျက်နှာတွင် ထည့်လိုက်ပြီ" "အကြံပြုချက်" - "သင့်နေ့ကို မွမ်းမံရန်" + + "သင့်အတွက် သတင်းများ" "သင်အနားယူသောနေရာ" "သင့်ကြံ့ခိုင်ရေးပန်းတိုင်ဆီ သွားရန်" "မိုးလေဝသကို ကြိုတင်ကာကွယ်ရန်" "သင်နှစ်သက်နိုင်သောအရာများ" - - + "%1$s ဝိဂျက်များသည် ညာဘက်တွင်ရှိပြီး ရှာဖွေမှုနှင့် ရွေးစရာများသည် ဘယ်ဘက်တွင်ရှိသည်" "{count,plural, =1{ဝိဂျက် # ခု}other{ဝိဂျက် # ခု}}" "{count,plural, =1{ဖြတ်လမ်းလင့်ခ် # ခု}other{ဖြတ်လမ်းလင့်ခ် # ခု}}" "%1$s%2$s" @@ -185,6 +185,8 @@ "သီးသန့်" "သီးသန့်ချတ်ခန်း ဆက်တင်များ" "သီးသန့်ချတ်ခန်း လော့ခ်ချ/ဖွင့်ရန်" + + "သီးသန့်ချတ်ခန်း အပြောင်းအလဲ" "အက်ပ်များ ထည့်သွင်းခြင်း" "‘သီးသန့်နေရာ’ တွင် အက်ပ်များ ထည့်သွင်းနိုင်သည်" diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index ee73d51343..69697dc252 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -40,14 +40,14 @@ "Legg til på startskjermen" "%1$s-modulen er lagt til på startskjermen" "Forslag" - "Få en bedre dag" + + "Nyheter for deg" "Avslappingssonen din" "Nå treningsmålene dine" "Hold deg i forkant av været" "Kanskje du også liker" - - + "%1$s moduler til høyre, søk og alternativer til venstre" "{count,plural, =1{# modul}other{# moduler}}" "{count,plural, =1{# snarvei}other{# snarveier}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privat" "Innstillinger for Private Space" "Lås / lås opp Private Space" + + "Private Space-overgang" "Installer apper" "Installer apper i privat område" diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index f061a821e5..c6d2e48928 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -40,14 +40,14 @@ "होम स्क्रिनमा राख्नुहोस्" "होम स्क्रिनमा %1$s विजेट हालियो" "सुझावहरू" - "आफ्नो आजको पर्फर्मेन्स बढाउनुहोस्" + + "तपाईंका निम्ति सिफारिस गरिएका समाचार" "तपाईंको Chill Zone" "आफूले तय गरेको तन्दुरुस्तीको लक्ष्यमा पुग्नुहोस्" "मौसमको पूर्वानुमान प्राप्त गर्नुहोस्" "तपाईंलाई निम्न कुराहरू पनि मन पर्न सक्छन्" - - + "दायाँ भागमा %1$s विजेटहरू, बायाँ भागमा खोज र विकल्पहरू" "{count,plural, =1{# विजेट}other{# वटा विजेट}}" "{count,plural, =1{# सर्टकट}other{# वटा सर्टकट}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "निजी" "निजी स्पेससम्बन्धी सेटिङ" "निजी स्पेस लक/अनलक गर्नुहोस्" + + "निजी स्पेस ट्रान्जिसन गरिँदै छ" "एपहरू इन्स्टल गर्नुहोस्" "निजी स्पेसमा एपहरू इन्स्टल गर्नुहोस्" diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 557fab3b57..c9d1e519cd 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -40,14 +40,14 @@ "Toevoegen aan startscherm" "Widget %1$s toegevoegd aan startscherm" "Suggesties" - "Geef je dag een boost" + + "Nieuws voor jou" "Je chillzone" "Behaal je fitnessdoelen" "Blijf het weer een stap voor" "Misschien ook interessant" - - + "%1$s-widgets aan de rechterkant, zoeken en opties aan de linkerkant" "{count,plural, =1{# widget}other{# widgets}}" "{count,plural, =1{# snelkoppeling}other{# snelkoppelingen}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privé" "Instellingen voor privéruimte" "Privéruimte vergrendelen/ontgrendelen" + + "Overschakelen naar privéruimte" "Apps installeren" "Apps installeren in privégedeelte" diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml index ed62a3576d..16cadba0a3 100644 --- a/res/values-or/strings.xml +++ b/res/values-or/strings.xml @@ -40,14 +40,14 @@ "ହୋମ ସ୍କ୍ରିନରେ ଯୋଗ କରନ୍ତୁ" "%1$sର ୱିଜେଟ ହୋମ ସ୍କ୍ରିନରେ ଯୋଡ଼ାଗଲା" "ପରାମର୍ଶଗୁଡ଼ିକ" - "ଆପଣଙ୍କ ଦିନକୁ ବୁଷ୍ଟ କରନ୍ତୁ" + + "ଆପଣଙ୍କ ପାଇଁ ନ୍ୟୁଜ" "ଆପଣଙ୍କ ଚିଲ ଜୋନ" "ଆପଣଙ୍କ ଫିଟନେସ ଲକ୍ଷ୍ୟରେ ପହଞ୍ଚନ୍ତୁ" "ପାଣିପାଗ ବିଷୟରେ ଆଗୁଆ ସୂଚନା ପାଆନ୍ତୁ" "ଆପଣ ମଧ୍ୟ ପସନ୍ଦ କରିପାରନ୍ତି" - - + "ଡାହାଣରେ %1$s ୱିଜେଟଗୁଡ଼ିକ ଅଛି, ବାମରେ ସର୍ଚ୍ଚ ଓ ବିକଳ୍ପଗୁଡ଼ିକ ଅଛି" "{count,plural, =1{# ୱିଜେଟ}other{# ୱିଜେଟ}}" "{count,plural, =1{#ଟି ସର୍ଟକଟ୍}other{#ଟି ସର୍ଟକଟ୍}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "ପ୍ରାଇଭେଟ" "ପ୍ରାଇଭେଟ ସ୍ପେସ ସେଟିଂସ" "ପ୍ରାଇଭେଟ ସ୍ପେସକୁ ଲକ/ଅନଲକ କରନ୍ତୁ" + + "ପ୍ରାଇଭେଟ ସ୍ପେସ ଟ୍ରାଞ୍ଜିସନିଂ" "ଆପ୍ ଇନଷ୍ଟଲ୍ କରନ୍ତୁ" "ଆପ୍ସକୁ ପ୍ରାଇଭେଟ ସ୍ପେସରେ ଇନଷ୍ଟଲ କରନ୍ତୁ" diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index 661f035672..c83186d47b 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -40,14 +40,14 @@ "ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ" "%1$s ਵਿਜੇਟ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ" "ਸੁਝਾਅ" - "ਆਪਣੇ ਦਿਨ ਨੂੰ ਬੂਸਟ ਕਰੋ" + + "ਤੁਹਾਡੇ ਲਈ ਖਬਰਾਂ" "ਤੁਹਾਡੇ ਲਈ ਸਕੂਨਮਈ ਖੇਤਰ" "ਆਪਣੇ ਫਿੱਟਨੈੱਸ ਸੰਬੰਧੀ ਟੀਚੇ ਹਾਸਲ ਕਰੋ" "ਮੌਸਮ ਬਾਰੇ ਅੱਪ-ਟੂ-ਡੇਟ ਰਹੋ" "ਸ਼ਾਇਦ ਤੁਸੀਂ ਇਹ ਵੀ ਪਸੰਦ ਕਰੋ" - - + "%1$s ਵਿਜੇਟ ਸੱਜੇ ਪਾਸੇ ਹਨ, ਖੋਜ ਵਿਜੇਟ ਅਤੇ ਹੋਰ ਵਿਕਲਪ ਖੱਬੇ ਪਾਸੇ ਹਨ" "{count,plural, =1{# ਵਿਜੇਟ}one{# ਵਿਜੇਟ}other{# ਵਿਜੇਟ}}" "{count,plural, =1{# ਸ਼ਾਰਟਕੱਟ}one{# ਸ਼ਾਰਟਕੱਟ}other{# ਸ਼ਾਰਟਕੱਟ}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "ਨਿੱਜੀ" "ਨਿੱਜੀ ਸਪੇਸ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ" "ਨਿੱਜੀ ਸਪੇਸ ਨੂੰ ਲਾਕ/ਅਣਲਾਕ ਕਰੋ" + + "ਨਿੱਜੀ ਸਪੇਸ ਨੂੰ ਤਬਦੀਲ ਕਰਨਾ" "ਐਪਾਂ ਸਥਾਪਤ ਕਰੋ" "ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਵਿੱਚ ਐਪਾਂ ਸਥਾਪਤ ਕਰੋ" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index a2627ff659..4cf4beb502 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -40,14 +40,14 @@ "Dodaj do ekranu głównego" "Widżet %1$s został dodany do ekranu głównego" "Sugestie" - "To Ci się przyda na co dzień" + + "Wiadomości dla Ciebie" "Strefa relaksu" "Zadbaj o swoją formę" "Nie daj się zaskoczyć pogodzie" "To też może Cię zainteresować" - - + "Widżety (%1$s) po prawej, wyszukiwanie i opcje po lewej" "{count,plural, =1{# widżet}few{# widżety}many{# widżetów}other{# widżetu}}" "{count,plural, =1{# skrót}few{# skróty}many{# skrótów}other{# skrótu}}" "%1$s%2$s" @@ -185,6 +185,8 @@ "Prywatne" "Ustawienia obszaru prywatnego" "Zablokuj/odblokuj obszar prywatny" + + "Przenoszenie obszaru prywatnego" "Instalowanie aplikacji" "Zainstaluj aplikacje w przestrzeni prywatnej" diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 46ace14c4e..4304a5ab01 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -40,14 +40,14 @@ "Adicionar ao ecrã principal" "Widget %1$s adicionado ao ecrã principal" "Sugestões" - "Melhorar o seu dia" + + "Notícias para si" "A sua zona de relaxamento" "Atingir os seus objetivos de fitness" "Ficar a par da meteorologia" "Também poderá gostar de" - - + "Widgets de %1$s à direita, pesquisa e opções à esquerda" "{count,plural, =1{# widget}other{# widgets}}" "{count,plural, =1{# atalho}other{# atalhos}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privado" "Definições do espaço privado" "Bloquear/desbloquear espaço privado" + + "Transição do espaço privado" "Instalar apps" "Instale apps no espaço privado" diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index f12bd4fed3..6256567783 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -23,7 +23,7 @@ "Trabalho" "O app não está instalado." "O app não está disponível" - "App transferido por download desativado no modo de segurança" + "App baixado desativado no modo de segurança" "Widgets desativados no modo de segurança" "O atalho não está disponível" "Início" @@ -40,14 +40,14 @@ "Adicionar à tela inicial" "Widget %1$s adicionado à tela inicial" "Sugestões" - "Melhore seu dia" + + "Notícias para você" "Sua zona de relaxamento" "Alcance seus objetivos fitness" "Fique por dentro da previsão do tempo" "Você também pode gostar de" - - + "Widgets da %1$s à direita, pesquisa e opções à esquerda" "{count,plural, =1{# widget}one{# widget}other{# widgets}}" "{count,plural, =1{# atalho}one{# atalho}other{# atalhos}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Particular" "Configurações do Espaço particular" "Bloquear/desbloquear o Espaço particular" + + "Espaço particular em transição" "Instalar apps" "Instalar apps no espaço privado" diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index 93fb1b176d..3bc7d353f6 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -40,14 +40,14 @@ "Adaugă pe ecranul de pornire" "Widgetul %1$s a fost adăugat pe ecranul de pornire" "Sugestii" - "Lucrează mai productiv" + + "Știri pentru tine" "Zona de relaxare" "Atinge-ți obiectivele de fitness" "Fii la curent cu prognoza meteo" "S-ar putea să îți placă și" - - + "Widgeturi pentru %1$s în dreapta, căutare și opțiuni în stânga" "{count,plural, =1{# widget}few{# widgeturi}other{# de widgeturi}}" "{count,plural, =1{# comandă rapidă}few{# comenzi rapide}other{# de comenzi rapide}}" "%1$s %2$s" @@ -185,6 +185,8 @@ "Privat" "Setări spațiu privat" "Blochează / deblochează spațiul privat" + + "Tranziție pentru spațiul privat" "Instalează aplicații" "Instalează aplicații în Spațiul privat" diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 4942eeb203..63c7f767f1 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -40,14 +40,14 @@ "Добавить на главный экран" "Виджет \"%1$s\" добавлен на главный экран" "Подсказки" - "Эффективная работа" + + "Новости для вас" "Развлечение и общение" "Ваши фитнес-цели" "Прогноз погоды" "Другие рекомендации" - - + "Виджеты приложения \"%1$s\" находятся справа, а панель поиска и настройки – слева" "{count,plural, =1{# виджет}one{# виджет}few{# виджета}many{# виджетов}other{# виджета}}" "{count,plural, =1{# ярлык}one{# ярлык}few{# ярлыка}many{# ярлыков}other{# ярлыка}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Доступно только вам" "Настройки личного пространства" "Блокировка и разблокировка личного пространства" + + "Переход к личному пространству" "Установить приложения" "Установить приложения в личном пространстве" diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml index a0b54ebfc0..78d3249e32 100644 --- a/res/values-si/strings.xml +++ b/res/values-si/strings.xml @@ -40,14 +40,14 @@ "මුල් තිරය වෙත එක් කරන්න" "%1$s විජට්ටුව මුල් පිටු තිරය වෙත එක් කරන ලදි" "යෝජනා" - "ඔබේ දවස වැඩි කරන්න" + + "ඔබ වෙනුවෙන් පුවත්" "ඔබේ නිවුණු කලාපය" "ඔබේ යෝග්‍යතා ඉලක්ක ළඟා කර ගන්න" "කාලගුණයට ඉදිරියෙන් සිටින්න" "ඔබ මේවාට ද කැමති විය හැක" - - + "දකුණේ %1$s විජට්, වමේ සෙවීම සහ විකල්ප" "{count,plural, =1{විජට් #}one{විජට් #}other{විජට් #}}" "{count,plural, =1{කෙටි මං #}one{කෙටි මං #}other{කෙටි මං #}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "පෞද්ගලික" "පෞද්ගලික අවකාශ සැකසීම්" "පෞද්ගලික අවකාශය අගුළු දමන්න/අගුළු හරින්න" + + "පෞද්ගලික අවකාශ සංක්‍රමණය" "යෙදුම් ස්ථාපනය කරන්න" "පෞද්ගලික අවකාශයට යෙදුම් ස්ථාපනය කරන්න" diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 7853de169b..4cf5f7bc98 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -40,14 +40,14 @@ "Pridať na plochu" "Na plochu bola pridaná miniaplikácia %1$s" "Návrhy" - "Zvýšte dnes svoju produktivitu" + + "Vaše správy" "Vaša komfortná zóna" "Dosiahnite svoje kondičné ciele" "Získavajte informácie o počasí v predstihu" "Mohlo by sa vám páčiť" - - + "Miniaplikácie %1$s vpravo, vyhľadávanie a možnosti vľavo" "{count,plural, =1{# miniaplikácia}few{# miniaplikácie}many{# widgets}other{# miniaplikácií}}" "{count,plural, =1{# odkaz}few{# odkazy}many{# shortcuts}other{# odkazov}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Súkromné" "Nastavenia súkromného priestoru" "Súkromný priestor zamykania a odomykania" + + "Prechod súkromného priestoru" "Inštalovať aplikácie" "Inštalácia aplikácií v súkromnom priestore" diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index c1f6599b93..6fa10ba710 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -40,14 +40,14 @@ "Dodaj na začetni zaslon" "Pripomoček »%1$s« je dodan na začetni zaslon." "Predlogi" - "Dajte pospešek svojemu dnevu" + + "Novice za vas" "Vaš kotiček za sprostitev" "Dosegajte cilje glede telesne pripravljenosti" "Bodite na tekočem z vremenom" "Morda vam bo všeč tudi" - - + "Pripomočki %1$s na desni, iskanje in možnosti na levi" "{count,plural, =1{# pripomoček}one{# pripomoček}two{# pripomočka}few{# pripomočki}other{# pripomočkov}}" "{count,plural, =1{# bližnjica}one{# bližnjica}two{# bližnjici}few{# bližnjice}other{# bližnjic}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Zasebno" "Nastavitve zasebnega prostora" "Zaklepanje/odklepanje zasebnega prostora" + + "Preklapljanje zasebnega prostora" "Nameščanje aplikacij" "Nameščanje aplikacij v zasebni prostor" diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index 9dab5420da..edc1aad1de 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -40,14 +40,14 @@ "Shto në ekranin bazë" "Miniaplikacioni %1$s u shtua në ekranin bazë" "Sugjerime" - "Përmirëso ditën tënde" + + "Lajme për ty" "Zona jote e qetësisë" "Realizo objektivat e stërvitjes" "Qëndro i informuar për motin" "Gjithashtu mund të të pëlqejë" - - + "Miniaplikacionet e %1$s në të djathtë, kërkimi dhe opsionet në të majtë" "{count,plural, =1{# miniaplikacion}other{# miniaplikacione}}" "{count,plural, =1{# shkurtore}other{# shkurtore}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Private" "Cilësimet e \"Hapësirës private\"" "Kyç/Shkyç \"Hapësirën private\"" + + "Kalimi te \"Hapësira private\"" "Instalo aplikacionet" "Instalo aplikacionet në hapësirën private" diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index 8553e58bb6..8550fa32b8 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -40,14 +40,14 @@ "Додај на почетни екран" "Додали сте виџет %1$s на почетни екран" "Предлози" - "Побољшајте дан" + + "Вести за вас" "Зона за опуштање" "Остварите фитнес циљеве" "Будите у току са временским приликама" "Можда ће вам се допасти и" - - + "Виџети %1$s са десне стране, претрага и опције са леве стране" "{count,plural, =1{# виџет}one{# виџет}few{# виџета}other{# виџета}}" "{count,plural, =1{# пречица}one{# пречица}few{# пречице}other{# пречица}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Приватно" "Подешавања приватног простора" "Закључај/откључај приватни простор" + + "Пренос приватног простора" "Инсталирајте апликације" "Инсталирај апликације у приватан простор" diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 24cf747143..fae79c7260 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -40,14 +40,14 @@ "Lägg till på startskärmen" "Widget för %1$s har lagts till på startskärmen" "Förslag" - "Få ut mer av din dag" + + "Nyheter för dig" "Koppla av" "Nå dina träningsmål" "Håll koll på vädret" "Andra appar du kanske gillar" - - + "Widgetar för %1$s till höger, sökning och alternativ till vänster" "{count,plural, =1{# widget}other{# widgetar}}" "{count,plural, =1{# genväg}other{# genvägar}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Privat" "Inställningar för privat rum" "Lås eller lås upp ditt privata rum" + + "Överföring av privat rum" "Installera appar" "Installera appar i privat rum" diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index 4537d82551..693beaeaae 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -40,14 +40,14 @@ "Weka kwenye skrini ya kwanza" "Umeongeza wijeti ya %1$s kwenye skrini ya kwanza" "Mapendekezo" - "Boresha siku yako" + + "Habari Kwa Ajili Yako" "Mahali Pako pa Kupumzika" "Fikia Malengo Yako ya Siha" "Pata Taarifa kuhusu Hali ya Hewa" "Huenda Pia Ukapenda" - - + "Wijeti za %1$s ziko upande wa kulia, utafutaji na chaguo ziko upande wa kushoto" "{count,plural, =1{Wijeti #}other{Wijeti #}}" "{count,plural, =1{Njia # ya mkato}other{Njia # za mkato}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Ya Faragha" "Mipangilio ya Nafasi ya Faragha" "Funga/Fungua Nafasi ya Faragha" + + "Mabadiliko ya Nafasi ya Faragha" "Sakinisha programu" "Sakinisha programu kwenye Sehemu ya Faragha" diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml index fede476192..a7c323e44b 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -40,14 +40,14 @@ "முகப்புத் திரையில் சேர்" "%1$s விட்ஜெட் முகப்புத் திரையில் சேர்க்கப்பட்டது" "பரிந்துரைகள்" - "உங்கள் நாளை உற்சாகமாக்குங்கள்" + + "உங்களுக்கான செய்திகள்" "உங்கள் மனதுக்கு இதமானவை" "உடற்பயிற்சி இலக்குகளை அடையுங்கள்" "வானிலை குறித்து முன்கூட்டியே அறிந்திருங்கள்" "நீங்கள் இவற்றையும் விரும்பக்கூடும்" - - + "%1$s விட்ஜெட்கள் வலதுபுறத்தில் உள்ளன, தேடல் மற்றும் விருப்பங்கள் இடதுபுறத்தில் உள்ளன" "{count,plural, =1{# விட்ஜெட்}other{# விட்ஜெட்டுகள்}}" "{count,plural, =1{# ஷார்ட்கட்}other{# ஷார்ட்கட்கள்}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "தனிப்பட்டது" "தனிப்பட்ட சேமிப்பிட அமைப்புகள்" "தனிப்பட்ட சேமிப்பிடத்தை லாக்/அன்லாக் செய்யும்" + + "தனிப்பட்ட சேமிப்பிடத்திற்கு மாற்றுகிறது" "ஆப்ஸை நிறுவுதல்" "தனிப்பட்ட சேமிப்பிடத்தில் ஆப்ஸை நிறுவும்" diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index 9fcc88f101..f54a9f74f7 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -40,14 +40,14 @@ "మొదటి స్క్రీన్‌కు జోడించండి" "మొదటి స్క్రీన్‌కు %1$s విడ్జెట్ జోడించబడింది" "సూచనలు" - "మీ రోజును బూస్ట్ చేయండి" + + "మీ కోసం వార్తలు" "మీరు ప్రశాంతంగా ఉండే ప్రదేశం" "ఫిట్‌నెస్ లక్ష్యాలను చేరుకోండి" "వాతావరణాన్ని ముందుగానే తెలుసుకోండి" "మీరు వీటిని కూడా ఇష్టపడవచ్చు" - - + "కుడి వైపున %1$s విడ్జెట్‌లు, ఎడమ వైపున సెర్చ్, ఇతర ఆప్షన్‌లు" "{count,plural, =1{# విడ్జెట్}other{# విడ్జెట్‌లు}}" "{count,plural, =1{# షార్ట్‌కట్}other{# షార్ట్‌కట్‌లు}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "ప్రైవేట్" "ప్రైవేట్ స్పేస్ సెట్టింగ్‌లు" "ప్రైవేట్ స్పేస్‌ను లాక్/అన్‌లాక్ చేయండి" + + "ప్రైవేట్ స్పేస్ కేటాయించడం జరుగుతుంది" "యాప్‌లను ఇన్‌స్టాల్ చేయండి" "ప్రైవేట్ స్పేస్‌కు యాప్‌లను ఇన్‌స్టాల్ చేయండి" diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index d189ed486b..db07dc2116 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -40,14 +40,14 @@ "เพิ่มลงในหน้าจอหลัก" "เพิ่มวิดเจ็ต %1$s ลงในหน้าจอหลักแล้ว" "คำแนะนำ" - "เสริมสร้างวันของคุณ" + + "ข่าวสารสำหรับคุณ" "พื้นที่สบายๆ ของคุณ" "บรรลุเป้าหมายการออกกำลังกาย" "รู้สภาพอากาศล่วงหน้า" "คุณอาจชอบ" - - + "วิดเจ็ต%1$sทางด้านขวา การค้นหาและตัวเลือกทางด้านซ้าย" "{count,plural, =1{วิดเจ็ต # รายการ}other{วิดเจ็ต # รายการ}}" "{count,plural, =1{ทางลัด # รายการ}other{ทางลัด # รายการ}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "ส่วนตัว" "การตั้งค่าพื้นที่ส่วนตัว" "ล็อก/ปลดล็อกพื้นที่ส่วนตัว" + + "การเปลี่ยนไปใช้พื้นที่ส่วนตัว" "ติดตั้งแอป" "ติดตั้งแอปไปยังพื้นที่ส่วนตัว" diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index 3dedd43763..c1d51b5147 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -40,14 +40,14 @@ "Idagdag sa home screen" "Idinagdag sa home screen ang widget na %1$s" "Mga Suhestyon" - "I-boost ang iyong araw" + + "Balita para sa Iyo" "Ang Iyong Chill Zone" "Makamit ang Iyong Mga Layunin sa Fitness" "Manatiling Handa sa Lagay ng Panahon" "Baka Magustuhan Mo Rin" - - + "Mga widget ng %1$s sa kanan, paghahanap at mga opsyon sa kaliwa" "{count,plural, =1{# widget}one{# widget}other{# na widget}}" "{count,plural, =1{# shortcut}one{# shortcut}other{# na shortcut}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Pribado" "Mga Setting ng Pribadong Space" "I-lock/I-unlock ang Pribadong Space" + + "Pag-transition ng Pribadong Space" "Mag-install ng mga app" "Mag-install ng mga app sa Pribadong Space" diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 561f82ed2d..b5807f5eaf 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -40,14 +40,14 @@ "Ana ekrana ekle" "%1$s widget\'ı ana ekrana eklendi" "Öneriler" - "Gününüzü canlandırın" + + "Size özel haberler" "Huzur alanınız" "Fitness hedeflerinize ulaşın" "Havanın durumu sizi şaşırtmasın" "Şunları da beğenebilirsiniz" - - + "%1$s widget\'ları sağda, arama ve seçenekler solda" "{count,plural, =1{# widget}other{# widget}}" "{count,plural, =1{# kısayol}other{# kısayol}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Gizli" "Gizli Alan Ayarları" "Gizli Alanı Kilitleyin/Kilidini Açın" + + "Gizli Alana Geçiş" "Uygulamaları yükleme" "Uygulamaları özel alana yükleyin" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 83727f84fb..3c9852bf35 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -40,14 +40,14 @@ "Додати на головний екран" "Віджет %1$s додано на головний екран" "Пропозиції" - "Підвищуйте свою продуктивність" + + "Новини для вас" "Ваша зона розваг" "Досягайте своїх фітнес-цілей" "Завчасно дізнавайтеся про зміни погоди" "Вам також може сподобатися" - - + "%1$s: віджети праворуч, пошук і опції ліворуч" "{count,plural, =1{# віджет}one{# віджет}few{# віджети}many{# віджетів}other{# віджета}}" "{count,plural, =1{# ярлик}one{# ярлик}few{# ярлики}many{# ярликів}other{# ярлика}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Приватні" "Налаштування приватного простору" "Заблокувати/розблокувати приватний простір" + + "Перехід у приватний простір" "Установити додатки" "Установити додатки в особистому просторі" diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml index c38fde347b..2690a4cfb2 100644 --- a/res/values-ur/strings.xml +++ b/res/values-ur/strings.xml @@ -40,14 +40,14 @@ "ہوم اسکرین میں شامل کریں" "%1$s ویجیٹ کو ہوم اسکرین میں شامل کیا گیا" "تجاویز" - "اپنی آج کی کارکردگی کو بوسٹ کریں" + + "آپ کے لیے خبریں" "آپ کا آرام دہ زون" "اپنی تندرستی کے مقاصد حاصل کریں" "موسم سے باخبر رہیں" "آپ کو یہ بھی پسند آ سکتا ہے" - - + "%1$s دائیں طرف وجیٹس، بائیں طرف تلاش اور اختیارات" "{count,plural, =1{# ویجیٹ}other{# ویجیٹس}}" "{count,plural, =1{# شارٹ کٹ}other{# شارٹ کٹس}}" "%1$s، %2$s" @@ -185,6 +185,8 @@ "نجی" "نجی اسپیس کی ترتیبات" "نجی اسپیس کو مقفل کریں/غیر مقفل کریں" + + "نجی اسپیس کی منتقلی" "ایپس انسٹال کریں" "پرائیویٹ اسپیس میں ایپس انسٹال کریں" diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml index 5c885cdf15..31bdd3cff1 100644 --- a/res/values-uz/strings.xml +++ b/res/values-uz/strings.xml @@ -40,14 +40,14 @@ "Bosh ekranga chiqarish" "%1$s vidjeti bosh ekranga qoʻshildi" "Takliflar" - "Kuningizni yaxshilang" + + "Siz uchun yangiliklar" "Sokin hududingiz" "Fitness maqsadlaringizga erishing" "Doim ob-havodan oldinda yuring" "Sizga yoqishi mumkin" - - + "%1$s vidjetlari oʻngda, qidiruv va sozlamalar chapda" "{count,plural, =1{# ta vidjet}other{# ta vidjet}}" "{count,plural, =1{# ta yorliq}other{# ta yorliq}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Yopiq" "Shaxsiy xona sozlamalari" "Shaxsiy xonani ochish/qulflash" + + "Maxfiy joyga almashtirish" "Ilovalar oʻrnatish" "Ilovalarni Maxfiy makonga oʻrnatish" diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index cf667296db..24b2cce2ac 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -40,14 +40,14 @@ "Thêm vào màn hình chính" "Đã thêm tiện ích %1$s vào màn hình chính" "Nội dung đề xuất" - "Thúc đẩy hiệu quả cho ngày của bạn" + + "Tin tức cho bạn" "Giai điệu thư giãn của bạn" "Đạt được mục tiêu tập thể dục" "Luôn nắm bắt tình hình thời tiết" "Có thể bạn cũng thích" - - + "Tiện ích %1$s ở bên phải, công cụ tìm kiếm và tuỳ chọn ở bên trái" "{count,plural, =1{# tiện ích}other{# tiện ích}}" "{count,plural, =1{# lối tắt}other{# lối tắt}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Riêng tư" "Cài đặt không gian riêng tư" "Khoá/mở khoá không gian riêng tư" + + "Chuyển đổi sang không gian riêng tư" "Cài đặt ứng dụng" "Cài đặt ứng dụng vào Không gian riêng tư" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 5c9f7ac9bc..994c976755 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -40,14 +40,14 @@ "添加到主屏幕" "已将“%1$s”微件添加到主屏幕" "建议" - "您的日常必备" + + "更多相关新闻" "您的休闲区" "达成您的健身目标" "天气早知道" "您可能还会喜欢" - - + "右边是%1$s微件,左边是搜索功能和选项" "{count,plural, =1{# 个微件}other{# 个微件}}" "{count,plural, =1{# 个快捷方式}other{# 个快捷方式}}" "%1$s%2$s" @@ -185,6 +185,8 @@ "私密" "私密空间设置" "锁定/解锁私密空间" + + "私密空间转换" "安装应用" "将应用安装到私密空间" diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index e0e07c1d1a..2a27de30d6 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -40,14 +40,14 @@ "加去主畫面" "已經將「%1$s」小工具加咗去主畫面" "建議" - "日常必備" + + "你的專屬新聞" "放鬆專區" "向健身目標邁進" "隨時掌握天氣資料" "相關推薦" - - + "右邊係「%1$s」小工具,左邊係搜尋功能同選項" "{count,plural, =1{# 個小工具}other{# 個小工具}}" "{count,plural, =1{# 個捷徑}other{# 個捷徑}}" "%1$s%2$s" @@ -185,6 +185,8 @@ "私人" "「私人空間」設定" "鎖定/解鎖「私人空間」" + + "轉為「私人空間」" "安裝應用程式" "將應用程式安裝在「私人空間」中" diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index bd46e263f8..e802d9ac14 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -40,14 +40,14 @@ "新增至主畫面" "已將「%1$s」小工具新增到主畫面" "建議" - "日常必備" + + "你的專屬新聞" "放鬆專區" "達成健身目標" "隨時掌握天氣資訊" "你可能也會喜歡的內容" - - + "右邊是「%1$s」小工具,左邊是搜尋功能和選項" "{count,plural, =1{# 項小工具}other{# 項小工具}}" "{count,plural, =1{# 個捷徑}other{# 個捷徑}}" "%1$s%2$s" @@ -185,6 +185,8 @@ "私人" "私人空間設定" "鎖定/取消鎖定私人空間" + + "轉換私人空間狀態" "安裝應用程式" "將應用程式安裝在私人空間中" diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index e5c19248d5..b93953595d 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -40,14 +40,14 @@ "Faka kusikrini sasekhaya" "Iwijethi ye-%1$s yengezwe kusikrini sasekhaya" "Iziphakamiso" - "Thuthukisa usuku lwakho" + + "Izindaba Zakho" "Indawo Ozipholela Kuyo" "Finyelela Imigomo Yakho Yokufaneleka" "Hlale Wazi Ngesimo Sezulu" "Ungase Futhi Uthande" - - + "Amawijethi okuthi %1$s kwesokudla, ukusesha nokukhethwayo kwesobunxele" "{count,plural, =1{iwijethi #}one{amawijethi #}other{amawijethi #}}" "{count,plural, =1{isinqamuleli #}one{izinqamuleli #}other{izinqamuleli #}}" "%1$s, %2$s" @@ -185,6 +185,8 @@ "Okuyimfihlo" "Amasethingi Esikhala Esiyimfihlo" "Khiya/Vula Isikhala Esiyimfihlo" + + "Ukuguqulwa Kwendawo Yangasese" "Faka ama-app" "Faka ama-app Endaweni Engasese" From e599d65d99d29c909d1765bdf33ab3a04798698a Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 21 Feb 2024 16:47:49 -0800 Subject: [PATCH 09/24] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I2158617abd9cc208726b0d22a5ef177c930f164c --- res/values-pt/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index 29ce022ff5..c45658babd 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -23,7 +23,7 @@ "Trabalho" "O app não está instalado." "O app não está disponível" - "App transferido por download desativado no modo de segurança" + "App baixado desativado no modo de segurança" "Widgets desativados no modo de segurança" "O atalho não está disponível" "Início" From 4d3878310610d4dc33ea452ac8648a740c85776d Mon Sep 17 00:00:00 2001 From: bvineeth Date: Wed, 21 Feb 2024 05:16:06 +0000 Subject: [PATCH 10/24] Fix taskbar_expand/collapse jank monitor Issue : Currently opposite jank is being reported taskbar expand and taskbar collapse Test: Checked the trace Fixes: b/326333977 Flag: None Change-Id: Ib5adeb33506717ee28d669c5d46557087ebf7e60 --- .../com/android/launcher3/taskbar/TaskbarStashController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index 8db343fa3f..32dc50fa1a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -573,7 +573,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba } mAnimator = new AnimatorSet(); addJankMonitorListener( - mAnimator, /* expanding= */ !mIsStashed, /* animationType= */ animationType); + mAnimator, /* expanding= */ !isStashed, /* animationType= */ animationType); boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity); final float stashTranslation = mActivity.isPhoneMode() || isTransientTaskbar ? 0 From 21ab3b979b78d15d71f979f6290844b0dd1795ee Mon Sep 17 00:00:00 2001 From: Stefan Andonian Date: Thu, 15 Feb 2024 22:00:57 +0000 Subject: [PATCH 11/24] Remove Unused parts of LauncherPrefs causing cyclical dependency. This "migration" feature was planned as an effort to reduce latency upon startup. That being said, now that there is a loading screen being added to pixel startup time, it doesn't make sense to perform a risky data migration just to save a 100ms or so of startup time. Therefore, I am removing this code as a clean up effort to make LauncherPrefs simpler. Also, there was a cyclical dependency introduced in b/324670265 that makes this cleanup effort especially worthwhile, as it resolves a bug for other developers. Bug: 324670265 Test: Installed this on a device and tested that nothing was broken. Flag: NA Change-Id: I81df710057d80fbd071bc26a53cb191d453e7dee --- .../android/quickstep/BootAwarePreloader.kt | 52 ------- .../quickstep/TouchInteractionService.java | 1 - src/com/android/launcher3/LauncherPrefs.kt | 147 +++--------------- .../launcher3/config/FeatureFlags.java | 7 - .../android/launcher3/LauncherPrefsTest.kt | 123 +-------------- 5 files changed, 24 insertions(+), 306 deletions(-) delete mode 100644 quickstep/src/com/android/quickstep/BootAwarePreloader.kt diff --git a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt deleted file mode 100644 index 2fc4d044cf..0000000000 --- a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep - -import android.content.Context -import android.util.Log -import com.android.launcher3.LauncherAppState -import com.android.launcher3.LauncherPrefs -import com.android.launcher3.moveStartupDataToDeviceProtectedStorageIsEnabled -import com.android.launcher3.util.LockedUserState - -/** - * Loads expensive objects in memory before the user is unlocked. This decreases experienced latency - * when starting the launcher for the first time after a reboot. - */ -object BootAwarePreloader { - private const val TAG = "BootAwarePreloader" - - @JvmStatic - fun start(context: Context) { - val lp = LauncherPrefs.get(context) - when { - LockedUserState.get(context).isUserUnlocked || - !moveStartupDataToDeviceProtectedStorageIsEnabled -> { - /* No-Op */ - } - lp.isStartupDataMigrated -> { - Log.d(TAG, "preloading start up data") - LauncherAppState.INSTANCE.get(context) - } - else -> { - Log.d(TAG, "queuing start up data migration to boot aware prefs") - LockedUserState.get(context).runOnUserUnlocked { - lp.migrateStartupDataToDeviceProtectedStorage() - } - } - } - } -} diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index f9486bd6a8..8619b4d095 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -493,7 +493,6 @@ public class TouchInteractionService extends Service { mTaskbarManager = new TaskbarManager(this); mRotationTouchHelper = mDeviceState.getRotationTouchHelper(); mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer(); - BootAwarePreloader.start(this); // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized. LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked); diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt index b0a644b269..d7e6387ab1 100644 --- a/src/com/android/launcher3/LauncherPrefs.kt +++ b/src/com/android/launcher3/LauncherPrefs.kt @@ -19,7 +19,6 @@ import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener -import android.util.Log import android.view.ViewConfiguration import androidx.annotation.VisibleForTesting import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN @@ -38,8 +37,6 @@ import com.android.launcher3.util.Themes * Use same context for shared preferences, so that we use a single cached instance * * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods. - * TODO(b/274501660): Fix ReorderWidgets#simpleReorder test before enabling - * isBootAwareStartupDataEnabled */ class LauncherPrefs(private val encryptedContext: Context) { private val deviceProtectedStorageContext = @@ -52,22 +49,8 @@ class LauncherPrefs(private val encryptedContext: Context) { private val Item.encryptedPrefs get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE) - // This call to `SharedPreferences` needs to be explicit rather than using `get` since doing so - // would result in a circular dependency between `isStartupDataMigrated` and `choosePreferences` - val isStartupDataMigrated: Boolean - get() = - bootAwarePrefs.getBoolean( - IS_STARTUP_DATA_MIGRATED.sharedPrefKey, - IS_STARTUP_DATA_MIGRATED.defaultValue - ) - private fun chooseSharedPreferences(item: Item): SharedPreferences = - if ( - (moveStartupDataToDeviceProtectedStorageIsEnabled && - item.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && - isStartupDataMigrated) || item.encryptionType == EncryptionType.DEVICE_PROTECTED - ) - bootAwarePrefs + if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs else item.encryptedPrefs /** Wrapper around `getInner` for a `ContextualItem` */ @@ -147,11 +130,7 @@ class LauncherPrefs(private val encryptedContext: Context) { .toMutableMap() val bootAwareUpdates = - updates.filter { - (it.first.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && - moveStartupDataToDeviceProtectedStorageIsEnabled) || - it.first.encryptionType == EncryptionType.DEVICE_PROTECTED - } + updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED } if (bootAwareUpdates.isNotEmpty()) { updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates } @@ -252,12 +231,7 @@ class LauncherPrefs(private val encryptedContext: Context) { .groupBy { it.encryptedPrefs } .toMutableMap() - val bootAwareUpdates = - items.filter { - (it.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && - moveStartupDataToDeviceProtectedStorageIsEnabled) || - it.encryptionType == EncryptionType.DEVICE_PROTECTED - } + val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED } if (bootAwareUpdates.isNotEmpty()) { itemsPerFile[bootAwarePrefs] = bootAwareUpdates } @@ -269,24 +243,7 @@ class LauncherPrefs(private val encryptedContext: Context) { } } - fun migrateStartupDataToDeviceProtectedStorage() { - if (!moveStartupDataToDeviceProtectedStorageIsEnabled) return - - Log.d( - TAG, - "Migrating data to unencrypted shared preferences to enable preloading " + - "while the user is locked the next time the device reboots." - ) - - with(bootAwarePrefs.edit()) { - ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.forEach { putValue(it, get(it)) } - putBoolean(IS_STARTUP_DATA_MIGRATED.sharedPrefKey, true) - apply() - } - } - companion object { - private const val TAG = "LauncherPrefs" @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs" @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) } @@ -296,80 +253,50 @@ class LauncherPrefs(private val encryptedContext: Context) { const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY" const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY" @JvmField - val ICON_STATE = - nonRestorableItem("pref_icon_shape_path", "", EncryptionType.MOVE_TO_DEVICE_PROTECTED) + val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED) @JvmField val ALL_APPS_OVERVIEW_THRESHOLD = - nonRestorableItem( - "pref_all_apps_overview_threshold", - 180, - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) + nonRestorableItem("pref_all_apps_overview_threshold", 180, EncryptionType.ENCRYPTED) @JvmField val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE = - nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.MOVE_TO_DEVICE_PROTECTED) + nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.ENCRYPTED) @JvmField val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS = nonRestorableItem( "LPNH_TIMEOUT_MS", ViewConfiguration.getLongPressTimeout(), - EncryptionType.MOVE_TO_DEVICE_PROTECTED + EncryptionType.ENCRYPTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT = - nonRestorableItem( - "LPNH_HAPTIC_HINT_START_SCALE_PERCENT", - 0, - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) + nonRestorableItem("LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0, EncryptionType.ENCRYPTED) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT = - nonRestorableItem( - "LPNH_HAPTIC_HINT_END_SCALE_PERCENT", - 100, - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) + nonRestorableItem("LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100, EncryptionType.ENCRYPTED) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT = - nonRestorableItem( - "LPNH_HAPTIC_HINT_SCALE_EXPONENT", - 1, - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) + nonRestorableItem("LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1, EncryptionType.ENCRYPTED) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS = - nonRestorableItem( - "LPNH_HAPTIC_HINT_ITERATIONS", - 50, - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) + nonRestorableItem("LPNH_HAPTIC_HINT_ITERATIONS", 50, EncryptionType.ENCRYPTED) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY = - nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED) + nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.ENCRYPTED) @JvmField val PRIVATE_SPACE_APPS = - nonRestorableItem("pref_private_space_apps", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED) - @JvmField val ENABLE_TWOLINE_ALLAPPS_TOGGLE = - backedUpItem("pref_enable_two_line_toggle", false) + nonRestorableItem("pref_private_space_apps", 0, EncryptionType.ENCRYPTED) @JvmField - val THEMED_ICONS = - backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED) + val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false) + @JvmField + val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED) @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "") @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0) @JvmField val WORKSPACE_SIZE = - backedUpItem( - DeviceGridState.KEY_WORKSPACE_SIZE, - "", - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) + backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED) @JvmField val HOTSEAT_COUNT = - backedUpItem( - DeviceGridState.KEY_HOTSEAT_COUNT, - -1, - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) + backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED) @JvmField val TASKBAR_PINNING = backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED) @@ -379,11 +306,10 @@ class LauncherPrefs(private val encryptedContext: Context) { backedUpItem( DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, - EncryptionType.MOVE_TO_DEVICE_PROTECTED + EncryptionType.ENCRYPTED ) @JvmField - val DB_FILE = - backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.MOVE_TO_DEVICE_PROTECTED) + val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED) @JvmField val SHOULD_SHOW_SMARTSPACE = backedUpItem( @@ -396,15 +322,11 @@ class LauncherPrefs(private val encryptedContext: Context) { backedUpItem( RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, - EncryptionType.MOVE_TO_DEVICE_PROTECTED + EncryptionType.ENCRYPTED ) @JvmField val IS_FIRST_LOAD_AFTER_RESTORE = - nonRestorableItem( - FIRST_LOAD_AFTER_RESTORE_KEY, - false, - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) + nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED) @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "") @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "") @JvmField @@ -413,7 +335,7 @@ class LauncherPrefs(private val encryptedContext: Context) { "idp_grid_name", isBackedUp = true, defaultValue = null, - encryptionType = EncryptionType.MOVE_TO_DEVICE_PROTECTED, + encryptionType = EncryptionType.ENCRYPTED, type = String::class.java ) @JvmField @@ -421,14 +343,6 @@ class LauncherPrefs(private val encryptedContext: Context) { backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) { RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info) } - @JvmField - val IS_STARTUP_DATA_MIGRATED = - ConstantItem( - "is_startup_data_boot_aware", - isBackedUp = false, - defaultValue = false, - encryptionType = EncryptionType.DEVICE_PROTECTED - ) // Preferences for widget configurations @JvmField @@ -493,12 +407,6 @@ class LauncherPrefs(private val encryptedContext: Context) { } } -// It is a var because the unit tests are setting this to true so they can run. -var moveStartupDataToDeviceProtectedStorageIsEnabled: Boolean = - com.android.launcher3.config.FeatureFlags.MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE.get() - -private val ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE: MutableSet> = mutableSetOf() - abstract class Item { abstract val sharedPrefKey: String abstract val isBackedUp: Boolean @@ -518,14 +426,6 @@ data class ConstantItem( // The default value can be null. If so, the type needs to be explicitly stated, or else NPE override val type: Class = defaultValue!!::class.java ) : Item() { - init { - if ( - encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && - moveStartupDataToDeviceProtectedStorageIsEnabled - ) { - ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.add(this) - } - } fun get(c: Context): T = LauncherPrefs.get(c).get(this) } @@ -552,5 +452,4 @@ data class ContextualItem( enum class EncryptionType { ENCRYPTED, DEVICE_PROTECTED, - MOVE_TO_DEVICE_PROTECTED } diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 072a96ce6e..8c0dc89b53 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -100,13 +100,6 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_DISMISS_PREDICTION_UNDO = getDebugFlag(270394476, "ENABLE_DISMISS_PREDICTION_UNDO", DISABLED, "Show an 'Undo' snackbar when users dismiss a predicted hotseat item"); - - public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag( - 251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED, - "Marks LauncherPref data as (and allows it to) available while the device is" - + " locked. Enabling this causes a 1-time movement of certain SharedPreferences" - + " data. Improves startup latency."); - public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(270395171, "CONTINUOUS_VIEW_TREE_CAPTURE", ENABLED, "Capture View tree every frame"); diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/src/com/android/launcher3/LauncherPrefsTest.kt index 88a430bd32..b81309506a 100644 --- a/tests/src/com/android/launcher3/LauncherPrefsTest.kt +++ b/tests/src/com/android/launcher3/LauncherPrefsTest.kt @@ -25,8 +25,6 @@ import com.android.launcher3.LauncherPrefs.Companion.BOOT_AWARE_PREFS_KEY import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -import org.junit.AfterClass -import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith @@ -48,20 +46,6 @@ class LauncherPrefsTest { private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext } private val launcherPrefs by lazy { LauncherPrefs.get(context) } - companion object { - @BeforeClass - @JvmStatic - fun setup() { - moveStartupDataToDeviceProtectedStorageIsEnabled = true - } - - @AfterClass - @JvmStatic - fun teardown() { - moveStartupDataToDeviceProtectedStorageIsEnabled = false - } - } - @Test fun has_keyMissingFromLauncherPrefs_returnsFalse() { assertThat(launcherPrefs.has(TEST_BOOLEAN_ITEM)).isFalse() @@ -222,32 +206,13 @@ class LauncherPrefsTest { launcherPrefs.removeSync(bootAwareItem) } - @Test - fun put_bootAwareItem_updatesEncryptedStorage() { - val bootAwareItem = - LauncherPrefs.backedUpItem( - TEST_PREF_KEY, - TEST_DEFAULT_VALUE, - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) - - val encryptedPrefs: SharedPreferences = - context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE) - encryptedPrefs.edit().remove(bootAwareItem.sharedPrefKey).commit() - - launcherPrefs.putSync(bootAwareItem.to(TEST_STRING_ITEM.defaultValue)) - assertThat(encryptedPrefs.contains(bootAwareItem.sharedPrefKey)).isTrue() - - launcherPrefs.removeSync(bootAwareItem) - } - @Test fun remove_bootAwareItem_removesFromDeviceProtectedStorage() { val bootAwareItem = LauncherPrefs.backedUpItem( TEST_PREF_KEY, TEST_DEFAULT_VALUE, - EncryptionType.MOVE_TO_DEVICE_PROTECTED + EncryptionType.DEVICE_PROTECTED ) val bootAwarePrefs: SharedPreferences = @@ -263,90 +228,4 @@ class LauncherPrefsTest { launcherPrefs.removeSync(bootAwareItem) assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isFalse() } - - @Test - fun remove_bootAwareItem_removesFromEncryptedStorage() { - val bootAwareItem = - LauncherPrefs.backedUpItem( - TEST_PREF_KEY, - TEST_DEFAULT_VALUE, - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) - - val encryptedPrefs: SharedPreferences = - context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE) - - encryptedPrefs - .edit() - .putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue) - .commit() - - launcherPrefs.removeSync(bootAwareItem) - assertThat(encryptedPrefs.contains(bootAwareItem.sharedPrefKey)).isFalse() - } - - @Test - fun migrate_bootAwareItemsToDeviceProtectedStorage_worksAsIntended() { - val bootAwareItem = - LauncherPrefs.backedUpItem( - TEST_PREF_KEY, - TEST_DEFAULT_VALUE, - EncryptionType.MOVE_TO_DEVICE_PROTECTED - ) - launcherPrefs.removeSync(bootAwareItem) - - val bootAwarePrefs: SharedPreferences = - context - .createDeviceProtectedStorageContext() - .getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE) - - if (bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)) { - bootAwarePrefs.edit().remove(bootAwareItem.sharedPrefKey).commit() - } - - val encryptedPrefs: SharedPreferences = - context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE) - - encryptedPrefs - .edit() - .putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue) - .commit() - - launcherPrefs.migrateStartupDataToDeviceProtectedStorage() - assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isTrue() - - launcherPrefs.removeSync(bootAwareItem) - } - - @Test - fun migrate_onlyEncryptedItemsToDeviceProtectedStorage_doesNotHappen() { - val onlyEncryptedItem = - LauncherPrefs.backedUpItem( - TEST_PREF_KEY + "_", - TEST_DEFAULT_VALUE + "_", - EncryptionType.ENCRYPTED - ) - - val bootAwarePrefs: SharedPreferences = - context - .createDeviceProtectedStorageContext() - .getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE) - - if (bootAwarePrefs.contains(onlyEncryptedItem.sharedPrefKey)) { - bootAwarePrefs.edit().remove(onlyEncryptedItem.sharedPrefKey).commit() - } - - val encryptedPrefs: SharedPreferences = - context.getSharedPreferences(onlyEncryptedItem.sharedPrefFile, Context.MODE_PRIVATE) - - encryptedPrefs - .edit() - .putString(onlyEncryptedItem.sharedPrefKey, onlyEncryptedItem.defaultValue) - .commit() - - launcherPrefs.migrateStartupDataToDeviceProtectedStorage() - assertThat(bootAwarePrefs.contains(onlyEncryptedItem.sharedPrefKey)).isFalse() - - encryptedPrefs.edit().remove(onlyEncryptedItem.sharedPrefKey).commit() - } } From acf37d29dbe009758fb1443eb1b583b462489833 Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Tue, 20 Feb 2024 16:38:34 +0000 Subject: [PATCH 12/24] In 3-button landscape/seascape, override nav insets to always present - In 3-button landscape/seascape, Launcher should always have nav insets regardless if it's initiated from fullscreen apps - This avoid inset to change mid-way during app->overview transition via recents button - This bug doesn't happen in portrait because bottomNav is always overriden in WindowManagerProxy, we're now applying the same fix in landscape/seascape Test: Recents button from fullscreen app in 3-button landscape/seascape, and launch adjacent fullscreen app in Overview Test: Homescreen and widget picker in 3-button landscape/seascape Fix: 316085856 Flag: None Change-Id: Ia4ae7948fe794b71c49f89c43dd2d1d14031abb9 --- .../launcher3/util/window/WindowManagerProxy.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java index 6a0090c60b..4a906d33e1 100644 --- a/src/com/android/launcher3/util/window/WindowManagerProxy.java +++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java @@ -145,7 +145,18 @@ public class WindowManagerProxy implements ResourceBasedOverride { : (isGesture ? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) : 0)); - Insets newNavInsets = Insets.of(navInsets.left, navInsets.top, navInsets.right, bottomNav); + int leftNav = navInsets.left; + int rightNav = navInsets.right; + if (!isLargeScreen && !isGesture && !isPortrait) { + // In 3-button landscape/seascape, Launcher should always have nav insets regardless if + // it's initiated from fullscreen apps. + int navBarWidth = getDimenByName(systemRes, NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE); + switch (getRotation(context)) { + case Surface.ROTATION_90 -> rightNav = navBarWidth; + case Surface.ROTATION_270 -> leftNav = navBarWidth; + } + } + Insets newNavInsets = Insets.of(leftNav, navInsets.top, rightNav, bottomNav); insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets); insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets); From 72a74669e7826b085be40efb5b268459ad350ef1 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 14 Feb 2024 12:32:59 -0800 Subject: [PATCH 13/24] Adding support for async view inflation Bug: 318539160 Test: atest AsyncBindingTest; atest ItemInflaterTest Flag: aconfig enable_workspace_inflation DEVELOPMENT Change-Id: I77a373db7a5805f68f4b8cbfa9b586b5674252de --- .../uioverrides/QuickstepWidgetHolder.java | 14 + src/com/android/launcher3/Alarm.java | 7 +- src/com/android/launcher3/Launcher.java | 27 +- src/com/android/launcher3/ModelCallbacks.kt | 12 +- .../LauncherAccessibilityDelegate.java | 2 +- src/com/android/launcher3/folder/Folder.java | 9 +- .../android/launcher3/folder/FolderIcon.java | 3 +- .../android/launcher3/icons/IconCache.java | 19 +- .../launcher3/model/BaseLauncherBinder.java | 113 +++++-- .../android/launcher3/model/BgDataModel.java | 17 +- .../android/launcher3/model/ModelUtils.java | 7 +- .../android/launcher3/util/ItemInflater.kt | 2 +- .../android/launcher3/util/RunnableList.java | 7 + .../widget/LauncherAppWidgetHost.java | 15 +- .../widget/LauncherAppWidgetHostView.java | 17 +- .../widget/LauncherWidgetHolder.java | 66 +++- .../widget/PendingAppWidgetHostView.java | 60 ++-- .../launcher3/model/AsyncBindingTest.kt | 212 ++++++++++++ .../model/ModelMultiCallbacksTest.java | 2 +- .../launcher3/util/ItemInflaterTest.kt | 313 ++++++++++++++++++ 20 files changed, 832 insertions(+), 92 deletions(-) create mode 100644 tests/src/com/android/launcher3/model/AsyncBindingTest.kt create mode 100644 tests/src/com/android/launcher3/util/ItemInflaterTest.kt diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java index 0fb2b17eef..e3ff281e4d 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java @@ -219,6 +219,20 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { return () -> holderListener.mListeningHolders.remove(handler); } + /** + * Recycling logic: + * The holder doesn't maintain any states associated with the view, so if the view was + * initially initialized by this holder, all its state are already set in the view. We just + * update the RemoteViews for this view again, in case the widget sent an update during the + * time between inflation and recycle. + */ + @Override + protected LauncherAppWidgetHostView recycleExistingView(LauncherAppWidgetHostView view) { + RemoteViews views = getHolderListener(view.getAppWidgetId()).addHolder(mUpdateHandler); + view.updateAppWidget(views); + return view; + } + @NonNull @Override protected LauncherAppWidgetHostView createViewInternal( diff --git a/src/com/android/launcher3/Alarm.java b/src/com/android/launcher3/Alarm.java index e4aebf606d..fb8088c13b 100644 --- a/src/com/android/launcher3/Alarm.java +++ b/src/com/android/launcher3/Alarm.java @@ -17,6 +17,7 @@ package com.android.launcher3; import android.os.Handler; +import android.os.Looper; import android.os.SystemClock; public class Alarm implements Runnable{ @@ -33,7 +34,11 @@ public class Alarm implements Runnable{ private long mLastSetTimeout; public Alarm() { - mHandler = new Handler(); + this(Looper.myLooper()); + } + + public Alarm(Looper looper) { + mHandler = new Handler(looper); } public void setOnAlarmListener(OnAlarmListener alarmListener) { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index edfef5eaf2..1ab6222c98 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -28,6 +28,7 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE; import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType; import static com.android.launcher3.Flags.enableAddAppWidgetViaConfigActivityV2; +import static com.android.launcher3.Flags.enableWorkspaceInflation; import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY; import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; @@ -1485,8 +1486,8 @@ public class Launcher extends StatefulActivity CellPos presenterPos = getCellPosMapper().mapModelToPresenter(itemInfo); if (showPendingWidget) { launcherInfo.restoreStatus = LauncherAppWidgetInfo.FLAG_UI_NOT_READY; - PendingAppWidgetHostView pendingAppWidgetHostView = - new PendingAppWidgetHostView(this, launcherInfo, appWidgetInfo); + PendingAppWidgetHostView pendingAppWidgetHostView = new PendingAppWidgetHostView( + this, mAppWidgetHolder, launcherInfo, appWidgetInfo); pendingAppWidgetHostView.setPreviewBitmap(widgetPreviewBitmap); hostView = pendingAppWidgetHostView; } else if (hostView instanceof PendingAppWidgetHostView) { @@ -2187,17 +2188,23 @@ public class Launcher extends StatefulActivity */ @Override public void bindItems(final List items, final boolean forceAnimateIcons) { - bindItems(items.stream().map(i -> Pair.create( + bindInflatedItems(items.stream().map(i -> Pair.create( i, getItemInflater().inflateItem(i, getModelWriter()))).toList(), forceAnimateIcons ? new AnimatorSet() : null); } + @Override + public void bindInflatedItems(List> items) { + bindInflatedItems(items, null); + } + /** * Bind all the items in the map, ignoring any null views * * @param boundAnim if non-null, uses it to create and play the bounce animation for added views */ - public void bindItems(List> shortcuts, @Nullable AnimatorSet boundAnim) { + public void bindInflatedItems( + List> shortcuts, @Nullable AnimatorSet boundAnim) { // Get the list of added items and intersect them with the set of items here Workspace workspace = mWorkspace; int newItemsScreenId = -1; @@ -2222,10 +2229,13 @@ public class Launcher extends StatefulActivity } } - final View view = e.second; + View view = e.second; if (view == null) { continue; } + if (enableWorkspaceInflation() && view instanceof LauncherAppWidgetHostView lv) { + view = getAppWidgetHolder().attachViewToHostAndGetAttachedView(lv); + } workspace.addInScreenFromBind(view, item); if (boundAnim != null) { // Animate all the applications up now @@ -2324,9 +2334,9 @@ public class Launcher extends StatefulActivity @Override public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks, - int workspaceItemCount, boolean isBindSync) { - mModelCallbacks.onInitialBindComplete(boundPages, pendingTasks, workspaceItemCount, - isBindSync); + RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) { + mModelCallbacks.onInitialBindComplete(boundPages, pendingTasks, onCompleteSignal, + workspaceItemCount, isBindSync); } /** @@ -3057,6 +3067,7 @@ public class Launcher extends StatefulActivity return super.getStatsLogManager().withDefaultInstanceId(mAllAppsSessionLogId); } + @Override public ItemInflater getItemInflater() { return mItemInflater; } diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt index 9867556136..9b65a310eb 100644 --- a/src/com/android/launcher3/ModelCallbacks.kt +++ b/src/com/android/launcher3/ModelCallbacks.kt @@ -72,6 +72,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { override fun onInitialBindComplete( boundPages: LIntSet, pendingTasks: RunnableList, + onCompleteSignal: RunnableList, workspaceItemCount: Int, isBindSync: Boolean ) { @@ -99,7 +100,14 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { } } pendingExecutor = executor - executor.attachTo(launcher) + + if (Flags.enableWorkspaceInflation()) { + // Finish the executor as soon as the pending inflation is completed + onCompleteSignal.add(executor::markCompleted) + } else { + // Pending executor is already completed, wait until first draw to run the tasks + executor.attachTo(launcher) + } launcher.bindComplete(workspaceItemCount, isBindSync) } @@ -409,4 +417,6 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { } fun getIsFirstPagePinnedItemEnabled(): Boolean = isFirstPagePinnedItemEnabled + + override fun getItemInflater() = launcher.itemInflater } diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index a846e683ee..e861d38733 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -440,7 +440,7 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED))); } - mContext.bindItems(Collections.singletonList(Pair.create(item, view)), anim); + mContext.bindInflatedItems(Collections.singletonList(Pair.create(item, view)), anim); } /** diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index f013126409..ec9c27dfab 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -40,6 +40,7 @@ import android.graphics.Path; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; +import android.os.Looper; import android.text.InputType; import android.text.Selection; import android.text.TextUtils; @@ -165,10 +166,10 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private static final Rect sTempRect = new Rect(); private static final int MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10; - private final Alarm mReorderAlarm = new Alarm(); - private final Alarm mOnExitAlarm = new Alarm(); - private final Alarm mOnScrollHintAlarm = new Alarm(); - final Alarm mScrollPauseAlarm = new Alarm(); + private final Alarm mReorderAlarm = new Alarm(Looper.getMainLooper()); + private final Alarm mOnExitAlarm = new Alarm(Looper.getMainLooper()); + private final Alarm mOnScrollHintAlarm = new Alarm(Looper.getMainLooper()); + final Alarm mScrollPauseAlarm = new Alarm(Looper.getMainLooper()); final ArrayList mItemsInReadingOrder = new ArrayList(); diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 284b31e971..ee0d5fce24 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -32,6 +32,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Looper; import android.util.AttributeSet; import android.util.Property; import android.view.LayoutInflater; @@ -121,7 +122,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel boolean mAnimating = false; - private Alarm mOpenAlarm = new Alarm(); + private Alarm mOpenAlarm = new Alarm(Looper.getMainLooper()); private boolean mForceHideDot; @ViewDebug.ExportedProperty(category = "launcher", deepExport = true) diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index ee66a60c03..8e73660ae0 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -37,6 +37,7 @@ import android.content.pm.ShortcutInfo; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.graphics.drawable.Drawable; +import android.os.Looper; import android.os.Process; import android.os.Trace; import android.os.UserHandle; @@ -44,6 +45,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; +import androidx.annotation.AnyThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -65,7 +67,6 @@ import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.CancellableTask; import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.Preconditions; import com.android.launcher3.widget.WidgetSections; import com.android.launcher3.widget.WidgetSections.WidgetSection; @@ -173,9 +174,9 @@ public class IconCache extends BaseIconCache { * * @return a request ID that can be used to cancel the request. */ + @AnyThread public CancellableTask updateIconInBackground(final ItemInfoUpdateReceiver caller, final ItemInfoWithIcon info) { - Preconditions.assertUIThread(); Supplier task; if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) { task = () -> { @@ -193,13 +194,19 @@ public class IconCache extends BaseIconCache { return mCancelledTask; } - if (mPendingIconRequestCount <= 0) { - MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); + Runnable endRunnable; + if (Looper.myLooper() == Looper.getMainLooper()) { + if (mPendingIconRequestCount <= 0) { + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); + } + mPendingIconRequestCount++; + endRunnable = this::onIconRequestEnd; + } else { + endRunnable = () -> { }; } - mPendingIconRequestCount++; CancellableTask request = new CancellableTask<>( - task, MAIN_EXECUTOR, caller::reapplyItemInfo, this::onIconRequestEnd); + task, MAIN_EXECUTOR, caller::reapplyItemInfo, endRunnable); Utilities.postAsyncCallback(mWorkerHandler, request); return request; } diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java index 9b2344d2ad..fa2a1b01c7 100644 --- a/src/com/android/launcher3/model/BaseLauncherBinder.java +++ b/src/com/android/launcher3/model/BaseLauncherBinder.java @@ -16,20 +16,25 @@ package com.android.launcher3.model; +import static com.android.launcher3.Flags.enableWorkspaceInflation; import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL; import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING; import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.os.Process; import android.os.Trace; import android.util.Log; +import android.util.Pair; +import android.view.View; 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.Workspace; +import com.android.launcher3.celllayout.CellPosMapper; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.BgDataModel.FixedContainerItems; @@ -38,6 +43,7 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.ItemInflater; import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.PackageUserKey; @@ -279,8 +285,8 @@ public abstract class BaseLauncherBinder { // Separate the items that are on the current screen, and all the other remaining items ArrayList currentWorkspaceItems = new ArrayList<>(); ArrayList otherWorkspaceItems = new ArrayList<>(); - ArrayList currentAppWidgets = new ArrayList<>(); - ArrayList otherAppWidgets = new ArrayList<>(); + ArrayList currentAppWidgets = new ArrayList<>(); + ArrayList otherAppWidgets = new ArrayList<>(); filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems, otherWorkspaceItems); @@ -304,8 +310,8 @@ public abstract class BaseLauncherBinder { executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor); // Load items on the current page. - bindWorkspaceItems(currentWorkspaceItems, mUiExecutor); - bindAppWidgets(currentAppWidgets, mUiExecutor); + bindItemsInChunks(currentWorkspaceItems, ITEMS_CHUNK, mUiExecutor); + bindItemsInChunks(currentAppWidgets, 1, mUiExecutor); if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) { mExtraItems.forEach(item -> executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor)); @@ -313,8 +319,41 @@ public abstract class BaseLauncherBinder { RunnableList pendingTasks = new RunnableList(); Executor pendingExecutor = pendingTasks::add; - bindWorkspaceItems(otherWorkspaceItems, pendingExecutor); - bindAppWidgets(otherAppWidgets, pendingExecutor); + + RunnableList onCompleteSignal = new RunnableList(); + + if (enableWorkspaceInflation()) { + MODEL_EXECUTOR.execute(() -> { + setupPendingBind(otherWorkspaceItems, otherAppWidgets, currentScreenIds, + pendingExecutor); + + // Wait for the async inflation to complete and then notify the completion + // signal on UI thread. + MAIN_EXECUTOR.execute(onCompleteSignal::executeAllAndDestroy); + }); + } else { + setupPendingBind( + otherWorkspaceItems, otherAppWidgets, currentScreenIds, pendingExecutor); + onCompleteSignal.executeAllAndDestroy(); + } + + executeCallbacksTask( + c -> { + if (!enableWorkspaceInflation()) { + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + c.onInitialBindComplete(currentScreenIds, pendingTasks, onCompleteSignal, + workspaceItemCount, isBindSync); + }, mUiExecutor); + } + + private void setupPendingBind( + List otherWorkspaceItems, + List otherAppWidgets, + IntSet currentScreenIds, + Executor pendingExecutor) { + bindItemsInChunks(otherWorkspaceItems, ITEMS_CHUNK, pendingExecutor); + bindItemsInChunks(otherAppWidgets, 1, pendingExecutor); StringCache cacheClone = mBgDataModel.stringCache.clone(); executeCallbacksTask(c -> c.bindStringCache(cacheClone), pendingExecutor); @@ -326,38 +365,51 @@ public abstract class BaseLauncherBinder { ItemInstallQueue.INSTANCE.get(mApp.getContext()) .resumeModelPush(FLAG_LOADER_RUNNING); }); - - executeCallbacksTask( - c -> { - MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - c.onInitialBindComplete( - currentScreenIds, pendingTasks, workspaceItemCount, isBindSync); - }, mUiExecutor); } - private void bindWorkspaceItems( - final ArrayList workspaceItems, final Executor executor) { + /** + * Tries to inflate the items asynchronously and bind. Returns true on success or false if + * async-binding is not supported in this case. + */ + private boolean inflateAsyncAndBind(List items, Executor executor) { + if (!enableWorkspaceInflation()) { + return false; + } + ItemInflater inflater = mCallbacks.getItemInflater(); + if (inflater == null) { + return false; + } + + if (mMyBindingId != mBgDataModel.lastBindId) { + Log.d(TAG, "Too many consecutive reloads, skipping obsolete view inflation"); + return true; + } + + ModelWriter writer = mApp.getModel() + .getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null); + List> bindItems = items.stream().map(i -> + Pair.create(i, inflater.inflateItem(i, writer, null))).toList(); + executeCallbacksTask(c -> c.bindInflatedItems(bindItems), executor); + return true; + } + + private void bindItemsInChunks(List workspaceItems, int chunkCount, + Executor executor) { + if (inflateAsyncAndBind(workspaceItems, executor)) { + return; + } + // Bind the workspace items int count = workspaceItems.size(); - for (int i = 0; i < count; i += ITEMS_CHUNK) { + for (int i = 0; i < count; i += chunkCount) { final int start = i; - final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i); + final int chunkSize = (i + chunkCount <= count) ? chunkCount : (count - i); executeCallbacksTask( c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false), executor); } } - private void bindAppWidgets(List appWidgets, Executor executor) { - // Bind the widgets, one at a time - int count = appWidgets.size(); - for (int i = 0; i < count; i++) { - final ItemInfo widget = appWidgets.get(i); - executeCallbacksTask( - c -> c.bindItems(Collections.singletonList(widget), false), executor); - } - } - protected void executeCallbacksTask(CallbackTask task, Executor executor) { executor.execute(() -> { if (mMyBindingId != mBgDataModel.lastBindId) { @@ -430,8 +482,11 @@ public abstract class BaseLauncherBinder { bindAppWidgets(appWidgets); executeCallbacksTask(c -> { MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - c.onInitialBindComplete( - mCurrentScreenIds, new RunnableList(), workspaceItemCount, isBindSync); + + RunnableList onCompleteSignal = new RunnableList(); + onCompleteSignal.executeAllAndDestroy(); + c.onInitialBindComplete(mCurrentScreenIds, new RunnableList(), onCompleteSignal, + workspaceItemCount, isBindSync); }, mUiExecutor); } diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 7f0f683091..8579d1d682 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -33,6 +33,8 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -54,6 +56,7 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.IntSparseArrayMap; +import com.android.launcher3.util.ItemInflater; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.RunnableList; import com.android.launcher3.widget.model.WidgetsListBaseEntry; @@ -495,7 +498,15 @@ public class BgDataModel { default void clearPendingBinds() { } default void startBinding() { } - default void bindItems(List shortcuts, boolean forceAnimateIcons) { } + @Nullable + default ItemInflater getItemInflater() { + return null; + } + + default void bindItems(@NonNull List shortcuts, boolean forceAnimateIcons) { } + /** Alternate method to bind preinflated views */ + default void bindInflatedItems(@NonNull List> items) { } + default void bindScreens(IntArray orderedScreenIds) { } default void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) { } default void finishBindingItems(IntSet pagesBoundFirst) { } @@ -520,7 +531,9 @@ public class BgDataModel { default void bindSmartspaceWidget() { } /** Called when workspace has been bound. */ - default void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks, + default void onInitialBindComplete(@NonNull IntSet boundPages, + @NonNull RunnableList pendingTasks, + @NonNull RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) { pendingTasks.executeAllAndDestroy(); } diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java index bc51c9bfad..9e72e2823e 100644 --- a/src/com/android/launcher3/model/ModelUtils.java +++ b/src/com/android/launcher3/model/ModelUtils.java @@ -20,7 +20,6 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -37,9 +36,9 @@ public class ModelUtils { */ public static void filterCurrentWorkspaceItems( final IntSet currentScreenIds, - ArrayList allWorkspaceItems, - ArrayList currentScreenItems, - ArrayList otherScreenItems) { + List allWorkspaceItems, + List currentScreenItems, + List otherScreenItems) { // Purge any null ItemInfos allWorkspaceItems.removeIf(Objects::isNull); // Order the set of items by their containers first, this allows use to walk through the diff --git a/src/com/android/launcher3/util/ItemInflater.kt b/src/com/android/launcher3/util/ItemInflater.kt index 79091caaab..cc66af1189 100644 --- a/src/com/android/launcher3/util/ItemInflater.kt +++ b/src/com/android/launcher3/util/ItemInflater.kt @@ -121,7 +121,7 @@ class ItemInflater( } val view = if (type == WidgetInflater.TYPE_PENDING || widgetInfo == null) - PendingAppWidgetHostView(context, item, widgetInfo) + PendingAppWidgetHostView(context, widgetHolder, item, widgetInfo) else widgetHolder.createView(item.appWidgetId, widgetInfo) prepareAppWidget(view, item) return view diff --git a/src/com/android/launcher3/util/RunnableList.java b/src/com/android/launcher3/util/RunnableList.java index f6e0c57fb2..2b8bf56a3b 100644 --- a/src/com/android/launcher3/util/RunnableList.java +++ b/src/com/android/launcher3/util/RunnableList.java @@ -69,4 +69,11 @@ public class RunnableList { } } } + + /** + * Returns true if the list has been destroyed + */ + public boolean isDestroyed() { + return mDestroyed; + } } diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java index b1c477cbda..40c39840d6 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java @@ -54,6 +54,9 @@ class LauncherAppWidgetHost extends AppWidgetHost { @Nullable private final IntConsumer mAppWidgetRemovedCallback; + @Nullable + private ListenableHostView mViewToRecycle; + public LauncherAppWidgetHost(@NonNull Context context, @Nullable IntConsumer appWidgetRemovedCallback, List providerChangeListeners) { @@ -73,11 +76,21 @@ class LauncherAppWidgetHost extends AppWidgetHost { } } + /** + * Sets the view to be recycled for the next widget creation. + */ + public void recycleViewForNextCreation(ListenableHostView viewToRecycle) { + mViewToRecycle = viewToRecycle; + } + @Override @NonNull public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { - return new ListenableHostView(context); + ListenableHostView result = + mViewToRecycle != null ? mViewToRecycle : new ListenableHostView(context); + mViewToRecycle = null; + return result; } /** diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index e77ec12e18..2259e3c0cd 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -40,12 +40,12 @@ import androidx.annotation.Nullable; import com.android.launcher3.CheckLongPressHelper; import com.android.launcher3.Flags; -import com.android.launcher3.Launcher; import com.android.launcher3.R; -import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.util.Themes; +import com.android.launcher3.views.ActivityContext; +import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener; /** @@ -72,7 +72,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView private final Rect mTempRect = new Rect(); private final CheckLongPressHelper mLongPressHelper; - protected final Launcher mLauncher; + protected final ActivityContext mActivityContext; // Maintain the color manager. private final LocalColorExtractor mColorExtractor; @@ -94,15 +94,15 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView public LauncherAppWidgetHostView(Context context) { super(context); - mLauncher = Launcher.getLauncher(context); + mActivityContext = ActivityContext.lookupContext(context); mLongPressHelper = new CheckLongPressHelper(this, this); - setAccessibilityDelegate(mLauncher.getAccessibilityDelegate()); + setAccessibilityDelegate(mActivityContext.getAccessibilityDelegate()); setBackgroundResource(R.drawable.widget_internal_focus_bg); if (Flags.enableFocusOutline()) { setDefaultFocusHighlightEnabled(false); } - if (Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) { + if (Themes.getAttrBoolean(context, R.attr.isWorkspaceDarkText)) { setOnLightBackground(true); } mColorExtractor = new LocalColorExtractor(); // no-op @@ -120,8 +120,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView @Override public boolean onLongClick(View view) { if (mIsScrollable) { - DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.requestDisallowInterceptTouchEvent(false); + mActivityContext.getDragLayer().requestDisallowInterceptTouchEvent(false); } view.performLongClick(); return true; @@ -218,7 +217,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { - DragLayer dragLayer = mLauncher.getDragLayer(); + BaseDragLayer dragLayer = mActivityContext.getDragLayer(); if (mIsScrollable) { dragLayer.requestDisallowInterceptTouchEvent(true); } diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java index 23127b3e4d..15bd6ed1f2 100644 --- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java +++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java @@ -17,7 +17,9 @@ package com.android.launcher3.widget; import static android.app.Activity.RESULT_CANCELED; +import static com.android.launcher3.Flags.enableWorkspaceInflation; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo; import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetHostView; @@ -27,6 +29,7 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.Looper; import android.util.SparseArray; import android.widget.Toast; @@ -310,7 +313,9 @@ public class LauncherWidgetHolder { } /** - * Create a view for the specified app widget + * Create a view for the specified app widget. When calling this method from a background + * thread, the returned view will not receive ongoing updates. The caller needs to reattach + * the view using {@link #attachViewToHostAndGetAttachedView} on UIThread * * @param appWidgetId The ID of the widget * @param appWidget The {@link LauncherAppWidgetProviderInfo} of the widget @@ -327,7 +332,55 @@ public class LauncherWidgetHolder { } LauncherAppWidgetHostView view = createViewInternal(appWidgetId, appWidget); - mViews.put(appWidgetId, view); + // Do not update mViews on a background thread call, as the holder is not thread safe. + if (!enableWorkspaceInflation() || Looper.myLooper() == Looper.getMainLooper()) { + mViews.put(appWidgetId, view); + } + return view; + } + + /** + * Attaches an already inflated view to the host. If the view can't be attached, creates + * and attaches a new view. + * @return the final attached view + */ + @NonNull + public final AppWidgetHostView attachViewToHostAndGetAttachedView( + @NonNull LauncherAppWidgetHostView view) { + if (mViews.get(view.getAppWidgetId()) != view) { + view = recycleExistingView(view); + mViews.put(view.getAppWidgetId(), view); + } + return view; + } + + /** + * Recycling logic: + * 1) If the final view should be a pendingView + * if the provided view is also a pendingView, return itself + * otherwise discard provided view and return a new pending view + * 2) If the recycled view is a pendingView, discard it and return a new view + * 3) Use the same for as creating a new view, but used the provided view in the host instead + * of creating a new view. This ensures that all the host callbacks are properly attached + * as a result of using the same flow. + */ + protected LauncherAppWidgetHostView recycleExistingView(LauncherAppWidgetHostView view) { + if ((mFlags & FLAG_LISTENING) == 0) { + if (view instanceof PendingAppWidgetHostView pv && pv.isDeferredWidget()) { + return view; + } else { + return new PendingAppWidgetHostView(mContext, this, view.getAppWidgetId(), + fromProviderInfo(mContext, view.getAppWidgetInfo())); + } + } + LauncherAppWidgetHost host = (LauncherAppWidgetHost) mWidgetHost; + if (view instanceof ListenableHostView lhv) { + host.recycleViewForNextCreation(lhv); + } + + view = createViewInternal( + view.getAppWidgetId(), fromProviderInfo(mContext, view.getAppWidgetInfo())); + host.recycleViewForNextCreation(null); return view; } @@ -338,8 +391,15 @@ public class LauncherWidgetHolder { // Since the launcher hasn't started listening to widget updates, we can't simply call // host.createView here because the later will make a binder call to retrieve // RemoteViews from system process. - return new PendingAppWidgetHostView(mContext, appWidgetId, appWidget); + return new PendingAppWidgetHostView(mContext, this, appWidgetId, appWidget); } else { + if (enableWorkspaceInflation() && Looper.myLooper() != Looper.getMainLooper()) { + // Widget is being inflated a background thread, just create and + // return a placeholder view + ListenableHostView hostView = new ListenableHostView(mContext); + hostView.setAppWidget(appWidgetId, appWidget); + return hostView; + } try { return (LauncherAppWidgetHostView) mWidgetHost.createView( mContext, appWidgetId, appWidget); diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index adf85c70e0..86400baaf2 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -50,6 +50,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.icons.FastBitmapDrawable; @@ -65,7 +66,7 @@ import java.util.List; public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener, ItemInfoUpdateReceiver { private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5; - private static final float MIN_SATUNATION = 0.7f; + private static final float MIN_SATURATION = 0.7f; private static final int FLAG_DRAW_SETTINGS = 1; private static final int FLAG_DRAW_ICON = 2; @@ -75,6 +76,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView private final Rect mRect = new Rect(); + private final LauncherWidgetHolder mWidgetHolder; private final LauncherAppWidgetProviderInfo mAppwidget; private final LauncherAppWidgetInfo mInfo; private final int mStartState; @@ -90,6 +92,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView private Drawable mSettingIconDrawable; private boolean mDrawableSizeChanged; + private boolean mIsDeferredWidget; private final TextPaint mPaint; @@ -98,13 +101,13 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView @Nullable private Bitmap mPreviewBitmap; - public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info, - @Nullable LauncherAppWidgetProviderInfo appWidget) { - this(context, info, appWidget, + public PendingAppWidgetHostView(Context context, LauncherWidgetHolder widgetHolder, + LauncherAppWidgetInfo info, @Nullable LauncherAppWidgetProviderInfo appWidget) { + this(context, widgetHolder, info, appWidget, context.getResources().getText(R.string.gadget_complete_setup_text)); super.updateAppWidget(null); - setOnClickListener(mLauncher.getItemOnClickListener()); + setOnClickListener(mActivityContext.getItemOnClickListener()); if (info.pendingItemInfo == null) { info.pendingItemInfo = new PackageItemInfo(info.providerName.getPackageName(), @@ -117,14 +120,16 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView } public PendingAppWidgetHostView( - Context context, int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) { - this(context, new LauncherAppWidgetInfo(appWidgetId, appWidget.provider), + Context context, LauncherWidgetHolder widgetHolder, + int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) { + this(context, widgetHolder, new LauncherAppWidgetInfo(appWidgetId, appWidget.provider), appWidget, appWidget.label); getBackground().mutate().setAlpha(DEFERRED_ALPHA); mCenterDrawable = new ColorDrawable(Color.TRANSPARENT); mDragFlags = FLAG_DRAW_LABEL; mDrawableSizeChanged = true; + mIsDeferredWidget = true; } /** Set {@link Bitmap} of widget preview. */ @@ -136,10 +141,11 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView invalidate(); } - private PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info, + private PendingAppWidgetHostView(Context context, + LauncherWidgetHolder widgetHolder, LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo appwidget, CharSequence label) { super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme)); - + mWidgetHolder = widgetHolder; mAppwidget = appwidget; mInfo = info; mStartState = info.restoreStatus; @@ -148,9 +154,12 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView mPaint = new TextPaint(); mPaint.setColor(Themes.getAttrColor(getContext(), android.R.attr.textColorPrimary)); - mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, - mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics())); + mPaint.setTextSize(TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_PX, + mActivityContext.getDeviceProfile().iconTextSizePx, + getResources().getDisplayMetrics())); mPreviewPaint = new Paint(ANTI_ALIAS_FLAG | DITHER_FLAG | FILTER_BITMAP_FLAG); + setWillNotDraw(false); setBackgroundResource(R.drawable.pending_widget_bg); } @@ -160,6 +169,11 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView return mAppwidget; } + @Override + public int getAppWidgetId() { + return mInfo.appWidgetId; + } + @Override public void updateAppWidget(RemoteViews remoteViews) { checkIfRestored(); @@ -172,6 +186,10 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView } } + public boolean isDeferredWidget() { + return mIsDeferredWidget; + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -184,8 +202,8 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView if (mOnDetachCleanup != null) { mOnDetachCleanup.close(); } - mOnDetachCleanup = mLauncher.getAppWidgetHolder() - .addOnUpdateListener(mInfo.appWidgetId, mAppwidget, this::checkIfRestored); + mOnDetachCleanup = mWidgetHolder.addOnUpdateListener( + mInfo.appWidgetId, mAppwidget, this::checkIfRestored); checkIfRestored(); } } @@ -211,11 +229,13 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView // This occurs when LauncherAppWidgetHostView is used to render a preview layout. return; } - // Remove and rebind the current widget (which was inflated in the wrong - // orientation), but don't delete it from the database - mLauncher.removeItem(this, info, false /* deleteFromDb */, - "widget removed because of configuration change"); - mLauncher.bindAppWidget(info); + if (mActivityContext instanceof Launcher launcher) { + // Remove and rebind the current widget (which was inflated in the wrong + // orientation), but don't delete it from the database + launcher.removeItem(this, info, false /* deleteFromDb */, + "widget removed because of configuration change"); + launcher.bindAppWidget(info); + } } @Override @@ -303,7 +323,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView // Make the dominant color bright. float[] hsv = new float[3]; Color.colorToHSV(dominantColor, hsv); - hsv[1] = Math.min(hsv[1], MIN_SATUNATION); + hsv[1] = Math.min(hsv[1], MIN_SATURATION); hsv[2] = 1; mSettingIconDrawable.setColorFilter(Color.HSVToColor(hsv), PorterDuff.Mode.SRC_IN); } @@ -344,7 +364,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView } private void updateDrawableBounds() { - DeviceProfile grid = mLauncher.getDeviceProfile(); + DeviceProfile grid = mActivityContext.getDeviceProfile(); int paddingTop = getPaddingTop(); int paddingBottom = getPaddingBottom(); int paddingLeft = getPaddingLeft(); diff --git a/tests/src/com/android/launcher3/model/AsyncBindingTest.kt b/tests/src/com/android/launcher3/model/AsyncBindingTest.kt new file mode 100644 index 0000000000..af367a814a --- /dev/null +++ b/tests/src/com/android/launcher3/model/AsyncBindingTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.model + +import android.os.Looper +import android.platform.test.flag.junit.SetFlagsRule +import android.util.Pair +import android.util.SparseArray +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.Flags +import com.android.launcher3.model.BgDataModel.Callbacks +import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.util.Executors.MAIN_EXECUTOR +import com.android.launcher3.util.Executors.MODEL_EXECUTOR +import com.android.launcher3.util.IntArray +import com.android.launcher3.util.IntSet +import com.android.launcher3.util.ItemInflater +import com.android.launcher3.util.LauncherLayoutBuilder +import com.android.launcher3.util.LauncherModelHelper +import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE +import com.android.launcher3.util.RunnableList +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.Spy +import org.mockito.kotlin.any +import org.mockito.kotlin.argThat +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.isNull +import org.mockito.kotlin.never +import org.mockito.kotlin.reset +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** Tests to verify async binding of model views */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class AsyncBindingTest { + + @get:Rule val setFlagsRule = SetFlagsRule() + + @Spy private var callbacks = MyCallbacks() + @Mock private lateinit var itemInflater: ItemInflater<*> + + private val inflationLooper = SparseArray() + + private lateinit var modelHelper: LauncherModelHelper + + @Before + fun setUp() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) + MockitoAnnotations.initMocks(this) + modelHelper = LauncherModelHelper() + + doAnswer { i -> + inflationLooper[(i.arguments[0] as ItemInfo).id] = Looper.myLooper() + View(modelHelper.sandboxContext) + } + .whenever(itemInflater) + .inflateItem(any(), any(), isNull()) + + // Set up the workspace with 3 pages of apps + modelHelper.setupDefaultLayoutProvider( + LauncherLayoutBuilder() + .atWorkspace(0, 1, 0) + .putApp(TEST_PACKAGE, TEST_PACKAGE) + .atWorkspace(1, 1, 0) + .putApp(TEST_PACKAGE, TEST_PACKAGE) + .atWorkspace(0, 1, 1) + .putApp(TEST_PACKAGE, TEST_PACKAGE) + .atWorkspace(1, 1, 1) + .putApp(TEST_PACKAGE, TEST_PACKAGE) + .atWorkspace(0, 1, 2) + .putApp(TEST_PACKAGE, TEST_PACKAGE) + ) + } + + @After + fun tearDown() { + modelHelper.destroy() + } + + @Test + fun test_bind_normally_without_itemInflater() { + MAIN_EXECUTOR.execute { modelHelper.model.addCallbacksAndLoad(callbacks) } + waitForLoaderAndTempMainThread() + + verify(callbacks, never()).bindInflatedItems(any()) + verify(callbacks, atLeastOnce()).bindItems(any(), any()) + } + + @Test + fun test_bind_inflates_item_on_background() { + callbacks.inflater = itemInflater + MAIN_EXECUTOR.execute { modelHelper.model.addCallbacksAndLoad(callbacks) } + waitForLoaderAndTempMainThread() + + verify(callbacks, never()).bindItems(any(), any()) + verify(callbacks, times(1)).bindInflatedItems(argThat { t -> t.size == 2 }) + + // Verify remaining items are bound using pendingTasks + reset(callbacks) + MAIN_EXECUTOR.submit(callbacks.pendingTasks!!::executeAllAndDestroy).get() + verify(callbacks, times(1)).bindInflatedItems(argThat { t -> t.size == 3 }) + + // Verify that all items were inflated on the background thread + assertEquals(5, inflationLooper.size()) + for (i in 0..4) assertEquals(MODEL_EXECUTOR.looper, inflationLooper.valueAt(i)) + } + + @Test + fun test_bind_sync_partially_inflates_on_background() { + modelHelper.loadModelSync() + assertTrue(modelHelper.model.isModelLoaded) + callbacks.inflater = itemInflater + + val firstPageBindIds = IntSet() + + MAIN_EXECUTOR.submit { + modelHelper.model.addCallbacksAndLoad(callbacks) + verify(callbacks, never()).bindItems(any(), any()) + verify(callbacks, times(1)) + .bindInflatedItems( + argThat { t -> + t.forEach { firstPageBindIds.add(it.first.id) } + t.size == 2 + } + ) + + // Verify that onInitialBindComplete is called and the binding is not yet complete + assertFalse(callbacks.onCompleteSignal!!.isDestroyed) + } + .get() + + waitForLoaderAndTempMainThread() + assertTrue(callbacks.onCompleteSignal!!.isDestroyed) + + // Verify that firstPageBindIds are loaded on the main thread and remaining + // on the background thread. + assertEquals(5, inflationLooper.size()) + for (i in 0..4) { + if (firstPageBindIds.contains(inflationLooper.keyAt(i))) + assertEquals(MAIN_EXECUTOR.looper, inflationLooper.valueAt(i)) + else assertEquals(MODEL_EXECUTOR.looper, inflationLooper.valueAt(i)) + } + + MAIN_EXECUTOR.submit { + reset(callbacks) + callbacks.pendingTasks!!.executeAllAndDestroy() + // Verify remaining 3 times are bound using pending tasks + verify(callbacks, times(1)).bindInflatedItems(argThat { t -> t.size == 3 }) + } + .get() + } + + private fun waitForLoaderAndTempMainThread() { + MAIN_EXECUTOR.submit {}.get() + MODEL_EXECUTOR.submit {}.get() + MAIN_EXECUTOR.submit {}.get() + } + + class MyCallbacks : Callbacks { + + var inflater: ItemInflater<*>? = null + var pendingTasks: RunnableList? = null + var onCompleteSignal: RunnableList? = null + + override fun bindItems(shortcuts: MutableList, forceAnimateIcons: Boolean) {} + + override fun bindInflatedItems(items: MutableList>) {} + + override fun getPagesToBindSynchronously(orderedScreenIds: IntArray?) = IntSet.wrap(0) + + override fun onInitialBindComplete( + boundPages: IntSet, + pendingTasks: RunnableList, + onCompleteSignal: RunnableList, + workspaceItemCount: Int, + isBindSync: Boolean + ) { + this.pendingTasks = pendingTasks + this.onCompleteSignal = onCompleteSignal + } + + override fun getItemInflater() = inflater + } +} diff --git a/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java index 25a4c4e8b8..b140f2efc7 100644 --- a/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java +++ b/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java @@ -187,7 +187,7 @@ public class ModelMultiCallbacksTest { @Override public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks, - int workspaceItemCount, boolean isBindSync) { + RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) { mPageBoundSync = boundPages; mPendingTasks = pendingTasks; } diff --git a/tests/src/com/android/launcher3/util/ItemInflaterTest.kt b/tests/src/com/android/launcher3/util/ItemInflaterTest.kt new file mode 100644 index 0000000000..efad899e35 --- /dev/null +++ b/tests/src/com/android/launcher3/util/ItemInflaterTest.kt @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.util + +import android.content.ComponentName +import android.content.Context +import android.content.pm.LauncherApps +import android.os.Bundle +import android.os.Process +import android.platform.test.flag.junit.SetFlagsRule +import android.view.View.OnClickListener +import android.view.View.OnFocusChangeListener +import android.widget.FrameLayout +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.BubbleTextView +import com.android.launcher3.Flags +import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR +import com.android.launcher3.apppairs.AppPairIcon +import com.android.launcher3.folder.FolderIcon +import com.android.launcher3.model.ModelWriter +import com.android.launcher3.model.data.AppInfo +import com.android.launcher3.model.data.FolderInfo +import com.android.launcher3.model.data.LauncherAppWidgetInfo +import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_ID_NOT_VALID +import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY +import com.android.launcher3.model.data.LauncherAppWidgetInfo.RESTORE_COMPLETED +import com.android.launcher3.ui.TestViewHelpers +import com.android.launcher3.util.Executors.MAIN_EXECUTOR +import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR +import com.android.launcher3.util.rule.ShellCommandRule +import com.android.launcher3.widget.LauncherAppWidgetHostView +import com.android.launcher3.widget.LauncherWidgetHolder +import com.android.launcher3.widget.PendingAppWidgetHostView +import com.android.launcher3.widget.WidgetManagerHelper +import java.util.concurrent.Callable +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.same +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions + +/** Tests for ItemInflater */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class ItemInflaterTest { + + @get:Rule val setFlagsRule = SetFlagsRule() + @get:Rule val grantWidgetRule = ShellCommandRule.grantWidgetBind() + + private val clickListener = OnClickListener {} + private val focusListener = OnFocusChangeListener { _, _ -> } + + @Mock private lateinit var modelWriter: ModelWriter + + private lateinit var testContext: Context + private lateinit var uiContext: ActivityContextWrapper + + private lateinit var widgetHolder: LauncherWidgetHolder + private lateinit var underTest: ItemInflater<*> + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testContext = InstrumentationRegistry.getInstrumentation().context + + uiContext = ActivityContextWrapper(getApplicationContext()) + uiContext.setTheme(Themes.getActivityThemeRes(uiContext, 0)) + + widgetHolder = LauncherWidgetHolder.newInstance(uiContext) + widgetHolder.startListening() + underTest = + ItemInflater( + uiContext, + widgetHolder, + clickListener, + focusListener, + FrameLayout(uiContext) + ) + } + + @After + fun tearDown() { + widgetHolder.destroy() + } + + @Test + fun test_workspace_item_inflated_on_UI() { + val itemInfo = workspaceItemInfo() + val view = + MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get() + + assertTrue(view is BubbleTextView) + assertEquals(itemInfo, view!!.tag) + } + + @Test + fun test_workspace_item_inflated_on_BG() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) + + val itemInfo = workspaceItemInfo() + val view = + VIEW_PREINFLATION_EXECUTOR.submit( + Callable { underTest.inflateItem(itemInfo, modelWriter) } + ) + .get() + + assertTrue(view is BubbleTextView) + assertEquals(itemInfo, view!!.tag) + } + + @Test + fun test_folder_inflated_on_UI() { + val itemInfo = FolderInfo() + itemInfo.contents.add(workspaceItemInfo()) + itemInfo.contents.add(workspaceItemInfo()) + itemInfo.contents.add(workspaceItemInfo()) + + val view = + MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get() + + assertTrue(view is FolderIcon) + assertEquals(itemInfo, view!!.tag) + } + + @Test + fun test_folder_inflated_on_BG() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) + + val itemInfo = FolderInfo() + itemInfo.contents.add(workspaceItemInfo()) + itemInfo.contents.add(workspaceItemInfo()) + itemInfo.contents.add(workspaceItemInfo()) + + val view = + VIEW_PREINFLATION_EXECUTOR.submit( + Callable { underTest.inflateItem(itemInfo, modelWriter) } + ) + .get() + + assertTrue(view is FolderIcon) + assertEquals(itemInfo, view!!.tag) + } + + @Test + fun test_app_pair_inflated_on_UI() { + val itemInfo = FolderInfo() + itemInfo.itemType = ITEM_TYPE_APP_PAIR + itemInfo.contents.add(workspaceItemInfo()) + itemInfo.contents.add(workspaceItemInfo()) + + val view = + MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get() + + assertTrue(view is AppPairIcon) + assertEquals(itemInfo, view!!.tag) + } + + @Test + fun test_app_pair_inflated_on_BG() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) + + val itemInfo = FolderInfo() + itemInfo.itemType = ITEM_TYPE_APP_PAIR + itemInfo.contents.add(workspaceItemInfo()) + itemInfo.contents.add(workspaceItemInfo()) + + val view = + VIEW_PREINFLATION_EXECUTOR.submit( + Callable { underTest.inflateItem(itemInfo, modelWriter) } + ) + .get() + + assertTrue(view is AppPairIcon) + assertEquals(itemInfo, view!!.tag) + } + + @Test + fun test_pending_widget_inflated_on_UI() { + val itemInfo = widgetItemInfo(true) + + val view = + MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get() + + assertTrue(view is PendingAppWidgetHostView) + assertEquals(itemInfo, view!!.tag) + } + + @Test + fun test_pending_widget_inflated_on_BG() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) + + val itemInfo = widgetItemInfo(true) + val view = + VIEW_PREINFLATION_EXECUTOR.submit( + Callable { underTest.inflateItem(itemInfo, modelWriter) } + ) + .get() + + assertTrue(view is PendingAppWidgetHostView) + assertEquals(itemInfo, view!!.tag) + } + + @Test + fun test_widget_restored_and_inflated_on_UI() { + val itemInfo = widgetItemInfo(false) + + val view = + MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get() + + // Verify that the widget is automatically restored and a final widget is returned + assertTrue(view is LauncherAppWidgetHostView) + assertFalse(view is PendingAppWidgetHostView) + assertEquals(itemInfo, view!!.tag) + assertEquals(RESTORE_COMPLETED, itemInfo.restoreStatus) + verify(modelWriter).updateItemInDatabase(same(itemInfo)) + } + + @Test + fun test_widget_restored_and_inflated_on_BG() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) + val itemInfo = widgetItemInfo(false) + + val view = + VIEW_PREINFLATION_EXECUTOR.submit( + Callable { underTest.inflateItem(itemInfo, modelWriter) } + ) + .get() + + // Verify that the widget is automatically restored and a final widget is returned + assertTrue(view is LauncherAppWidgetHostView) + assertFalse(view is PendingAppWidgetHostView) + assertEquals(itemInfo, view!!.tag) + assertEquals(RESTORE_COMPLETED, itemInfo.restoreStatus) + verify(modelWriter).updateItemInDatabase(same(itemInfo)) + } + + @Test + fun test_invalid_widget_deleted() { + val itemInfo = + widgetItemInfo(false).apply { + providerName = ComponentName(providerName.packageName, "invalid_provider_name") + } + val view = + MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get() + assertNull(view) + verify(modelWriter).deleteItemFromDatabase(same(itemInfo), any()) + } + + @Test + fun test_normal_widget_inflated_UI() { + val providerInfo = TestViewHelpers.findWidgetProvider(false) + val id = widgetHolder.allocateAppWidgetId() + assertTrue( + WidgetManagerHelper(uiContext).bindAppWidgetIdIfAllowed(id, providerInfo, Bundle()) + ) + val itemInfo = LauncherAppWidgetInfo(id, providerInfo.provider) + itemInfo.spanX = 2 + itemInfo.spanY = 2 + + val view = + MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get() + + // Verify that the widget is automatically restored and a final widget is returned + assertTrue(view is LauncherAppWidgetHostView) + assertFalse(view is PendingAppWidgetHostView) + assertEquals(itemInfo, view!!.tag) + verifyNoMoreInteractions(modelWriter) + } + + private fun workspaceItemInfo() = + AppInfo( + uiContext, + uiContext + .getSystemService(LauncherApps::class.java)!! + .getActivityList(testContext.packageName, Process.myUserHandle())[0], + Process.myUserHandle() + ) + .makeWorkspaceItem(uiContext) + + private fun widgetItemInfo(hasConfig: Boolean) = + LauncherAppWidgetInfo(0, TestViewHelpers.findWidgetProvider(hasConfig).component).apply { + spanX = 2 + spanY = 2 + restoreStatus = FLAG_ID_NOT_VALID or FLAG_UI_NOT_READY + } +} From 0467404c25d70cfeb118dfb03fcdb3299e118748 Mon Sep 17 00:00:00 2001 From: Will Leshner Date: Fri, 2 Feb 2024 17:08:03 -0800 Subject: [PATCH 14/24] Tweak WidgetPickerActivity size filtering. Better conform to documentation about how to use widget sizes when filtering for sizes in WidgetPickerActivity. Bug: 322191186 Test: atest Launcher3Tests Flag: NA Change-Id: I4239e64dd620e00a3c55046bfb66fe3099336aac --- .../launcher3/WidgetPickerActivity.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java index 68e7824793..8c4db4a569 100644 --- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java +++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java @@ -219,7 +219,9 @@ public class WidgetPickerActivity extends BaseActivity { final boolean isHorizontallyResizable = (info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0; if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) { - if (info.maxResizeWidth > 0 && info.maxResizeWidth < mDesiredWidgetWidth) { + if (info.maxResizeWidth > 0 + && info.maxResizeWidth >= info.minWidth + && info.maxResizeWidth < mDesiredWidgetWidth) { return rejectWidget( widget, "maxResizeWidth[%d] < mDesiredWidgetWidth[%d]", @@ -227,12 +229,13 @@ public class WidgetPickerActivity extends BaseActivity { mDesiredWidgetWidth); } - final int minWidth = info.minResizeWidth > 0 ? info.minResizeWidth : info.minWidth; + final int minWidth = Math.min(info.minResizeWidth, info.minWidth); if (minWidth > mDesiredWidgetWidth) { return rejectWidget( widget, - "minWidth[%d] > mDesiredWidgetWidth[%d]", - minWidth, + "min(minWidth[%d], minResizeWidth[%d]) > mDesiredWidgetWidth[%d]", + info.minWidth, + info.minResizeWidth, mDesiredWidgetWidth); } } @@ -240,7 +243,9 @@ public class WidgetPickerActivity extends BaseActivity { final boolean isVerticallyResizable = (info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0; if (mDesiredWidgetHeight > 0 && isVerticallyResizable) { - if (info.maxResizeHeight > 0 && info.maxResizeHeight < mDesiredWidgetHeight) { + if (info.maxResizeHeight > 0 + && info.maxResizeHeight >= info.minHeight + && info.maxResizeHeight < mDesiredWidgetHeight) { return rejectWidget( widget, "maxResizeHeight[%d] < mDesiredWidgetHeight[%d]", @@ -248,20 +253,19 @@ public class WidgetPickerActivity extends BaseActivity { mDesiredWidgetHeight); } - final int minHeight = info.minResizeHeight > 0 ? info.minResizeHeight : info.minHeight; + final int minHeight = Math.min(info.minResizeHeight, info.minHeight); if (minHeight > mDesiredWidgetHeight) { return rejectWidget( widget, - "minHeight[%d] > mDesiredWidgetHeight[%d]", - minHeight, + "min(minHeight[%d], minResizeHeight[%d]) > mDesiredWidgetHeight[%d]", + info.minHeight, + info.minResizeHeight, mDesiredWidgetHeight); } } - if (!isHorizontallyResizable - && !isVerticallyResizable - && (info.minWidth < mDesiredWidgetWidth || info.minHeight < mDesiredWidgetHeight)) { - return rejectWidget(widget, "too small and not resizeable"); + if (!isHorizontallyResizable || !isVerticallyResizable) { + return rejectWidget(widget, "not resizeable"); } return acceptWidget(widget); @@ -271,12 +275,15 @@ public class WidgetPickerActivity extends BaseActivity { WidgetItem widget, String rejectionReason, Object... args) { return new WidgetAcceptabilityVerdict( false, - widget.label, + widget.widgetInfo != null + ? widget.widgetInfo.provider.flattenToShortString() + : widget.label, String.format(Locale.ENGLISH, rejectionReason, args)); } private static WidgetAcceptabilityVerdict acceptWidget(WidgetItem widget) { - return new WidgetAcceptabilityVerdict(true, widget.label, ""); + return new WidgetAcceptabilityVerdict( + true, widget.widgetInfo.provider.flattenToShortString(), ""); } private record WidgetAcceptabilityVerdict( From d0f392db6e166b62f0b3a70eb20586066f23dddc Mon Sep 17 00:00:00 2001 From: fbaron Date: Thu, 15 Feb 2024 15:09:21 -0800 Subject: [PATCH 15/24] Fix testDragToFolder() The issue here was that we were dragging to the folder icon when it was already a folder. We need to drag to the folder icon, wait for the folder to open, and then drag into the middle of the opened folder. Fix: 320742556 Flag: NONE Test: TaplDragTest#testDragToFolder Change-Id: I82816f8e249cbe55fb4eef79c27e19b6fe00318e --- .../com/android/launcher3/tapl/Folder.java | 5 ++ .../android/launcher3/tapl/FolderIcon.java | 4 +- .../android/launcher3/tapl/HomeAppIcon.java | 30 ++++++++++-- ...derDragTarget.java => IconDragTarget.java} | 4 +- .../com/android/launcher3/tapl/Workspace.java | 48 +++++++++++++++---- .../launcher3/dragging/TaplDragTest.java | 3 +- 6 files changed, 74 insertions(+), 20 deletions(-) rename tests/multivalentTests/tapl/com/android/launcher3/tapl/{FolderDragTarget.java => IconDragTarget.java} (91%) diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/Folder.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/Folder.java index 1352cc07c1..b8adfe6656 100644 --- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/Folder.java +++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/Folder.java @@ -16,6 +16,8 @@ package com.android.launcher3.tapl; +import android.graphics.Rect; + import androidx.annotation.NonNull; import androidx.test.uiautomator.UiObject2; @@ -58,4 +60,7 @@ public class Folder { return mLauncher.getWorkspace(); } } + Rect getDropLocationBounds() { + return mLauncher.getVisibleBounds(mContainer); + } } diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/FolderIcon.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/FolderIcon.java index 0c453bd5b5..080e52c337 100644 --- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/FolderIcon.java +++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/FolderIcon.java @@ -26,7 +26,7 @@ import com.android.launcher3.testing.shared.TestProtocol; /** * Folder Icon, an app folder in workspace. */ -public class FolderIcon implements FolderDragTarget { +public class FolderIcon implements IconDragTarget { protected final UiObject2 mObject; protected final LauncherInstrumentation mLauncher; @@ -60,7 +60,7 @@ public class FolderIcon implements FolderDragTarget { /** This method requires public access, however should not be called in tests. */ @Override - public FolderIcon getTargetFolder(Rect bounds) { + public FolderIcon getTargetIcon(Rect bounds) { return this; } } diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/HomeAppIcon.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/HomeAppIcon.java index 693baa02c5..ca85b290c1 100644 --- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/HomeAppIcon.java +++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/HomeAppIcon.java @@ -27,7 +27,7 @@ import java.util.function.Supplier; /** * App icon on the workspace or all apps. */ -public abstract class HomeAppIcon extends AppIcon implements FolderDragTarget, WorkspaceDragSource { +public abstract class HomeAppIcon extends AppIcon implements IconDragTarget, WorkspaceDragSource { private final String mAppName; @@ -42,7 +42,7 @@ public abstract class HomeAppIcon extends AppIcon implements FolderDragTarget, W * @param target the destination icon. */ @NonNull - public FolderIcon dragToIcon(FolderDragTarget target) { + public FolderIcon dragToIcon(IconDragTarget target) { try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to drag icon")) { final Rect dropBounds = target.getDropLocationBounds(); @@ -51,13 +51,33 @@ public abstract class HomeAppIcon extends AppIcon implements FolderDragTarget, W () -> { final Rect bounds = target.getDropLocationBounds(); return new Point(bounds.centerX(), bounds.centerY()); - }); - FolderIcon result = target.getTargetFolder(dropBounds); + }, false); + FolderIcon result = target.getTargetIcon(dropBounds); mLauncher.assertTrue("Can't find the target folder.", result != null); return result; } } + /** + * Drag the AppIcon to the given position of a folder icon, and then inside that folder. + * + * @param target the destination folder. + */ + @NonNull + public Folder dragToFolder(IconDragTarget target) { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to drag icon")) { + Workspace.dragIconToWorkspace( + mLauncher, this, + () -> { + final Rect bounds = target.getDropLocationBounds(); + return new Point(bounds.centerX(), bounds.centerY()); + }, /* isDraggingToFolder */ true); + } + return new Folder(mLauncher); + } + + /** This method requires public access, however should not be called in tests. */ @Override public Rect getDropLocationBounds() { @@ -66,7 +86,7 @@ public abstract class HomeAppIcon extends AppIcon implements FolderDragTarget, W /** This method requires public access, however should not be called in tests. */ @Override - public FolderIcon getTargetFolder(Rect bounds) { + public FolderIcon getTargetIcon(Rect bounds) { for (FolderIcon folderIcon : mLauncher.getWorkspace().getFolderIcons()) { final Rect folderIconBounds = folderIcon.getDropLocationBounds(); if (bounds.contains(folderIconBounds.centerX(), folderIconBounds.centerY())) { diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/FolderDragTarget.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/IconDragTarget.java similarity index 91% rename from tests/multivalentTests/tapl/com/android/launcher3/tapl/FolderDragTarget.java rename to tests/multivalentTests/tapl/com/android/launcher3/tapl/IconDragTarget.java index 2c60668ba8..2f86703d9b 100644 --- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/FolderDragTarget.java +++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/IconDragTarget.java @@ -18,11 +18,11 @@ package com.android.launcher3.tapl; import android.graphics.Rect; -public interface FolderDragTarget { +public interface IconDragTarget { /** This method requires public access, however should not be called in tests. */ Rect getDropLocationBounds(); /** This method requires public access, however should not be called in tests. */ - FolderIcon getTargetFolder(Rect bounds); + FolderIcon getTargetIcon(Rect bounds); } diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java index a911de4049..8528c83da5 100644 --- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java @@ -540,7 +540,8 @@ public final class Workspace extends Home { * This function expects the launchable is inside the workspace and there is no drop event. */ static void dragIconToWorkspace( - LauncherInstrumentation launcher, Launchable launchable, Supplier destSupplier) { + LauncherInstrumentation launcher, Launchable launchable, Supplier destSupplier, + boolean isDraggingToFolder) { dragIconToWorkspace( launcher, launchable, @@ -548,7 +549,8 @@ public final class Workspace extends Home { /* isDecelerating= */ false, () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT), /* expectDropEvents= */ null, - /* startsActivity = */ false); + /* startsActivity = */ false, + isDraggingToFolder); } static void dragIconToWorkspace( @@ -559,7 +561,8 @@ public final class Workspace extends Home { @Nullable Runnable expectDropEvents, boolean startsActivity) { dragIconToWorkspace(launcher, launchable, dest, /* isDecelerating */ true, - expectLongClickEvents, expectDropEvents, startsActivity); + expectLongClickEvents, expectDropEvents, startsActivity, + /* isDraggingToFolder */ false); } static void dragIconToWorkspace( @@ -569,7 +572,8 @@ public final class Workspace extends Home { boolean isDecelerating, Runnable expectLongClickEvents, @Nullable Runnable expectDropEvents, - boolean startsActivity) { + boolean startsActivity, + boolean isDraggingToFolder) { try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer( "want to drag icon to workspace")) { final long downTime = SystemClock.uptimeMillis(); @@ -596,11 +600,27 @@ public final class Workspace extends Home { dragStart = screenEdge; } - // targetDest.x is now between 0 and displayX so we found the target page, - // we just have to put move the icon to the destination and drop it - launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating, - downTime, SystemClock.uptimeMillis(), false, - LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); + // targetDest.x is now between 0 and displayX so we found the target page. + // If not a folder, we just have to put move the icon to the destination and drop it. + // If it's a folder we want to drag to the folder icon and then drag to the center of + // that folder when it opens. + if (isDraggingToFolder) { + Point finalDragStart = dragStart; + Point finalTargetDest = targetDest; + Folder folder = executeAndWaitForFolderOpen(launcher, () -> launcher.movePointer( + finalDragStart, finalTargetDest, DEFAULT_DRAG_STEPS, isDecelerating, + downTime, SystemClock.uptimeMillis(), false, + LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER)); + + Rect dropBounds = folder.getDropLocationBounds(); + dragStart = targetDest; + targetDest = new Point(dropBounds.centerX(), dropBounds.centerY()); + } + + launcher.movePointer(dragStart, targetDest, + DEFAULT_DRAG_STEPS, isDecelerating, downTime, SystemClock.uptimeMillis(), + false, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); + dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents, startsActivity); } } @@ -685,6 +705,16 @@ public final class Workspace extends Home { () -> "Page scroll didn't happen", "Scrolling page"); } + private static Folder executeAndWaitForFolderOpen(LauncherInstrumentation launcher, + Runnable command) { + launcher.executeAndWaitForEvent(command, + event -> TestProtocol.FOLDER_OPENED_MESSAGE.equals( + event.getClassName().toString()), + () -> "Fail to open folder.", + "open folder"); + return new Folder(launcher); + } + static void dragIconToHotseat( LauncherInstrumentation launcher, Launchable launchable, diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java index 9942602122..58d5420fd1 100644 --- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java +++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java @@ -94,8 +94,7 @@ public class TaplDragTest extends AbstractLauncherUiTest { PHOTOS_APP_NAME); final HomeAppIcon mapIcon = createShortcutInCenterIfNotExist(MAPS_APP_NAME); - folderIcon = mapIcon.dragToIcon(folderIcon); - folder = folderIcon.open(); + folder = mapIcon.dragToFolder(folderIcon); folder.getAppIcon(MAPS_APP_NAME); workspace = folder.close(); From 1a98a9d8748de9f0fe68b4379854d8d4741b0707 Mon Sep 17 00:00:00 2001 From: Jeremy Sim Date: Wed, 21 Feb 2024 14:24:18 -0800 Subject: [PATCH 16/24] Fix app pair launches with certain apps This CL changes the way app pairs are saved. Previously, we saved the app pair using WorkspaceItemInfos directly from Recents, which caused certain apps to fail on launch because they weren't being launched with the correct Intent. Now, we look up a proper launchable WorkspaceItemInfo from the AllAppsStore and save that to the app pair, which should fix the launch issues. Bug: 323112914 Bug: 323110399 Test: Can launch Slides, Play Store, and Calendar when part of an app pair. Flag: ACONFIG com.android.wm.shell.enable_app_pairs TEAMFOOD Change-Id: I475afa1a237c50ffb0ffaf85af43912a803b8011 --- .../quickstep/util/AppPairsController.java | 84 +++++++++++++++++-- .../launcher3/allapps/AllAppsStore.java | 13 ++- .../android/launcher3/model/data/AppInfo.java | 3 + 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java index b8463230fc..ce8df9bdff 100644 --- a/quickstep/src/com/android/quickstep/util/AppPairsController.java +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -20,6 +20,7 @@ package com.android.quickstep.util; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH; +import static com.android.launcher3.model.data.AppInfo.PACKAGE_KEY_COMPARATOR; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; @@ -30,6 +31,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.isPersisten import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.LauncherApps; import android.util.Log; import android.util.Pair; @@ -42,10 +44,12 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -93,14 +97,38 @@ public class AppPairsController { } /** - * Creates a new app pair ItemInfo and adds it to the workspace + * Creates a new app pair ItemInfo and adds it to the workspace. + *
+ * We create WorkspaceItemInfos to save onto the app pair in the following way: + *
1. We verify that the ComponentKey from our Recents tile corresponds to a real + * launchable app in the app store. + *
2. If it doesn't, we search for the underlying launchable app via package name, and use + * that instead. + *
3. If that fails, we re-use the existing WorkspaceItemInfo by cloning it and replacing + * its intent with one from PackageManager. + *
4. If everything fails, we just use the WorkspaceItemInfo as is, with its existing + * intent. This is not preferred, but will still work in most cases (notably it will not work + * well on trampoline apps). */ public void saveAppPair(GroupedTaskView gtv) { TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers(); - WorkspaceItemInfo app1 = attributes[0].getItemInfo().clone(); - WorkspaceItemInfo app2 = attributes[1].getItemInfo().clone(); - app1.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; - app2.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo(); + WorkspaceItemInfo recentsInfo2 = attributes[0].getItemInfo(); + WorkspaceItemInfo app1 = lookupLaunchableItem(recentsInfo1.getComponentKey()); + WorkspaceItemInfo app2 = lookupLaunchableItem(recentsInfo2.getComponentKey()); + + // If app lookup fails, use the WorkspaceItemInfo that we have, but try to override default + // intent with one from PackageManager. + if (app1 == null) { + Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo1.title + + " failed. Falling back to the WorkspaceItemInfo from Recents."); + app1 = convertRecentsItemToAppItem(recentsInfo1); + } + if (app2 == null) { + Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo2.title + + " failed. Falling back to the WorkspaceItemInfo from Recents."); + app2 = convertRecentsItemToAppItem(recentsInfo2); + } @PersistentSnapPosition int snapPosition = gtv.getSnapPosition(); if (!isPersistentSnapPosition(snapPosition)) { @@ -188,6 +216,52 @@ public class AppPairsController { ); } + /** + * Creates a new launchable WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION by looking the + * ComponentKey up in the AllAppsStore. If no app is found, attempts a lookup by package + * instead. If that lookup fails, returns null. + */ + @Nullable + private WorkspaceItemInfo lookupLaunchableItem(@Nullable ComponentKey key) { + if (key == null) { + return null; + } + + AllAppsStore appsStore = Launcher.getLauncher(mContext).getAppsView().getAppsStore(); + + // Lookup by ComponentKey + AppInfo appInfo = appsStore.getApp(key); + if (appInfo == null) { + // Lookup by package + appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR); + } + + return appInfo != null ? appInfo.makeWorkspaceItem(mContext) : null; + } + + /** + * Converts a WorkspaceItemInfo of itemType=ITEM_TYPE_TASK (from a Recents task) to a new + * WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION. + */ + private WorkspaceItemInfo convertRecentsItemToAppItem(WorkspaceItemInfo recentsItem) { + if (recentsItem.itemType != LauncherSettings.Favorites.ITEM_TYPE_TASK) { + Log.w(TAG, "Expected ItemInfo of type ITEM_TYPE_TASK, but received " + + recentsItem.itemType); + } + + WorkspaceItemInfo launchableItem = recentsItem.clone(); + PackageManager p = mContext.getPackageManager(); + Intent launchIntent = p.getLaunchIntentForPackage(recentsItem.getTargetPackage()); + Log.w(TAG, "Initial intent from Recents: " + launchableItem.intent + "\n" + + "Intent from PackageManager: " + launchIntent); + if (launchIntent != null) { + // If lookup from PackageManager fails, just use the existing intent + launchableItem.intent = launchIntent; + } + launchableItem.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + return launchableItem; + } + /** * Handles the complicated logic for how to animate an app pair entrance when already inside an * app or app pair. diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index 009a2aa6b7..9623709348 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -40,6 +40,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; @@ -138,12 +139,22 @@ public class AllAppsStore { /** * Returns {@link AppInfo} if any apps matches with provided {@link ComponentKey}, otherwise * null. + * + * Uses {@link AppInfo#COMPONENT_KEY_COMPARATOR} as a default comparator. */ @Nullable public AppInfo getApp(ComponentKey key) { + return getApp(key, COMPONENT_KEY_COMPARATOR); + } + + /** + * Generic version of {@link #getApp(ComponentKey)} that allows comparator to be specified. + */ + @Nullable + public AppInfo getApp(ComponentKey key, Comparator comparator) { mTempInfo.componentName = key.componentName; mTempInfo.user = key.user; - int index = Arrays.binarySearch(mApps, mTempInfo, COMPONENT_KEY_COMPARATOR); + int index = Arrays.binarySearch(mApps, mTempInfo, comparator); return index < 0 ? null : mApps[index]; } diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java index b213fe306e..210d720bca 100644 --- a/src/com/android/launcher3/model/data/AppInfo.java +++ b/src/com/android/launcher3/model/data/AppInfo.java @@ -52,6 +52,9 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory { return uc != 0 ? uc : a.componentName.compareTo(b.componentName); }; + public static final Comparator PACKAGE_KEY_COMPARATOR = Comparator.comparingInt( + (AppInfo a) -> a.user.hashCode()).thenComparing(ItemInfo::getTargetPackage); + /** * The intent used to start the application. */ From fdf6df32263c492756f1398ae6b712070c983e31 Mon Sep 17 00:00:00 2001 From: bvineeth Date: Wed, 21 Feb 2024 09:14:56 +0000 Subject: [PATCH 17/24] Add tags for CUJ_TASKBAR_EXPAND/COLLAPSE Test: Checked traces Flag: None Bug: b/263362887 Change-Id: I28652b0629b8033fe931809bacceeede3c36ea93 --- .../taskbar/TaskbarStashController.java | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index 32dc50fa1a..c4be85fcb8 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -549,7 +549,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba createAnimToIsStashed( /* isStashed= */ false, placeholderDuration, - TRANSITION_UNSTASH_SUW_MANUAL); + TRANSITION_UNSTASH_SUW_MANUAL, + /* jankTag= */ "SUW_MANUAL"); animation.addListener(AnimatorListeners.forEndCallback( () -> mControllers.taskbarViewController.setDeferUpdatesForSUW(false))); animation.play(mAnimator); @@ -560,9 +561,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba * @param isStashed whether it's a stash animation or an unstash animation * @param duration duration of the animation * @param animationType what transition type to play. + * @param jankTag tag to be used in jank monitor trace. */ private void createAnimToIsStashed(boolean isStashed, long duration, - @StashAnimation int animationType) { + @StashAnimation int animationType, String jankTag) { if (animationType == TRANSITION_UNSTASH_SUW_MANUAL && isStashed) { // The STASH_ANIMATION_SUW_MANUAL must only be used during an unstash animation. Log.e(TAG, "Illegal arguments:Using TRANSITION_UNSTASH_SUW_MANUAL to stash taskbar"); @@ -573,7 +575,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba } mAnimator = new AnimatorSet(); addJankMonitorListener( - mAnimator, /* expanding= */ !isStashed, /* animationType= */ animationType); + mAnimator, /* expanding= */ !isStashed, /* tag= */ jankTag); boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity); final float stashTranslation = mActivity.isPhoneMode() || isTransientTaskbar ? 0 @@ -800,7 +802,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba } private void addJankMonitorListener( - AnimatorSet animator, boolean expanding, @StashAnimation int animationType) { + AnimatorSet animator, boolean expanding, String tag) { View v = mControllers.taskbarActivityContext.getDragLayer(); if (!v.isAttachedToWindow()) { // If the task bar drag layer is not attached to window, we don't need to monitor jank @@ -814,8 +816,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba public void onAnimationStart(@NonNull Animator animation) { final Configuration.Builder builder = Configuration.Builder.withView(action, v); - if (animationType == TRANSITION_HOME_TO_APP) { - builder.setTag("HOME_TO_APP"); + if (tag != null) { + builder.setTag(tag); } InteractionJankMonitor.getInstance().begin(builder); } @@ -1215,12 +1217,34 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba mLastStartedTransitionType = animationType; // This sets mAnimator. - createAnimToIsStashed(mIsStashed, duration, animationType); + createAnimToIsStashed(mIsStashed, duration, animationType, + computeTaskbarJankMonitorTag(changedFlags)); return mAnimator; } return null; } + /** Calculates the tag for CUJ_TASKBAR_EXPAND and CUJ_TASKBAR_COLLAPSE jank traces.*/ + private String computeTaskbarJankMonitorTag(int changedFlags) { + if (hasAnyFlag(changedFlags, FLAG_IN_APP)) { + // moving in or out of the app + if (hasAnyFlag(FLAG_IN_APP)) { + return "Home to App"; + } else { + return "App to Home"; + } + } + if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_AUTO)) { + // stash and unstash with-in the app + if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)) { + return "Stashed in app"; + } else { + return "Manually unstashed"; + } + } + return ""; + } + private @StashAnimation int computeTransitionType(int changedFlags) { boolean hotseatHiddenDuringAppLaunch = From 0ccdb919907952dc9cd0d17f039340a6e5879743 Mon Sep 17 00:00:00 2001 From: Johannes Gallmann Date: Mon, 19 Feb 2024 12:03:51 +0100 Subject: [PATCH 18/24] Fix flickers when invoking overview transition during predictive back-to-home Bug: 321923510 Bug: 321968468 Flag: ACONFIG com.android.systemui.predictive_back_system_animations TEAMFOOD Test: Manual, i.e. verifying that recents transition is not invokable during the predictive back to home animations and instead the flickerless overview transition is is invoked (same as for regular back-to-home) Change-Id: I90029de8d0c98853d38ed8984cbe7cda3b566b5a --- .../launcher3/uioverrides/QuickstepLauncher.java | 12 ++++++++++++ .../LauncherBackAnimationController.java | 2 ++ .../quickstep/RecentsAnimationDeviceState.java | 16 ++++++++++++++++ .../quickstep/TouchInteractionService.java | 14 ++++++++++++-- .../android/quickstep/util/TISBindHelper.java | 9 +++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index f3f36c5abb..a458659760 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -243,6 +243,8 @@ public class QuickstepLauncher extends Launcher { private boolean mEnableWidgetDepth; + private boolean mIsPredictiveBackToHomeInProgress; + private HomeTransitionController mHomeTransitionController; @Override @@ -512,6 +514,7 @@ public class QuickstepLauncher extends Launcher { mAppTransitionManager.onActivityDestroyed(); } mAppTransitionManager = null; + mIsPredictiveBackToHomeInProgress = false; if (mUnfoldTransitionProgressProvider != null) { SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null); @@ -970,6 +973,7 @@ public class QuickstepLauncher extends Launcher { if (taskbarManager != null) { taskbarManager.setActivity(this); } + mTISBindHelper.setPredictiveBackToHomeInProgress(mIsPredictiveBackToHomeInProgress); } @Override @@ -1274,6 +1278,14 @@ public class QuickstepLauncher extends Launcher { mPendingSplitSelectInfo = null; } + /** + * Sets flag whether a predictive back-to-home animation is in progress + */ + public void setPredictiveBackToHomeInProgress(boolean isInProgress) { + mIsPredictiveBackToHomeInProgress = isInProgress; + mTISBindHelper.setPredictiveBackToHomeInProgress(isInProgress); + } + @Override public boolean areFreeformTasksVisible() { if (mDesktopVisibilityController != null) { diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java index 5772450b62..a0f81db415 100644 --- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java +++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java @@ -437,6 +437,7 @@ public class LauncherBackAnimationController { if (mLauncher.isDestroyed()) { return; } + mLauncher.setPredictiveBackToHomeInProgress(true); LauncherTaskbarUIController taskbarUIController = mLauncher.getTaskbarUIController(); if (taskbarUIController != null) { taskbarUIController.onLauncherVisibilityChanged(true); @@ -475,6 +476,7 @@ public class LauncherBackAnimationController { } private void finishAnimation() { + mLauncher.setPredictiveBackToHomeInProgress(false); mBackTarget = null; mLauncherTarget = null; mBackInProgress = false; diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index cb0aa8f69f..c56a6219b2 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -119,6 +119,7 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E private boolean mIsSwipeToNotificationEnabled; private final boolean mIsOneHandedModeSupported; private boolean mPipIsActive; + private boolean mIsPredictiveBackToHomeInProgress; private int mGestureBlockingTaskId = -1; private @NonNull Region mExclusionRegion = GestureExclusionManager.EMPTY_REGION; @@ -361,6 +362,20 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E return mSystemUiStateFlags; } + /** + * Sets the flag that indicates whether a predictive back-to-home animation is in progress + */ + public void setPredictiveBackToHomeInProgress(boolean isInProgress) { + mIsPredictiveBackToHomeInProgress = isInProgress; + } + + /** + * @return whether a predictive back-to-home animation is currently in progress + */ + public boolean isPredictiveBackToHomeInProgress() { + return mIsPredictiveBackToHomeInProgress; + } + /** * @return whether SystemUI is in a state where we can start a system gesture. */ @@ -609,6 +624,7 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E pw.println(" deferredGestureRegion=" + mDeferredGestureRegion.getBounds()); pw.println(" exclusionRegion=" + mExclusionRegion.getBounds()); pw.println(" pipIsActive=" + mPipIsActive); + pw.println(" predictiveBackToHomeInProgress=" + mIsPredictiveBackToHomeInProgress); mRotationTouchHelper.dump(pw); } } diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index f9486bd6a8..415f73f1aa 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -397,6 +397,14 @@ public class TouchInteractionService extends Service { return tis.mTaskbarManager; } + /** + * Sets whether a predictive back-to-home animation is in progress in the device state + */ + public void setPredictiveBackToHomeInProgress(boolean isInProgress) { + executeForTouchInteractionService(tis -> + tis.mDeviceState.setPredictiveBackToHomeInProgress(isInProgress)); + } + /** * Returns the {@link OverviewCommandHelper}. *

@@ -1170,7 +1178,8 @@ public class TouchInteractionService extends Service { } boolean previousGestureAnimatedToLauncher = - previousGestureState.isRunningAnimationToLauncher(); + previousGestureState.isRunningAnimationToLauncher() + || mDeviceState.isPredictiveBackToHomeInProgress(); // with shell-transitions, home is resumed during recents animation, so // explicitly check against recents animation too. boolean launcherResumedThroughShellTransition = @@ -1275,7 +1284,8 @@ public class TouchInteractionService extends Service { boolean hasWindowFocus = activity.getRootView().hasWindowFocus(); boolean isPreviousGestureAnimatingToLauncher = - previousGestureState.isRunningAnimationToLauncher(); + previousGestureState.isRunningAnimationToLauncher() + || mDeviceState.isPredictiveBackToHomeInProgress(); boolean isInLiveTileMode = gestureState.getActivityInterface().isInLiveTileMode(); reasonString.append(SUBSTRING_PREFIX) .append(hasWindowFocus diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java index ddc796fc33..9a010429d3 100644 --- a/quickstep/src/com/android/quickstep/util/TISBindHelper.java +++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java @@ -108,6 +108,15 @@ public class TISBindHelper implements ServiceConnection { return mBinder == null ? null : mBinder.getTaskbarManager(); } + /** + * Sets flag whether a predictive back-to-home animation is in progress + */ + public void setPredictiveBackToHomeInProgress(boolean isInProgress) { + if (mBinder != null) { + mBinder.setPredictiveBackToHomeInProgress(isInProgress); + } + } + @Nullable public OverviewCommandHelper getOverviewCommandHelper() { return mBinder == null ? null : mBinder.getOverviewCommandHelper(); From c71b48c6582380e38c7257c695c2ac70a1f222ee Mon Sep 17 00:00:00 2001 From: Johannes Gallmann Date: Fri, 23 Feb 2024 12:30:46 +0100 Subject: [PATCH 19/24] Transfer coordinate into new Rect instead of in-place Other parts of the animation rely on currentRectF to remain stable, therefore we shouldn't modify the RectF object in place. Bug: 323628523 Flag: NONE Test: Manual, i.e. verifying that widget close (and app close) animation animates correctly regardless of device orientation Change-Id: I48e9ed047b956db654192eef39e5d94fbac53553 --- .../launcher3/QuickstepTransitionManager.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 851f2b3d73..2a6e37dc18 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -2049,7 +2049,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener private final RemoteAnimationTarget[] mAppTargets; private final Matrix mMatrix = new Matrix(); private final Point mTmpPos = new Point(); - private final Rect mCurrentRect = new Rect(); + private final RectF mCurrentRectF = new RectF(); private final float mStartRadius; private final float mEndRadius; private final SurfaceTransactionApplier mSurfaceApplier; @@ -2116,25 +2116,24 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } if (target.mode == MODE_CLOSING) { - transferRectToTargetCoordinate(target, currentRectF, false, currentRectF); - currentRectF.round(mCurrentRect); + transferRectToTargetCoordinate(target, currentRectF, false, mCurrentRectF); // Scale the target window to match the currentRectF. final float scale; // We need to infer the crop (we crop the window to match the currentRectF). if (mWindowStartBounds.height() > mWindowStartBounds.width()) { - scale = Math.min(1f, currentRectF.width() / mWindowOriginalBounds.width()); + scale = Math.min(1f, mCurrentRectF.width() / mWindowOriginalBounds.width()); - int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale)); + int unscaledHeight = (int) (mCurrentRectF.height() * (1f / scale)); int croppedHeight = mWindowStartBounds.height() - unscaledHeight; mTmpRect.set(0, 0, mWindowOriginalBounds.width(), mWindowStartBounds.height() - croppedHeight); } else { - scale = Math.min(1f, currentRectF.height() + scale = Math.min(1f, mCurrentRectF.height() / mWindowOriginalBounds.height()); - int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale)); + int unscaledWidth = (int) (mCurrentRectF.width() * (1f / scale)); int croppedWidth = mWindowStartBounds.width() - unscaledWidth; mTmpRect.set(0, 0, mWindowStartBounds.width() - croppedWidth, mWindowOriginalBounds.height()); @@ -2142,7 +2141,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener // Match size and position of currentRect. mMatrix.setScale(scale, scale); - mMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top); + mMatrix.postTranslate(mCurrentRectF.left, mCurrentRectF.top); builder.setMatrix(mMatrix) .setWindowCrop(mTmpRect) From 58bd15c6ab8dd8e146bc2d73a019cc344fc7e29d Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Fri, 23 Feb 2024 11:57:21 +0000 Subject: [PATCH 20/24] Fix setContentAlpha skipping all TaskViews when runningTaskId is -1 - Currently when runningTaskId is -1, it can skips setting contentAlpha for all TaskViews if they haven't been loaded, this cause RecentsView to become empty in the bug - Can be reproduced with excludeFromRecents=true apps Bug: 324495241 Test: swipe up from an app that has excludeFromRecents=true, e.g. Volume Dialog, Home quick settings tile, and Routines widget; ensure RecentsView is visible after gesture settles Flag: None Change-Id: I9f1b4ffed6d09805c83c6e2241903c73d4ead9ad --- .../src/com/android/quickstep/views/RecentsView.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 0a55770bfe..7bfa5eddd1 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -4261,10 +4261,12 @@ public abstract class RecentsView= 0; i--) { TaskView child = requireTaskViewAt(i); int[] childTaskIds = child.getTaskIds(); - if (!mRunningTaskTileHidden || - (childTaskIds[0] != runningTaskId && childTaskIds[1] != runningTaskId)) { - child.setStableAlpha(alpha); + if (runningTaskId != INVALID_TASK_ID + && mRunningTaskTileHidden + && (childTaskIds[0] == runningTaskId || childTaskIds[1] == runningTaskId)) { + continue; } + child.setStableAlpha(alpha); } mClearAllButton.setContentAlpha(mContentAlpha); int alphaInt = Math.round(alpha * 255); From 5aa23d41dd57718d4faa3bea2cdbd6776579d7d2 Mon Sep 17 00:00:00 2001 From: Pat Manning Date: Tue, 20 Feb 2024 13:54:58 +0000 Subject: [PATCH 21/24] Focus a task by default when using keyboard to go to overview. Fix: 322898816 Test: TaplTestsQuickstep.java Flag: None. Change-Id: I6d4b0ca6593ed2ac7c7d8793954c36e55b5978de --- .../quickstep/OverviewCommandHelper.java | 52 ++++++++++++------- .../android/quickstep/TaplTestsQuickstep.java | 27 ++++++++++ .../com/android/launcher3/tapl/Workspace.java | 38 ++++++++++++++ 3 files changed, 97 insertions(+), 20 deletions(-) diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index febfc3ad73..65b5397fd1 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -209,27 +209,38 @@ public class OverviewCommandHelper { && dp != null && (dp.isTablet || dp.isTwoPanels); - if (cmd.type == TYPE_HIDE) { - if (!allowQuickSwitch) { + switch (cmd.type) { + case TYPE_HIDE: + if (!allowQuickSwitch) { + return true; + } + mKeyboardTaskFocusIndex = uiController.launchFocusedTask(); + if (mKeyboardTaskFocusIndex == -1) { + return true; + } + break; + case TYPE_KEYBOARD_INPUT: + if (allowQuickSwitch) { + uiController.openQuickSwitchView(); + return true; + } else { + mKeyboardTaskFocusIndex = 0; + break; + } + case TYPE_HOME: + ActiveGestureLog.INSTANCE.addLog( + "OverviewCommandHelper.executeCommand(TYPE_HOME)"); + mService.startActivity(mOverviewComponentObserver.getHomeIntent()); return true; - } - mKeyboardTaskFocusIndex = uiController.launchFocusedTask(); - if (mKeyboardTaskFocusIndex == -1) { - return true; - } - } - if (cmd.type == TYPE_KEYBOARD_INPUT) { - if (allowQuickSwitch) { - uiController.openQuickSwitchView(); - return true; - } else { + case TYPE_SHOW: + // When Recents is not currently visible, the command's type is TYPE_SHOW + // when overview is triggered via the keyboard overview button or Action+Tab + // keys (Not Alt+Tab which is KQS). The overview button on-screen in 3-button + // nav is TYPE_TOGGLE. mKeyboardTaskFocusIndex = 0; - } - } - if (cmd.type == TYPE_HOME) { - ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(TYPE_HOME)"); - mService.startActivity(mOverviewComponentObserver.getHomeIntent()); - return true; + break; + default: + // continue below to handle displaying Recents. } } else { createdRecentsView = visibleRecentsView; @@ -351,7 +362,8 @@ public class OverviewCommandHelper { private void updateRecentsViewFocus(CommandInfo cmd) { RecentsView recentsView = mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView(); - if (recentsView == null || (cmd.type != TYPE_KEYBOARD_INPUT && cmd.type != TYPE_HIDE)) { + if (recentsView == null || (cmd.type != TYPE_KEYBOARD_INPUT && cmd.type != TYPE_HIDE + && cmd.type != TYPE_SHOW)) { return; } // When the overview is launched via alt tab (cmd type is TYPE_KEYBOARD_INPUT), diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index ad8535f84c..c0b31b70ff 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -44,6 +44,7 @@ import com.android.launcher3.tapl.Overview; import com.android.launcher3.tapl.OverviewActions; import com.android.launcher3.tapl.OverviewTask; import com.android.launcher3.tapl.SelectModeButtons; +import com.android.launcher3.tapl.Workspace; import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; @@ -227,6 +228,32 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { isInState(() -> LauncherState.NORMAL)); } + @Test + public void testOpenOverviewWithActionPlusTabKeys() throws Exception { + startTestAppsWithCheck(); + startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app. + Workspace home = mLauncher.goHome(); + assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL)); + + Overview overview = home.openOverviewFromActionPlusTabKeyboardShortcut(); + + assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW)); + overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused. + } + + @Test + public void testOpenOverviewWithRecentsKey() throws Exception { + startTestAppsWithCheck(); + startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app. + Workspace home = mLauncher.goHome(); + assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL)); + + Overview overview = home.openOverviewFromRecentsKeyboardShortcut(); + + assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW)); + overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused. + } + private int getCurrentOverviewPage(Launcher launcher) { return launcher.getOverviewPanel().getCurrentPage(); } diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java index 7abaed3860..506e563777 100644 --- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java @@ -17,10 +17,14 @@ package com.android.launcher3.tapl; import static android.view.KeyEvent.KEYCODE_META_RIGHT; +import static android.view.KeyEvent.KEYCODE_RECENT_APPS; +import static android.view.KeyEvent.KEYCODE_TAB; +import static android.view.KeyEvent.META_META_ON; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED; import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL; import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; +import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL; import static com.android.launcher3.testing.shared.TestProtocol.UIOBJECT_STALE_ELEMENT; import static junit.framework.TestCase.assertNotNull; @@ -134,6 +138,40 @@ public final class Workspace extends Home { } } + /** Opens the Launcher Overview page with the action+tab keyboard shortcut. */ + public Overview openOverviewFromActionPlusTabKeyboardShortcut() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = + mLauncher.addContextLayer("want to open overview")) { + verifyActiveContainer(); + mLauncher.runToState( + () -> mLauncher.getDevice().pressKeyCode(KEYCODE_TAB, META_META_ON), + OVERVIEW_STATE_ORDINAL, + "pressing keyboard shortcut"); + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "pressed meta+tab key")) { + return new Overview(mLauncher); + } + } + } + + /** Opens the Launcher Overview page with the Recents keyboard shortcut. */ + public Overview openOverviewFromRecentsKeyboardShortcut() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = + mLauncher.addContextLayer("want to open overview")) { + verifyActiveContainer(); + mLauncher.runToState( + () -> mLauncher.getDevice().pressKeyCode(KEYCODE_RECENT_APPS), + OVERVIEW_STATE_ORDINAL, + "pressing keyboard shortcut"); + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "pressed recents apps key")) { + return new Overview(mLauncher); + } + } + } + /** * Returns the home qsb. * From a80997d9eb21baac4a36eabf5f1b72cdfa0a27e0 Mon Sep 17 00:00:00 2001 From: Priyanka Advani Date: Fri, 23 Feb 2024 18:05:39 +0000 Subject: [PATCH 22/24] Revert "Remove Unused parts of LauncherPrefs causing cyclical dependency." This reverts commit 21ab3b979b78d15d71f979f6290844b0dd1795ee. Reason for revert: Culprit for b/326611479. Will be verifying through ABTD for confirmation and before submitting the revert. Change-Id: Ib71791d159b3ac65ca332f58500341a1a0fe36e1 --- .../android/quickstep/BootAwarePreloader.kt | 52 +++++++ .../quickstep/TouchInteractionService.java | 1 + src/com/android/launcher3/LauncherPrefs.kt | 147 +++++++++++++++--- .../launcher3/config/FeatureFlags.java | 7 + .../android/launcher3/LauncherPrefsTest.kt | 123 ++++++++++++++- 5 files changed, 306 insertions(+), 24 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/BootAwarePreloader.kt diff --git a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt new file mode 100644 index 0000000000..2fc4d044cf --- /dev/null +++ b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep + +import android.content.Context +import android.util.Log +import com.android.launcher3.LauncherAppState +import com.android.launcher3.LauncherPrefs +import com.android.launcher3.moveStartupDataToDeviceProtectedStorageIsEnabled +import com.android.launcher3.util.LockedUserState + +/** + * Loads expensive objects in memory before the user is unlocked. This decreases experienced latency + * when starting the launcher for the first time after a reboot. + */ +object BootAwarePreloader { + private const val TAG = "BootAwarePreloader" + + @JvmStatic + fun start(context: Context) { + val lp = LauncherPrefs.get(context) + when { + LockedUserState.get(context).isUserUnlocked || + !moveStartupDataToDeviceProtectedStorageIsEnabled -> { + /* No-Op */ + } + lp.isStartupDataMigrated -> { + Log.d(TAG, "preloading start up data") + LauncherAppState.INSTANCE.get(context) + } + else -> { + Log.d(TAG, "queuing start up data migration to boot aware prefs") + LockedUserState.get(context).runOnUserUnlocked { + lp.migrateStartupDataToDeviceProtectedStorage() + } + } + } + } +} diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 8619b4d095..f9486bd6a8 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -493,6 +493,7 @@ public class TouchInteractionService extends Service { mTaskbarManager = new TaskbarManager(this); mRotationTouchHelper = mDeviceState.getRotationTouchHelper(); mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer(); + BootAwarePreloader.start(this); // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized. LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked); diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt index d7e6387ab1..b0a644b269 100644 --- a/src/com/android/launcher3/LauncherPrefs.kt +++ b/src/com/android/launcher3/LauncherPrefs.kt @@ -19,6 +19,7 @@ import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.util.Log import android.view.ViewConfiguration import androidx.annotation.VisibleForTesting import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN @@ -37,6 +38,8 @@ import com.android.launcher3.util.Themes * Use same context for shared preferences, so that we use a single cached instance * * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods. + * TODO(b/274501660): Fix ReorderWidgets#simpleReorder test before enabling + * isBootAwareStartupDataEnabled */ class LauncherPrefs(private val encryptedContext: Context) { private val deviceProtectedStorageContext = @@ -49,8 +52,22 @@ class LauncherPrefs(private val encryptedContext: Context) { private val Item.encryptedPrefs get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE) + // This call to `SharedPreferences` needs to be explicit rather than using `get` since doing so + // would result in a circular dependency between `isStartupDataMigrated` and `choosePreferences` + val isStartupDataMigrated: Boolean + get() = + bootAwarePrefs.getBoolean( + IS_STARTUP_DATA_MIGRATED.sharedPrefKey, + IS_STARTUP_DATA_MIGRATED.defaultValue + ) + private fun chooseSharedPreferences(item: Item): SharedPreferences = - if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs + if ( + (moveStartupDataToDeviceProtectedStorageIsEnabled && + item.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && + isStartupDataMigrated) || item.encryptionType == EncryptionType.DEVICE_PROTECTED + ) + bootAwarePrefs else item.encryptedPrefs /** Wrapper around `getInner` for a `ContextualItem` */ @@ -130,7 +147,11 @@ class LauncherPrefs(private val encryptedContext: Context) { .toMutableMap() val bootAwareUpdates = - updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED } + updates.filter { + (it.first.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && + moveStartupDataToDeviceProtectedStorageIsEnabled) || + it.first.encryptionType == EncryptionType.DEVICE_PROTECTED + } if (bootAwareUpdates.isNotEmpty()) { updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates } @@ -231,7 +252,12 @@ class LauncherPrefs(private val encryptedContext: Context) { .groupBy { it.encryptedPrefs } .toMutableMap() - val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED } + val bootAwareUpdates = + items.filter { + (it.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && + moveStartupDataToDeviceProtectedStorageIsEnabled) || + it.encryptionType == EncryptionType.DEVICE_PROTECTED + } if (bootAwareUpdates.isNotEmpty()) { itemsPerFile[bootAwarePrefs] = bootAwareUpdates } @@ -243,7 +269,24 @@ class LauncherPrefs(private val encryptedContext: Context) { } } + fun migrateStartupDataToDeviceProtectedStorage() { + if (!moveStartupDataToDeviceProtectedStorageIsEnabled) return + + Log.d( + TAG, + "Migrating data to unencrypted shared preferences to enable preloading " + + "while the user is locked the next time the device reboots." + ) + + with(bootAwarePrefs.edit()) { + ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.forEach { putValue(it, get(it)) } + putBoolean(IS_STARTUP_DATA_MIGRATED.sharedPrefKey, true) + apply() + } + } + companion object { + private const val TAG = "LauncherPrefs" @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs" @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) } @@ -253,50 +296,80 @@ class LauncherPrefs(private val encryptedContext: Context) { const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY" const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY" @JvmField - val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED) + val ICON_STATE = + nonRestorableItem("pref_icon_shape_path", "", EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val ALL_APPS_OVERVIEW_THRESHOLD = - nonRestorableItem("pref_all_apps_overview_threshold", 180, EncryptionType.ENCRYPTED) + nonRestorableItem( + "pref_all_apps_overview_threshold", + 180, + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) @JvmField val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE = - nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.ENCRYPTED) + nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS = nonRestorableItem( "LPNH_TIMEOUT_MS", ViewConfiguration.getLongPressTimeout(), - EncryptionType.ENCRYPTED + EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT = - nonRestorableItem("LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0, EncryptionType.ENCRYPTED) + nonRestorableItem( + "LPNH_HAPTIC_HINT_START_SCALE_PERCENT", + 0, + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT = - nonRestorableItem("LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100, EncryptionType.ENCRYPTED) + nonRestorableItem( + "LPNH_HAPTIC_HINT_END_SCALE_PERCENT", + 100, + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT = - nonRestorableItem("LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1, EncryptionType.ENCRYPTED) + nonRestorableItem( + "LPNH_HAPTIC_HINT_SCALE_EXPONENT", + 1, + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS = - nonRestorableItem("LPNH_HAPTIC_HINT_ITERATIONS", 50, EncryptionType.ENCRYPTED) + nonRestorableItem( + "LPNH_HAPTIC_HINT_ITERATIONS", + 50, + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY = - nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.ENCRYPTED) + nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val PRIVATE_SPACE_APPS = - nonRestorableItem("pref_private_space_apps", 0, EncryptionType.ENCRYPTED) + nonRestorableItem("pref_private_space_apps", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED) + @JvmField val ENABLE_TWOLINE_ALLAPPS_TOGGLE = + backedUpItem("pref_enable_two_line_toggle", false) @JvmField - val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false) - @JvmField - val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED) + val THEMED_ICONS = + backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "") @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0) @JvmField val WORKSPACE_SIZE = - backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED) + backedUpItem( + DeviceGridState.KEY_WORKSPACE_SIZE, + "", + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) @JvmField val HOTSEAT_COUNT = - backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED) + backedUpItem( + DeviceGridState.KEY_HOTSEAT_COUNT, + -1, + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) @JvmField val TASKBAR_PINNING = backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED) @@ -306,10 +379,11 @@ class LauncherPrefs(private val encryptedContext: Context) { backedUpItem( DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, - EncryptionType.ENCRYPTED + EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField - val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED) + val DB_FILE = + backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val SHOULD_SHOW_SMARTSPACE = backedUpItem( @@ -322,11 +396,15 @@ class LauncherPrefs(private val encryptedContext: Context) { backedUpItem( RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, - EncryptionType.ENCRYPTED + EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val IS_FIRST_LOAD_AFTER_RESTORE = - nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED) + nonRestorableItem( + FIRST_LOAD_AFTER_RESTORE_KEY, + false, + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "") @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "") @JvmField @@ -335,7 +413,7 @@ class LauncherPrefs(private val encryptedContext: Context) { "idp_grid_name", isBackedUp = true, defaultValue = null, - encryptionType = EncryptionType.ENCRYPTED, + encryptionType = EncryptionType.MOVE_TO_DEVICE_PROTECTED, type = String::class.java ) @JvmField @@ -343,6 +421,14 @@ class LauncherPrefs(private val encryptedContext: Context) { backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) { RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info) } + @JvmField + val IS_STARTUP_DATA_MIGRATED = + ConstantItem( + "is_startup_data_boot_aware", + isBackedUp = false, + defaultValue = false, + encryptionType = EncryptionType.DEVICE_PROTECTED + ) // Preferences for widget configurations @JvmField @@ -407,6 +493,12 @@ class LauncherPrefs(private val encryptedContext: Context) { } } +// It is a var because the unit tests are setting this to true so they can run. +var moveStartupDataToDeviceProtectedStorageIsEnabled: Boolean = + com.android.launcher3.config.FeatureFlags.MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE.get() + +private val ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE: MutableSet> = mutableSetOf() + abstract class Item { abstract val sharedPrefKey: String abstract val isBackedUp: Boolean @@ -426,6 +518,14 @@ data class ConstantItem( // The default value can be null. If so, the type needs to be explicitly stated, or else NPE override val type: Class = defaultValue!!::class.java ) : Item() { + init { + if ( + encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && + moveStartupDataToDeviceProtectedStorageIsEnabled + ) { + ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.add(this) + } + } fun get(c: Context): T = LauncherPrefs.get(c).get(this) } @@ -452,4 +552,5 @@ data class ContextualItem( enum class EncryptionType { ENCRYPTED, DEVICE_PROTECTED, + MOVE_TO_DEVICE_PROTECTED } diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 8c0dc89b53..072a96ce6e 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -100,6 +100,13 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_DISMISS_PREDICTION_UNDO = getDebugFlag(270394476, "ENABLE_DISMISS_PREDICTION_UNDO", DISABLED, "Show an 'Undo' snackbar when users dismiss a predicted hotseat item"); + + public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag( + 251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED, + "Marks LauncherPref data as (and allows it to) available while the device is" + + " locked. Enabling this causes a 1-time movement of certain SharedPreferences" + + " data. Improves startup latency."); + public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(270395171, "CONTINUOUS_VIEW_TREE_CAPTURE", ENABLED, "Capture View tree every frame"); diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/src/com/android/launcher3/LauncherPrefsTest.kt index b81309506a..88a430bd32 100644 --- a/tests/src/com/android/launcher3/LauncherPrefsTest.kt +++ b/tests/src/com/android/launcher3/LauncherPrefsTest.kt @@ -25,6 +25,8 @@ import com.android.launcher3.LauncherPrefs.Companion.BOOT_AWARE_PREFS_KEY import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import org.junit.AfterClass +import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith @@ -46,6 +48,20 @@ class LauncherPrefsTest { private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext } private val launcherPrefs by lazy { LauncherPrefs.get(context) } + companion object { + @BeforeClass + @JvmStatic + fun setup() { + moveStartupDataToDeviceProtectedStorageIsEnabled = true + } + + @AfterClass + @JvmStatic + fun teardown() { + moveStartupDataToDeviceProtectedStorageIsEnabled = false + } + } + @Test fun has_keyMissingFromLauncherPrefs_returnsFalse() { assertThat(launcherPrefs.has(TEST_BOOLEAN_ITEM)).isFalse() @@ -206,13 +222,32 @@ class LauncherPrefsTest { launcherPrefs.removeSync(bootAwareItem) } + @Test + fun put_bootAwareItem_updatesEncryptedStorage() { + val bootAwareItem = + LauncherPrefs.backedUpItem( + TEST_PREF_KEY, + TEST_DEFAULT_VALUE, + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) + + val encryptedPrefs: SharedPreferences = + context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE) + encryptedPrefs.edit().remove(bootAwareItem.sharedPrefKey).commit() + + launcherPrefs.putSync(bootAwareItem.to(TEST_STRING_ITEM.defaultValue)) + assertThat(encryptedPrefs.contains(bootAwareItem.sharedPrefKey)).isTrue() + + launcherPrefs.removeSync(bootAwareItem) + } + @Test fun remove_bootAwareItem_removesFromDeviceProtectedStorage() { val bootAwareItem = LauncherPrefs.backedUpItem( TEST_PREF_KEY, TEST_DEFAULT_VALUE, - EncryptionType.DEVICE_PROTECTED + EncryptionType.MOVE_TO_DEVICE_PROTECTED ) val bootAwarePrefs: SharedPreferences = @@ -228,4 +263,90 @@ class LauncherPrefsTest { launcherPrefs.removeSync(bootAwareItem) assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isFalse() } + + @Test + fun remove_bootAwareItem_removesFromEncryptedStorage() { + val bootAwareItem = + LauncherPrefs.backedUpItem( + TEST_PREF_KEY, + TEST_DEFAULT_VALUE, + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) + + val encryptedPrefs: SharedPreferences = + context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE) + + encryptedPrefs + .edit() + .putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue) + .commit() + + launcherPrefs.removeSync(bootAwareItem) + assertThat(encryptedPrefs.contains(bootAwareItem.sharedPrefKey)).isFalse() + } + + @Test + fun migrate_bootAwareItemsToDeviceProtectedStorage_worksAsIntended() { + val bootAwareItem = + LauncherPrefs.backedUpItem( + TEST_PREF_KEY, + TEST_DEFAULT_VALUE, + EncryptionType.MOVE_TO_DEVICE_PROTECTED + ) + launcherPrefs.removeSync(bootAwareItem) + + val bootAwarePrefs: SharedPreferences = + context + .createDeviceProtectedStorageContext() + .getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE) + + if (bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)) { + bootAwarePrefs.edit().remove(bootAwareItem.sharedPrefKey).commit() + } + + val encryptedPrefs: SharedPreferences = + context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE) + + encryptedPrefs + .edit() + .putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue) + .commit() + + launcherPrefs.migrateStartupDataToDeviceProtectedStorage() + assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isTrue() + + launcherPrefs.removeSync(bootAwareItem) + } + + @Test + fun migrate_onlyEncryptedItemsToDeviceProtectedStorage_doesNotHappen() { + val onlyEncryptedItem = + LauncherPrefs.backedUpItem( + TEST_PREF_KEY + "_", + TEST_DEFAULT_VALUE + "_", + EncryptionType.ENCRYPTED + ) + + val bootAwarePrefs: SharedPreferences = + context + .createDeviceProtectedStorageContext() + .getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE) + + if (bootAwarePrefs.contains(onlyEncryptedItem.sharedPrefKey)) { + bootAwarePrefs.edit().remove(onlyEncryptedItem.sharedPrefKey).commit() + } + + val encryptedPrefs: SharedPreferences = + context.getSharedPreferences(onlyEncryptedItem.sharedPrefFile, Context.MODE_PRIVATE) + + encryptedPrefs + .edit() + .putString(onlyEncryptedItem.sharedPrefKey, onlyEncryptedItem.defaultValue) + .commit() + + launcherPrefs.migrateStartupDataToDeviceProtectedStorage() + assertThat(bootAwarePrefs.contains(onlyEncryptedItem.sharedPrefKey)).isFalse() + + encryptedPrefs.edit().remove(onlyEncryptedItem.sharedPrefKey).commit() + } } From a5ac9107b8ab47f51f1d8afb8a269f89f4ec5129 Mon Sep 17 00:00:00 2001 From: Sihua Ma Date: Thu, 22 Feb 2024 14:41:58 -0800 Subject: [PATCH 23/24] Enforce parent outline for widgets This bounds the widget view inside BaseLauncherAppWidgetHostView to reduce the overlap Bug: 322919716 Flag: N/A Test: Manual Change-Id: I270d94e3fff96724e95f9a38ab41f83beebd1271 --- .../widget/BaseLauncherAppWidgetHostView.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java index 580b4f11ea..104209ef53 100644 --- a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java @@ -34,6 +34,17 @@ import com.android.launcher3.util.Executors; */ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView { + private static final ViewOutlineProvider VIEW_OUTLINE_PROVIDER = new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + // Since ShortcutAndWidgetContainer sets clipChildren to false, we should restrict the + // outline to be the view bounds, otherwise widgets might draw themselves outside of + // the launcher view. Setting alpha to 0 to match the previous behavior. + outline.setRect(0, 0, view.getWidth(), view.getHeight()); + outline.setAlpha(.0f); + } + }; + protected final LayoutInflater mInflater; private final Rect mEnforcedRectangle = new Rect(); @@ -49,10 +60,13 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo } }; + private boolean mIsCornerRadiusEnforced; + public BaseLauncherAppWidgetHostView(Context context) { super(context); setExecutor(Executors.THREAD_POOL_EXECUTOR); + setClipToOutline(true); mInflater = LayoutInflater.from(context); mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext()); @@ -84,8 +98,8 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo @UiThread private void resetRoundedCorners() { - setOutlineProvider(ViewOutlineProvider.BACKGROUND); - setClipToOutline(false); + setOutlineProvider(VIEW_OUTLINE_PROVIDER); + mIsCornerRadiusEnforced = false; } @UiThread @@ -104,7 +118,7 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo background, mEnforcedRectangle); setOutlineProvider(mCornerRadiusEnforcementOutline); - setClipToOutline(true); + mIsCornerRadiusEnforced = true; invalidateOutline(); } @@ -115,6 +129,6 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo /** Returns true if the corner radius are enforced for this App Widget. */ public boolean hasEnforcedCornerRadius() { - return getClipToOutline(); + return mIsCornerRadiusEnforced; } } From 40fc6eb323d3d42e3b745b528e42b20ef7aa3cb3 Mon Sep 17 00:00:00 2001 From: Saumya Prakash Date: Tue, 20 Feb 2024 22:50:31 +0000 Subject: [PATCH 24/24] Ensure taskbar insets for camera cutout only apply on the needed sides For devices with a camera cutout, we only need the increased inset to accomodate a camera cutout on the sides/orientations where the cutout interrupts the taskbar. This change ensures that the insets are only added when necessary. Fix: 325508089 Test: Open an app and ensure the insets remain unchanged for 0 and 90 degree rotations. Ensure the insets for 180 and 270 degrees are still covering the camera cutout. Flag: N/A Change-Id: I3ee14ffc84d72512acdb750d0f614acacefc7639 --- .../taskbar/TaskbarActivityContext.java | 62 ++++++++++--------- .../taskbar/TaskbarInsetsController.kt | 21 ++----- 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 9006df8a1a..50fe6bee69 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -505,52 +505,26 @@ public class TaskbarActivityContext extends BaseTaskbarContext { /** * Creates {@link WindowManager.LayoutParams} for Taskbar, and also sets LP.paramsForRotation - * for taskbar showing as navigation bar + * for taskbar */ private WindowManager.LayoutParams createAllWindowParams() { final int windowType = ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL; WindowManager.LayoutParams windowLayoutParams = createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE); - if (!isPhoneButtonNavMode()) { - return windowLayoutParams; - } - // Provide WM layout params for all rotations to cache, see NavigationBar#getBarLayoutParams - int width = WindowManager.LayoutParams.MATCH_PARENT; - int height = WindowManager.LayoutParams.MATCH_PARENT; - int gravity = Gravity.BOTTOM; windowLayoutParams.paramsForRotation = new WindowManager.LayoutParams[4]; for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { WindowManager.LayoutParams lp = createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE); - switch (rot) { - case Surface.ROTATION_0, Surface.ROTATION_180 -> { - // Defaults are fine - width = WindowManager.LayoutParams.MATCH_PARENT; - height = mLastRequestedNonFullscreenSize; - gravity = Gravity.BOTTOM; - } - case Surface.ROTATION_90 -> { - width = mLastRequestedNonFullscreenSize; - height = WindowManager.LayoutParams.MATCH_PARENT; - gravity = Gravity.END; - } - case Surface.ROTATION_270 -> { - width = mLastRequestedNonFullscreenSize; - height = WindowManager.LayoutParams.MATCH_PARENT; - gravity = Gravity.START; - } - + if (isPhoneButtonNavMode()) { + populatePhoneButtonNavModeWindowLayoutParams(rot, lp); } - lp.width = width; - lp.height = height; - lp.gravity = gravity; windowLayoutParams.paramsForRotation[rot] = lp; } - // Override current layout params + // Override with current layout params WindowManager.LayoutParams currentParams = windowLayoutParams.paramsForRotation[getDisplay().getRotation()]; windowLayoutParams.width = currentParams.width; @@ -560,6 +534,32 @@ public class TaskbarActivityContext extends BaseTaskbarContext { return windowLayoutParams; } + /** + * Update {@link WindowManager.LayoutParams} with values specific to phone and 3 button + * navigation users + */ + private void populatePhoneButtonNavModeWindowLayoutParams(int rot, + WindowManager.LayoutParams lp) { + lp.width = WindowManager.LayoutParams.MATCH_PARENT; + lp.height = WindowManager.LayoutParams.MATCH_PARENT; + lp.gravity = Gravity.BOTTOM; + + // Override with per-rotation specific values + switch (rot) { + case Surface.ROTATION_0, Surface.ROTATION_180 -> { + lp.height = mLastRequestedNonFullscreenSize; + } + case Surface.ROTATION_90 -> { + lp.width = mLastRequestedNonFullscreenSize; + lp.gravity = Gravity.END; + } + case Surface.ROTATION_270 -> { + lp.width = mLastRequestedNonFullscreenSize; + lp.gravity = Gravity.START; + } + } + } + public void onConfigurationChanged(@Config int configChanges) { mControllers.onConfigurationChanged(configChanges); if (!mIsUserSetupComplete) { @@ -920,8 +920,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext { } if (landscapePhoneButtonNav) { mWindowLayoutParams.width = size; + mWindowLayoutParams.paramsForRotation[getDisplay().getRotation()].width = size; } else { mWindowLayoutParams.height = size; + mWindowLayoutParams.paramsForRotation[getDisplay().getRotation()].height = size; } mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged(); notifyUpdateLayoutParams(); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt index aa457ca4f1..567fad02ac 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt @@ -118,11 +118,9 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas getProvidedInsets(insetsRoundedCornerFlag) } - if (!context.isGestureNav) { - if (windowLayoutParams.paramsForRotation != null) { - for (layoutParams in windowLayoutParams.paramsForRotation) { - layoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag) - } + if (windowLayoutParams.paramsForRotation != null) { + for (layoutParams in windowLayoutParams.paramsForRotation) { + layoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag) } } @@ -156,19 +154,12 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas ) } - val gravity = windowLayoutParams.gravity - // Pre-calculate insets for different providers across different rotations for this gravity for (rotation in Surface.ROTATION_0..Surface.ROTATION_270) { // Add insets for navbar rotated params - if (windowLayoutParams.paramsForRotation != null) { - val layoutParams = windowLayoutParams.paramsForRotation[rotation] - for (provider in layoutParams.providedInsets) { - setProviderInsets(provider, layoutParams.gravity, rotation) - } - } - for (provider in windowLayoutParams.providedInsets) { - setProviderInsets(provider, gravity, rotation) + val layoutParams = windowLayoutParams.paramsForRotation[rotation] + for (provider in layoutParams.providedInsets) { + setProviderInsets(provider, layoutParams.gravity, rotation) } } context.notifyUpdateLayoutParams()