diff --git a/Android.bp b/Android.bp
index edc3dd934f..ad2f77ee20 100644
--- a/Android.bp
+++ b/Android.bp
@@ -33,6 +33,9 @@ license {
android_library {
name: "launcher-aosp-tapl",
+ libs: [
+ "framework-statsd",
+ ],
static_libs: [
"androidx.annotation_annotation",
"androidx.test.runner",
@@ -112,6 +115,7 @@ android_library {
"androidx.preference_preference",
"androidx.slice_slice-view",
"androidx.cardview_cardview",
+ "com.google.android.material_material",
"iconloader_base",
],
manifest: "AndroidManifest-common.xml",
@@ -148,9 +152,13 @@ android_app {
],
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"src_shortcuts_overrides/**/*.java",
+ "src_shortcuts_overrides/**/*.kt",
"src_ui_overrides/**/*.java",
+ "src_ui_overrides/**/*.kt",
"ext_tests/src/**/*.java",
+ "ext_tests/src/**/*.kt",
],
resource_dirs: [
"ext_tests/res",
@@ -191,8 +199,12 @@ android_library {
resource_dirs: [
"quickstep/res",
],
+ libs: [
+ "framework-statsd",
+ ],
static_libs: [
"Launcher3ResLib",
+ "lottie",
"SystemUISharedLib",
"SystemUI-statsd",
],
@@ -204,7 +216,10 @@ android_library {
// Source code used for test helpers
filegroup {
name: "launcher-src-ext-tests",
- srcs: ["ext_tests/src/**/*.java"],
+ srcs: [
+ "ext_tests/src/**/*.java",
+ "ext_tests/src/**/*.kt",
+ ],
}
// Common source files used to build launcher
@@ -212,8 +227,11 @@ filegroup {
name: "launcher-src-no-build-config",
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"src_shortcuts_overrides/**/*.java",
+ "src_shortcuts_overrides/**/*.kt",
"quickstep/src/**/*.java",
+ "quickstep/src/**/*.kt",
],
}
@@ -223,20 +241,27 @@ filegroup {
srcs: ["proguard.flags"],
}
-
// Library with all the dependencies for building Launcher Go
android_library {
name: "LauncherGoResLib",
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"quickstep/src/**/*.java",
+ "quickstep/src/**/*.kt",
"go/src/**/*.java",
+ "go/src/**/*.kt",
"go/quickstep/src/**/*.java",
+ "go/quickstep/src/**/*.kt",
],
resource_dirs: [
"go/res",
"go/quickstep/res",
],
+ // Note the ordering here is important when it comes to resource
+ // overriding. We want the most specific resource overrides defined
+ // in QuickstepResLib to take precendece, so it should be the final
+ // dependency. See b/205278434 for how this can go wrong.
static_libs: [
"Launcher3CommonDepsLib",
"QuickstepResLib",
@@ -252,3 +277,30 @@ android_library {
},
}
+// Build rule for Quickstep library
+android_library {
+ name: "Launcher3QuickStepLib",
+ srcs: [
+ ":launcher-src-no-build-config",
+ ],
+ resource_dirs: [],
+ libs: [
+ "framework-statsd",
+ ],
+ // Note the ordering here is important when it comes to resource
+ // overriding. We want the most specific resource overrides defined
+ // in QuickstepResLib to take precendece, so it should be the final
+ // dependency. See b/208647810 for how this can go wrong.
+ static_libs: [
+ "SystemUI-statsd",
+ "SystemUISharedLib",
+ "Launcher3CommonDepsLib",
+ "QuickstepResLib",
+ ],
+ manifest: "quickstep/AndroidManifest.xml",
+ platform_apis: true,
+ min_sdk_version: "current",
+ lint: {
+ baseline_filename: "lint-baseline-launcher3.xml",
+ },
+}
diff --git a/Android.mk b/Android.mk
index 6db529f64e..ceaaf138f2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -49,45 +49,9 @@ LOCAL_MANIFEST_FILE := go/AndroidManifest.xml
LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/LICENSE.txt
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
include $(BUILD_PACKAGE)
-#
-# Build rule for Quickstep library.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT2_ONLY := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- SystemUI-statsd \
- SystemUISharedLib
-ifneq (,$(wildcard frameworks/base))
- LOCAL_PRIVATE_PLATFORM_APIS := true
-else
- LOCAL_SDK_VERSION := system_current
- LOCAL_MIN_SDK_VERSION := 26
-endif
-LOCAL_MODULE := Launcher3QuickStepLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/LICENSE.txt
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-java-files-under, quickstep/src) \
- $(call all-java-files-under, src_shortcuts_overrides)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
-LOCAL_PROGUARD_ENABLED := disabled
-
-
-LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
#
# Build rule for Quickstep app.
#
@@ -121,7 +85,7 @@ LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/LICENSE.txt
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
include $(BUILD_PACKAGE)
@@ -141,7 +105,7 @@ else
LOCAL_SDK_VERSION := system_current
LOCAL_MIN_SDK_VERSION := 26
endif
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
+LOCAL_STATIC_ANDROID_LIBRARIES := LauncherGoResLib
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
@@ -172,7 +136,7 @@ LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/LICENSE.txt
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
include $(BUILD_PACKAGE)
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 728c7e25ac..cb141df421 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -33,7 +33,6 @@
with some minor changed based on the derivative app.
-->
-
@@ -149,7 +148,7 @@
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c1666c022c..d34430c428 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -34,6 +34,7 @@
android:fullBackupOnly="true"
android:fullBackupContent="@xml/backupscheme"
android:hardwareAccelerated="true"
+ android:debuggable="true"
android:icon="@drawable/ic_launcher_home"
android:label="@string/derived_app_name"
android:theme="@style/AppTheme"
@@ -52,7 +53,7 @@
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|density"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
diff --git a/SystemUIShared/src/com/android/systemui/shared/system/QuickStepContract.java b/SystemUIShared/src/com/android/systemui/shared/system/QuickStepContract.java
index 5754a67776..87e6fe60a1 100644
--- a/SystemUIShared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/SystemUIShared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -297,14 +297,14 @@ public class QuickStepContract {
* These values are expressed in pixels because they should not respect display or font
* scaling, this means that we don't have to reload them on config changes.
*/
- public static float getWindowCornerRadius(Resources resources) {
+ public static float getWindowCornerRadius(Context context) {
if (sRecentsDisabled) {
return 0;
}
if (sHasCustomCornerRadius) {
return sCustomCornerRadius;
}
- return ScreenDecorationsUtils.getWindowCornerRadius(resources);
+ return ScreenDecorationsUtils.getWindowCornerRadius(context);
}
/**
diff --git a/buglist_with_title.txt b/buglist_with_title.txt
deleted file mode 100644
index aa8b413284..0000000000
--- a/buglist_with_title.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-144170434 twickham P1 FIXED Improve Overview -> Home transition ----
-149934536 twickham P2 FIXED Update gesture nav pullback logic ----
-154951045 peanutbutter P1 FIXED Odd animation occuring at times when swiping to home ----
-154964045 awickham P2 FIXED "Clear all" text is not in the middle of app's window vertically ----
-158701272 twickham P4 FIXED Discontinuities when long-swiping to home ----
-160361464 tracyzhou P2 FIXED Place launcher above the target app in live tile mode ----
-160568387 twickham P2 FIXED Can't get to app switcher by swiping up (motion pause not detected) ----
-160718310 xuqiu P1 FIXED With "Select" overview action selected, App icon is missing in other overview apps after orientation change ----
-160748731 sunnygoyal P2 ASSIGNED Unify prediction model with Launcher model ----
-160759508 twickham P2 FIXED Swipe up cannot back to home screen in overview. ----
-161273376 xuqiu P2 FIXED [Overview Actions] Add logging and helpful messages ----
-161536946 twickham P2 FIXED Haptics don't indicate snap-to in overview, ----
-161685099 winsonc P2 FIXED Screen still stay at the quick settings/notification when I swipe up with 3 finger to check the all apps. ----
-161801331 hyunyoungs P2 FIXED Change AllAppsSearch plugin to support only data fetch ----
-161901771 xuqiu P1 FIXED Overlapping layer of highlights with app layout getting darker when keep rotating the device from "Feedback" viewpoint in split screen ----
-161939759 sunnygoyal P2 FIXED RD1A: Going to overview in landscape mode clips the screen content ----
-162012217 perumaal P2 ASSIGNED Leaked Activity Caused by Gleams ----
-162454040 bookatz P2 ASSIGNED Create multiuser test that checks that opening an app works properly ----
-162480567 sfufa P4 FIXED Enable Item Decorations for search items ----
-162564471 tracyzhou P2 FIXED [Live tile] Handle tapping overview actions in live tile mode ----
-162623012 zakcohen P1 ASSIGNED Enable chips flag ----
-162812884 winsonc P2 ASSIGNED [R]The color have not changed in some page after turning on the dark theme. ----
-162861289 hyunyoungs P2 FIXED Add FocusIndicator support to DEVICE_SEARCH feature in S ----
-162871508 sfufa P2 ASSIGNED Introduce support for Hero app section ----
diff --git a/build.gradle b/build.gradle
index 4878f4cc3f..22cb59b426 100644
--- a/build.gradle
+++ b/build.gradle
@@ -348,6 +348,8 @@ dependencies {
implementation 'com.google.protobuf:protobuf-javalite:3.8.0'
implementation 'com.github.LawnchairLauncher:oss-notices:1.0.2'
+
+ api 'com.airbnb.android:lottie:3.3.0'
}
protobuf {
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index 72b8d3fcae..0f61d149d0 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -18,6 +18,8 @@ package com.android.launcher3.testing;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import android.app.Activity;
+import android.app.Application;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
@@ -31,7 +33,10 @@ import com.android.launcher3.LauncherSettings;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedList;
+import java.util.Map;
+import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -41,9 +46,48 @@ import java.util.concurrent.TimeUnit;
public class DebugTestInformationHandler extends TestInformationHandler {
private static LinkedList sLeaks;
private static Collection sEvents;
+ private static Application.ActivityLifecycleCallbacks sActivityLifecycleCallbacks;
+ private static final Map sActivities =
+ Collections.synchronizedMap(new WeakHashMap<>());
+ private static int sActivitiesCreatedCount = 0;
public DebugTestInformationHandler(Context context) {
init(context);
+ if (sActivityLifecycleCallbacks == null) {
+ sActivityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ sActivities.put(activity, true);
+ ++sActivitiesCreatedCount;
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ }
+ };
+ ((Application) context.getApplicationContext())
+ .registerActivityLifecycleCallbacks(sActivityLifecycleCallbacks);
+ }
}
private static void runGcAndFinalizersSync() {
@@ -80,7 +124,7 @@ public class DebugTestInformationHandler extends TestInformationHandler {
}
@Override
- public Bundle call(String method) {
+ public Bundle call(String method, String arg) {
final Bundle response = new Bundle();
switch (method) {
case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
@@ -160,8 +204,22 @@ public class DebugTestInformationHandler extends TestInformationHandler {
}
}
+ case TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, sActivitiesCreatedCount);
+ return response;
+ }
+
+ case TestProtocol.REQUEST_GET_ACTIVITIES: {
+ response.putStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ sActivities.keySet().stream().map(
+ a -> a.getClass().getSimpleName() + " ("
+ + (a.isDestroyed() ? "destroyed" : "current") + ")")
+ .toArray(String[]::new));
+ return response;
+ }
+
default:
- return super.call(method);
+ return super.call(method, arg);
}
}
}
diff --git a/go/AndroidManifest-launcher.xml b/go/AndroidManifest-launcher.xml
index 6a8f715bb2..2223036a7a 100644
--- a/go/AndroidManifest-launcher.xml
+++ b/go/AndroidManifest-launcher.xml
@@ -49,7 +49,7 @@
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|density|uiMode"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
diff --git a/go/quickstep/res/layout/overview_actions_container.xml b/go/quickstep/res/layout/overview_actions_container.xml
index 0e718ca08d..196541f0a9 100644
--- a/go/quickstep/res/layout/overview_actions_container.xml
+++ b/go/quickstep/res/layout/overview_actions_container.xml
@@ -14,12 +14,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|bottom">
+
+
\ No newline at end of file
diff --git a/go/quickstep/res/values-gu/strings.xml b/go/quickstep/res/values-gu/strings.xml
new file mode 100644
index 0000000000..7c627e18f8
--- /dev/null
+++ b/go/quickstep/res/values-gu/strings.xml
@@ -0,0 +1,19 @@
+
+
+ "ઍપ શેર કરો"
+ "સાંભળો"
+ "અનુવાદ કરો"
+ "લેન્સ"
+ "સમજાઈ ગયું"
+ "રદ કરો"
+ "સેટિંગ"
+ "સ્ક્રીન પરની ટેક્સ્ટનો અનુવાદ કરો અથવા સાંભળો"
+ "તમારી સ્ક્રીન પરની ટેક્સ્ટ, વેબ ઍડ્રેસ અને સ્ક્રીનશૉટ જેવી માહિતી Google સાથે શેર કરવામાં આવી શકે છે.\n\nતમે શેર કરતા હો તેવી માહિતીમાં ફેરફાર કરવા માટે, ""સેટિંગ > ઍપ > ડિફૉલ્ટ ઍપ > ડિજિટલ આસિસ્ટંટ ઍપ"" પર જાઓ."
+ "આ સુવિધાનો ઉપયોગ કરવા માટે આસિસ્ટંટ પસંદ કરો"
+ "તમારી સ્ક્રીન પર ટેક્સ્ટ સાંભળવા માટે અથવા તેનો અનુવાદ કરવા માટે, સેટિંગમાં જઈને ડિજિટલ આસિસ્ટંટ ઍપ પસંદ કરો"
+ "આ સુવિધાનો ઉપયોગ કરવા માટે તમારું આસિસ્ટંટ બદલો"
+ "તમારી સ્ક્રીન પર ટેક્સ્ટ સાંભળવા માટે અથવા તેનો અનુવાદ કરવા માટે, સેટિંગમાં જઈને તમારી ડિજિટલ આસિસ્ટંટ ઍપ બદલો"
+ "આ સ્ક્રીન પર ટેક્સ્ટ સાંભળવા માટે અહીં ટૅપ કરો"
+ "આ સ્ક્રીન પર ટેક્સ્ટનો અનુવાદ કરવા માટે અહીં ટૅપ કરો"
+
diff --git a/go/quickstep/res/values-kn/strings.xml b/go/quickstep/res/values-kn/strings.xml
new file mode 100644
index 0000000000..ef1954199b
--- /dev/null
+++ b/go/quickstep/res/values-kn/strings.xml
@@ -0,0 +1,19 @@
+
+
+ "ಆ್ಯಪ್ ಹಂಚಿಕೊಳ್ಳಿ"
+ "ಆಲಿಸಿ"
+ "ಅನುವಾದಿಸಿ"
+ "Lens"
+ "ಅರ್ಥವಾಯಿತು"
+ "ರದ್ದುಗೊಳಿಸಿ"
+ "ಸೆಟ್ಟಿಂಗ್ಗಳು"
+ "ಸ್ಕ್ರೀನ್ ಮೇಲಿರುವ ಪಠ್ಯವನ್ನು ಅನುವಾದಿಸಿ ಅಥವಾ ಆಲಿಸಿ"
+ "ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲಿರುವ ಪಠ್ಯ, ವೆಬ್ ವಿಳಾಸಗಳು, ಮತ್ತು ಸ್ಕ್ರೀನ್ಶಾಟ್ಗಳಂತಹ ಮಾಹಿತಿಯನ್ನು Google ಜೊತೆಗೆ ಹಂಚಿಕೊಳ್ಳಬಹುದು.\n\nನೀವು ಯಾವ ಮಾಹಿತಿಯನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತೀರಿ ಎಂಬುದನ್ನು ಬದಲಾಯಿಸಲು, ""ಸೆಟ್ಟಿಂಗ್ಗಳು, ಆ್ಯಪ್ಗಳು ಮತ್ತು ಡೀಫಾಲ್ಟ್ ಆ್ಯಪ್ಗಳು, ಡಿಜಿಟಲ್ ಅಸಿಸ್ಟೆಂಟ್ ಆ್ಯಪ್ ಎಂಬಲ್ಲಿ ಹೋಗಿ""."
+ "ಈ ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬಳಸಲು ಅಸಿಸ್ಟಂಟ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"
+ "ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ನಲ್ಲಿರುವ ಪಠ್ಯವನ್ನು ಆಲಿಸಲು ಅಥವಾ ಅನುವಾದಿಸಲು ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಡಿಜಿಟಲ್ ಅಸಿಸ್ಟೆಂಟ್ ಆ್ಯಪ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"
+ "ಈ ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬಳಸಲು ನಿಮ್ಮ ಅಸಿಸ್ಟಂಟ್ ಅನ್ನು ಬದಲಾಯಿಸಿ"
+ "ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ನಲ್ಲಿರುವ ಪಠ್ಯವನ್ನು ಆಲಿಸಲು ಅಥವಾ ಅನುವಾದಿಸಲು ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ನಿಮ್ಮ ಡಿಜಿಟಲ್ ಅಸಿಸ್ಟೆಂಟ್ ಆ್ಯಪ್ ಅನ್ನು ಬದಲಾಯಿಸಿ"
+ "ಈ ಸ್ಕ್ರೀನ್ನಲ್ಲಿರುವ ಪಠ್ಯವನ್ನು ಆಲಿಸಲು ಇಲ್ಲಿ ಟ್ಯಾಪ್ ಮಾಡಿ"
+ "ಈ ಸ್ಕ್ರೀನ್ನಲ್ಲಿರುವ ಪಠ್ಯವನ್ನು ಅನುವಾದಿಸಲು ಇಲ್ಲಿ ಟ್ಯಾಪ್ ಮಾಡಿ"
+
diff --git a/go/quickstep/src/com/android/quickstep/views/GoOverviewActionsView.java b/go/quickstep/src/com/android/quickstep/views/GoOverviewActionsView.java
index 97ba5905e0..d4eca2fd40 100644
--- a/go/quickstep/src/com/android/quickstep/views/GoOverviewActionsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/GoOverviewActionsView.java
@@ -35,6 +35,7 @@ import com.android.quickstep.util.RecentsOrientedState;
*/
public class GoOverviewActionsView extends OverviewActionsView {
+ @Nullable
private ArrowTipView mArrowTipView;
public GoOverviewActionsView(Context context) {
@@ -117,7 +118,7 @@ public class GoOverviewActionsView extends OverviewActionsView
+ errorLine1=" setColorResources(mWallpaperColorResources);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ line="528"
+ column="17"/>
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 6d49d75953..c5599887dd 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -59,6 +59,8 @@ message ContainerInfo {
SettingsContainer settings_container = 9;
PredictedHotseatContainer predicted_hotseat_container = 10;
TaskSwitcherContainer task_switcher_container = 11;
+ TaskBarContainer task_bar_container = 12;
+ WallpapersContainer wallpapers_container = 13;
ExtendedContainers extended_containers = 20;
}
}
@@ -100,6 +102,16 @@ message SettingsContainer {
message TaskSwitcherContainer {
}
+// Container for taskbar.
+// Configured to show up on large screens(tablet-sized) such as foldables in expanded state, within
+// an app view(not in launcher screen).
+message TaskBarContainer {
+ optional int32 index = 1;
+
+ // Bit encoded value to capture pinned and predicted taskbar positions.
+ optional int32 cardinality = 2;
+}
+
enum Attribute {
UNKNOWN = 0;
DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat
@@ -141,6 +153,8 @@ enum Attribute {
ALL_APPS_SEARCH_RESULT_NAVVYSITE = 25;
ALL_APPS_SEARCH_RESULT_TIPS = 26;
ALL_APPS_SEARCH_RESULT_PEOPLE_TILE = 27;
+ ALL_APPS_SEARCH_RESULT_LEGACY_SHORTCUT = 30;
+ ALL_APPS_SEARCH_RESULT_ASSISTANT_MEMORY = 31;
WIDGETS_BOTTOM_TRAY = 28;
WIDGETS_TRAY_PREDICTION = 29;
@@ -230,9 +244,16 @@ message FolderContainer {
oneof ParentContainer {
WorkspaceContainer workspace = 4;
HotseatContainer hotseat = 5;
+ TaskBarContainer taskbar = 6;
}
}
+// Represents wallpapers container for quick switching.
+message WallpapersContainer {
+ // Number of wallpapers in the container.
+ optional int32 cardinality = 1;
+}
+
// Represents state of EditText field before update.
enum FromState {
// Default value.
diff --git a/quickstep/Android.bp b/quickstep/Android.bp
index 38c9919b07..7b3e6c45a2 100644
--- a/quickstep/Android.bp
+++ b/quickstep/Android.bp
@@ -26,3 +26,19 @@ filegroup {
path: "robolectric_tests",
srcs: ["robolectric_tests/src/**/*.java"],
}
+
+filegroup {
+ name: "launcher3-quickstep-tests-src",
+ path: "tests",
+ srcs: ["tests/src/**/*.java"],
+}
+
+filegroup {
+ name: "launcher3-quickstep-oop-tests-src",
+ path: "tests",
+ srcs: [
+ "tests/src/com/android/quickstep/NavigationModeSwitchRule.java",
+ "tests/src/com/android/quickstep/AbstractQuickStepTest.java",
+ "tests/src/com/android/quickstep/TaplTestsQuickstep.java",
+ ]
+}
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index ee07bfadb0..e6119fac7a 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -52,7 +52,7 @@
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|density"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index b43d8d1a4b..352cd3e7b6 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -22,17 +22,13 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3">
-
-
+
@@ -40,8 +36,8 @@
+
-
@@ -106,7 +102,6 @@
@@ -122,7 +117,6 @@
-
-
-
-
-
-
-
diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml
new file mode 100644
index 0000000000..fb014f75b1
--- /dev/null
+++ b/quickstep/res/drawable/bg_overview_clear_all_button.xml
@@ -0,0 +1,27 @@
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/button_taskbar_edu_bordered.xml b/quickstep/res/drawable/button_taskbar_edu_bordered.xml
new file mode 100644
index 0000000000..47f8e8f8cd
--- /dev/null
+++ b/quickstep/res/drawable/button_taskbar_edu_bordered.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_picker_handle.xml b/quickstep/res/drawable/button_taskbar_edu_colored.xml
similarity index 56%
rename from res/drawable/bg_widgets_picker_handle.xml
rename to quickstep/res/drawable/button_taskbar_edu_colored.xml
index 68681a684d..70bfc9f255 100644
--- a/res/drawable/bg_widgets_picker_handle.xml
+++ b/quickstep/res/drawable/button_taskbar_edu_colored.xml
@@ -13,17 +13,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
- -
-
-
-
-
-
- -
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/default_sandbox_mock_launcher.xml b/quickstep/res/drawable/default_sandbox_mock_launcher.xml
deleted file mode 100644
index 38fbcf09b6..0000000000
--- a/quickstep/res/drawable/default_sandbox_mock_launcher.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
diff --git a/quickstep/res/drawable/gesture_tutorial_finger_dot.xml b/quickstep/res/drawable/gesture_tutorial_finger_dot.xml
new file mode 100644
index 0000000000..5f8aafd30a
--- /dev/null
+++ b/quickstep/res/drawable/gesture_tutorial_finger_dot.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_motion_back.xml b/quickstep/res/drawable/gesture_tutorial_motion_back.xml
deleted file mode 100644
index a6860fac64..0000000000
--- a/quickstep/res/drawable/gesture_tutorial_motion_back.xml
+++ /dev/null
@@ -1,1233 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_motion_home_dark_mode.xml b/quickstep/res/drawable/gesture_tutorial_motion_home_dark_mode.xml
deleted file mode 100644
index aff35c1bce..0000000000
--- a/quickstep/res/drawable/gesture_tutorial_motion_home_dark_mode.xml
+++ /dev/null
@@ -1,1254 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_motion_home_light_mode.xml b/quickstep/res/drawable/gesture_tutorial_motion_home_light_mode.xml
deleted file mode 100644
index 98d97ad62d..0000000000
--- a/quickstep/res/drawable/gesture_tutorial_motion_home_light_mode.xml
+++ /dev/null
@@ -1,1254 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_motion_overview_dark_mode.xml b/quickstep/res/drawable/gesture_tutorial_motion_overview_dark_mode.xml
deleted file mode 100644
index b007d20f34..0000000000
--- a/quickstep/res/drawable/gesture_tutorial_motion_overview_dark_mode.xml
+++ /dev/null
@@ -1,1623 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/gesture_tutorial_ripple.xml b/quickstep/res/drawable/gesture_tutorial_ripple.xml
similarity index 100%
rename from res/drawable/gesture_tutorial_ripple.xml
rename to quickstep/res/drawable/gesture_tutorial_ripple.xml
diff --git a/quickstep/res/drawable/ic_screenshot.xml b/quickstep/res/drawable/ic_screenshot.xml
index d97eae1d15..9ee6c44011 100644
--- a/quickstep/res/drawable/ic_screenshot.xml
+++ b/quickstep/res/drawable/ic_screenshot.xml
@@ -13,11 +13,20 @@
limitations under the License.
-->
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
-
+ android:pathData="M5.8334,1.666H8.3334V3.3327H5.8334V6.666H4.1667V3.3327C4.1667,2.4122 4.9129,1.666 5.8334,1.666Z"
+ android:fillColor="#000000"/>
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_accessibility_button.xml b/quickstep/res/drawable/ic_sysbar_accessibility_button.xml
new file mode 100644
index 0000000000..e0d5406810
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_accessibility_button.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_0.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_0.xml
new file mode 100644
index 0000000000..ff5cb9ef6b
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_0.xml
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_90.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_90.xml
new file mode 100644
index 0000000000..90fedb17ec
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_90.xml
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_0.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_0.xml
new file mode 100644
index 0000000000..a89e7a34ad
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_0.xml
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_90.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_90.xml
new file mode 100644
index 0000000000..0dc67b0d22
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_90.xml
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/mock_conversation.xml b/quickstep/res/drawable/mock_conversation.xml
deleted file mode 100644
index 272d9ed8a8..0000000000
--- a/quickstep/res/drawable/mock_conversation.xml
+++ /dev/null
@@ -1,212 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/quickstep/res/drawable/mock_conversations_list.xml b/quickstep/res/drawable/mock_conversations_list.xml
deleted file mode 100644
index 2dbc88f0bf..0000000000
--- a/quickstep/res/drawable/mock_conversations_list.xml
+++ /dev/null
@@ -1,361 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/quickstep/res/drawable/mock_webpage_dark_mode.xml b/quickstep/res/drawable/mock_webpage_dark_mode.xml
deleted file mode 100644
index 93b22b7d31..0000000000
--- a/quickstep/res/drawable/mock_webpage_dark_mode.xml
+++ /dev/null
@@ -1,251 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/quickstep/res/drawable/mock_webpage_light_mode.xml b/quickstep/res/drawable/mock_webpage_light_mode.xml
deleted file mode 100644
index 98abb92ab7..0000000000
--- a/quickstep/res/drawable/mock_webpage_light_mode.xml
+++ /dev/null
@@ -1,263 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/quickstep/res/drawable/task_menu_item_bg.xml b/quickstep/res/drawable/task_menu_item_bg.xml
index b6a8b909ee..16c13ebebc 100644
--- a/quickstep/res/drawable/task_menu_item_bg.xml
+++ b/quickstep/res/drawable/task_menu_item_bg.xml
@@ -15,7 +15,8 @@
limitations under the License.
-->
-
-
-
+
+
+
diff --git a/quickstep/res/drawable/taskbar_edu_splitscreen.png b/quickstep/res/drawable/taskbar_edu_splitscreen.png
new file mode 100644
index 0000000000..f9d2a63a0e
Binary files /dev/null and b/quickstep/res/drawable/taskbar_edu_splitscreen.png differ
diff --git a/quickstep/res/drawable/taskbar_edu_stashing.png b/quickstep/res/drawable/taskbar_edu_stashing.png
new file mode 100644
index 0000000000..f9d2a63a0e
Binary files /dev/null and b/quickstep/res/drawable/taskbar_edu_stashing.png differ
diff --git a/quickstep/res/drawable/taskbar_edu_switch_apps.png b/quickstep/res/drawable/taskbar_edu_switch_apps.png
new file mode 100644
index 0000000000..f9d2a63a0e
Binary files /dev/null and b/quickstep/res/drawable/taskbar_edu_switch_apps.png differ
diff --git a/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml b/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml
index d6160def0f..534f241ae9 100644
--- a/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml
+++ b/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml
@@ -16,7 +16,7 @@
-->
+ android:color="@color/taskbar_nav_icon_selection_ripple">
-
diff --git a/quickstep/res/interpolator/app_open_x.xml b/quickstep/res/interpolator/app_open_x.xml
new file mode 100644
index 0000000000..5fa0bcb9ae
--- /dev/null
+++ b/quickstep/res/interpolator/app_open_x.xml
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/quickstep/res/interpolator/three_point_fast_out_extra_slow_in.xml b/quickstep/res/interpolator/three_point_fast_out_extra_slow_in.xml
new file mode 100644
index 0000000000..70c4231140
--- /dev/null
+++ b/quickstep/res/interpolator/three_point_fast_out_extra_slow_in.xml
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index e79e57efef..9ad10dc54e 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -14,68 +14,111 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+ android:id="@+id/root_view"
+ android:background="@color/all_set_page_background" >
-
+ android:layout_height="match_parent"
+ android:id="@+id/content_view"
+ android:fitsSystemWindows="true">
-
-
-
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:scaleType="centerCrop"
-
+
+
-
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/allset_page_margin_horizontal"
+ android:layout_marginEnd="@dimen/allset_page_margin_horizontal"
+ android:layoutDirection="locale"
+ android:textDirection="locale"
+ android:forceHasOverlappingRendering="false"
+ android:fitsSystemWindows="true" >
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
index b51809492a..32183610fa 100644
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -45,8 +45,7 @@
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
- android:outlineProvider="none"
- android:theme="@style/HomeScreenElementTheme" />
+ android:outlineProvider="none" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_foldable_mock_conversation_list.xml b/quickstep/res/layout/gesture_tutorial_foldable_mock_conversation_list.xml
new file mode 100644
index 0000000000..e5cd9bce1e
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_foldable_mock_conversation_list.xml
@@ -0,0 +1,396 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_foldable_mock_hotseat.xml b/quickstep/res/layout/gesture_tutorial_foldable_mock_hotseat.xml
new file mode 100644
index 0000000000..5612666b69
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_foldable_mock_hotseat.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_foldable_mock_taskbar.xml b/quickstep/res/layout/gesture_tutorial_foldable_mock_taskbar.xml
new file mode 100644
index 0000000000..ddfeeecf04
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_foldable_mock_taskbar.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_foldable_mock_webpage.xml b/quickstep/res/layout/gesture_tutorial_foldable_mock_webpage.xml
new file mode 100644
index 0000000000..67e9b02b3d
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_foldable_mock_webpage.xml
@@ -0,0 +1,275 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index cdda43c8a8..08e6178bdb 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -25,13 +25,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
+ android:layout_alignParentBottom="true"/>
@@ -41,15 +40,56 @@
android:layout_height="20dp"
android:visibility="invisible" />
-
+ android:visibility="invisible">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_mock_conversation_list.xml b/quickstep/res/layout/gesture_tutorial_mock_conversation_list.xml
new file mode 100644
index 0000000000..364ad6d17d
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_mock_conversation_list.xml
@@ -0,0 +1,394 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_mock_hotseat.xml b/quickstep/res/layout/gesture_tutorial_mock_hotseat.xml
new file mode 100644
index 0000000000..b3e86cf4de
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_mock_hotseat.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_mock_webpage.xml b/quickstep/res/layout/gesture_tutorial_mock_webpage.xml
new file mode 100644
index 0000000000..bb20968d8f
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_mock_webpage.xml
@@ -0,0 +1,271 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index d258d08157..0fda0bf8d4 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -14,11 +14,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|bottom">
-
+
+
+
+
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index f303f31199..01d675f371 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -25,13 +25,6 @@
android:clipToPadding="false"
android:visibility="invisible" />
-
-
diff --git a/quickstep/res/layout/predicted_hotseat_edu.xml b/quickstep/res/layout/predicted_hotseat_edu.xml
index 1dab48267b..e4e3956c6e 100644
--- a/quickstep/res/layout/predicted_hotseat_edu.xml
+++ b/quickstep/res/layout/predicted_hotseat_edu.xml
@@ -73,6 +73,7 @@
launcher:containerType="hotseat" />
+
+
+
+
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
new file mode 100644
index 0000000000..cd5bcbdddd
--- /dev/null
+++ b/quickstep/res/layout/task_grouped.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/task_menu_with_arrow.xml b/quickstep/res/layout/task_menu_with_arrow.xml
new file mode 100644
index 0000000000..38573fd1e5
--- /dev/null
+++ b/quickstep/res/layout/task_menu_with_arrow.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/task_view_menu_option.xml b/quickstep/res/layout/task_view_menu_option.xml
index 5978b97dd4..8a8fc36b84 100644
--- a/quickstep/res/layout/task_view_menu_option.xml
+++ b/quickstep/res/layout/task_view_menu_option.xml
@@ -18,7 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical"
+ android:orientation="horizontal"
android:paddingTop="@dimen/task_card_menu_option_vertical_padding"
android:paddingBottom="@dimen/task_card_menu_option_vertical_padding"
android:background="@drawable/task_menu_item_bg"
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index e680233e6a..3b1d217ec5 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -13,12 +13,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+ android:layout_height="wrap_content"
+ android:clipChildren="false">
+
+
+
+
-
+ android:paddingTop="@dimen/taskbar_contextual_padding_top"
+ android:gravity="center_vertical"
+ android:layout_gravity="start"/>
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
+ android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
+ android:layout_marginEnd="@dimen/taskbar_contextual_button_margin"
+ android:gravity="center_vertical"
+ android:layout_gravity="end"/>
-
+
+
-
+ android:background="@color/taskbar_stashed_handle_dark_color"
+ android:clipToOutline="true"
+ android:layout_gravity="bottom"/>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_contextual_button.xml b/quickstep/res/layout/taskbar_contextual_button.xml
new file mode 100644
index 0000000000..4ffb8d81dc
--- /dev/null
+++ b/quickstep/res/layout/taskbar_contextual_button.xml
@@ -0,0 +1,21 @@
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_edu.xml b/quickstep/res/layout/taskbar_edu.xml
new file mode 100644
index 0000000000..3796ff930e
--- /dev/null
+++ b/quickstep/res/layout/taskbar_edu.xml
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_nav_button.xml b/quickstep/res/layout/taskbar_nav_button.xml
new file mode 100644
index 0000000000..aea4885d15
--- /dev/null
+++ b/quickstep/res/layout/taskbar_nav_button.xml
@@ -0,0 +1,24 @@
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 815ba042b2..935f546cf0 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -25,11 +25,12 @@
"Programgebruikinstellings"
"Vee alles uit"
"Onlangse programme"
+
+
"%1$s, %2$s"
"< 1 minuut"
"%1$s oor vandag"
"Programvoorstelle"
- "Alle programme"
"Jou voorspelde programme"
"Kry programvoorstelle in die onderste ry van jou tuisskerm"
"Kry programvoorstelle op jou tuisskerm se gunstelingery"
@@ -76,9 +77,22 @@
"Tutoriaal %1$d/%2$d"
"Deel"
"Skermkiekie"
+ "Verdeel"
+ "Probeer ander program om verdeelde skerm te gebruik"
+ "Program steun nie verdeelde skerm nie."
"Jou organisasie laat nie hierdie program toe nie"
"Slaan navigasietutoriaal oor?"
"Jy kan dit later in die %1$s-program kry"
"Kanselleer"
"Slaan oor"
+ "Draai skerm"
+ "Taakbalkopvoeding het verskyn"
+ "Taakbalkopvoeding is toegemaak"
+ "Gebruik die taakbalk om tussen programme te wissel"
+ "Sleep na die kant om twee programme gelyktydig te gebruik"
+ "Raak en hou om die taakbalk te versteek"
+ "Volgende"
+ "Terug"
+ "Maak toe"
+ "Klaar"
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 5a3df9d9bb..5dc5c955f2 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -25,11 +25,12 @@
"የመተግበሪያ አጠቃቀም ቅንብሮች"
"ሁሉንም አጽዳ"
"የቅርብ ጊዜ መተግበሪያዎች"
+
+
"%1$s፣ %2$s"
"< 1 ደቂቃ"
"ዛሬ %1$s ቀርቷል"
"የመተግበሪያ አስተያየቶች"
- "ሁሉም መተግበሪያዎች"
"የእርስዎ የሚገመቱ መተግበሪያዎች"
"በመነሻ ገጽዎ ታችኛው ረድፍ ላይ የመተግበሪያ አስተያየት ጥቆማዎችን ያግኙ"
"በመነሻ ማያ ገጽዎ የተወዳጆች ረድፍ ላይ የመተግበሪያ አስተያየት ጥቆማዎችን ያግኙ"
@@ -76,9 +77,22 @@
"አጋዥ ሥልጠና %1$d/%2$d"
"አጋራ"
"ቅጽበታዊ ገጽ እይታ"
+ "ክፈል"
+ "የተከፈለ ማያን ለመጠቀም ሌላ መተግበሪያ መታ ያድርጉ"
+ "መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"
"ይህ ድርጊት በመተግበሪያው ወይም በእርስዎ ድርጅት አይፈቀድም"
"የአሰሳ አጋዥ ሥልጠናን ይዝለሉ?"
"ይህን በኋላ በ%1$s መተግበሪያው ውስጥ ማግኘት ይችላሉ"
"ይቅር"
"ዝለል"
+ "ማያ ገጹን አዙር"
+ "የተግባር አሞሌ ትምህርት ይታያል"
+ "የተግባር አሞሌ ትምህርት ተዘግቷል"
+ "መተግበሪያዎችን ለመቀየር የተግባር አሞሌውን ይጠቀሙ"
+ "በአንድ ጊዜ ሁለት መተግበሪያዎችን ለመጠቀም ወደ ጎን ይጎትቱ"
+ "የተግባር አሞሌውን ለመደበቅ ነክተው ይያዙት"
+ "ቀጣይ"
+ "ተመለስ"
+ "ዝጋ"
+ "ተጠናቅቋል"
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index 8205f89444..b05ffadad1 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -25,11 +25,12 @@
"إعدادات استخدام التطبيق"
"محو الكل"
"التطبيقات المستخدمة مؤخرًا"
+
+
"%1$s، %2$s"
"أقل من دقيقة"
"يتبقى اليوم %1$s."
"التطبيقات المقترحة"
- "جميع التطبيقات"
"تطبيقاتك المتوقّعة"
"رؤية التطبيقات المقترحة في الصف السفلي من الشاشة الرئيسية"
"رؤية التطبيقات المقترحة في صف التطبيقات المفضّلة في الشاشة الرئيسية"
@@ -76,9 +77,22 @@
"الدليل التوجيهي %1$d من إجمالي %2$d"
"مشاركة"
"لقطة شاشة"
+ "تقسيم"
+ "انقر على تطبيق آخر لاستخدام وضع تقسيم الشاشة."
+ "التطبيق لا يتيح تقسيم الشاشة."
"لا يسمح التطبيق أو لا تسمح مؤسستك بهذا الإجراء."
"هل تريد تخطي الدليل التوجيهي؟"
"يمكنك العثور على هذا الدليل التوجيهي لاحقًا في التطبيق %1$s"
"إلغاء"
"التخطي"
+ "تدوير الشاشة"
+ "ظهرت لوحة تعليم استخدام شريط المهام."
+ "تم إغلاق لوحة تعليم استخدام شريط المهام."
+ "يمكنك استخدام شريط المهام للتبديل بين التطبيقات."
+ "اسحبه إلى جانب الشاشة لاستخدام تطبيقين في آنٍ واحد."
+ "انقر مع الاستمرار لإخفاء شريط المهام."
+ "الشاشة التالية"
+ "رجوع"
+ "إغلاق"
+ "تم"
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index 9b22c62e93..b59cfdee58 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -22,14 +22,15 @@
"পিন"
"Freeform"
"কোনো শেহতীয়া বস্তু নাই"
- "এপে ব্যৱহাৰ কৰা ডেটাৰ ছেটিংসমূহ"
- "সকলো মচক"
+ "এপে ব্যৱহাৰ কৰা ডেটাৰ ছেটিং"
+ "আটাইবোৰ মচক"
"শেহতীয়া এপসমূহ"
+
+
"%1$s, %2$s"
"< ১ মিনিট"
"আজি %1$s বাকী আছ"
"এপ চাজেশ্বন"
- "সকলো এপ্"
"আপোনাৰ প্ৰয়োজন হ\'ব পৰা এপ্"
"আপোনাৰ গৃহ স্ক্ৰীনৰ একেবাৰে তলৰ শাৰীটোত এপৰ পৰামর্শসমূহ পাওক"
"আপোনাৰ গৃহ স্ক্ৰীনৰ প্ৰিয় সমলৰ শাৰীটোত এপৰ পৰামর্শসমূহ পাওক"
@@ -38,7 +39,7 @@
"আপোনাৰ সকলোতকৈ বেছিকৈ ব্যৱহৃত এপ্সমূহ গৃহ স্ক্ৰীনতে সহজে এক্সেছ কৰক। আপোনাৰ ৰুটিনসমূহৰ ভিত্তিত পৰামর্শসমূহ সলনি হ\'ব। একেবাৰে তলৰ শাৰীটোত থকা এপ্সমূহ এটা নতুন ফ\'ল্ডাৰলৈ যাব।"
"এপৰ পৰামর্শসমূহ পাওক"
"নালাগে, ধন্যবাদ"
- "ছেটিংসমূহ"
+ "ছেটিং"
"সকলোতকৈ বেছিকৈ ব্যৱহৃত এপ্সমূহ ইয়াত প্ৰদর্শিত হয় আৰু ৰুটিনসমূহ ওপৰত ভিত্তি কৰি সলনি হয়"
"এপৰ পৰামর্শসমূহ পাবলৈ একেবাৰে তলৰ শাৰীত থকা এপ্সমূহ টানি আঁতৰাওক"
"খালী ঠাইত এপৰ পৰামর্শসমূহ যোগ কৰা হ\'ল"
@@ -76,9 +77,22 @@
"টিউট’ৰিয়েল %1$d/%2$d"
"শ্বেয়াৰ কৰক"
"স্ক্ৰীনশ্বট"
+ "বিভাজন কৰক"
+ "বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰিবলৈ অন্য এটা এপত টিপক"
+ "এপ্টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে।"
"এপ্টোৱে অথবা আপোনাৰ প্ৰতিষ্ঠানে এই কাৰ্যটোৰ অনুমতি নিদিয়ে"
"নেভিগেশ্বনৰ টিউট’ৰিয়েল এৰিব বিচাৰে নেকি?"
"আপুনি এয়া পাছত %1$s এপ্টোত বিচাৰিব পাৰিব"
"বাতিল কৰক"
"এৰি যাওক"
+ "স্ক্ৰীনখন ঘূৰাওক"
+ "টাস্কবাৰৰ শিক্ষাৰ পেনেলটো প্ৰদর্শিত হৈছে"
+ "টাস্কবাৰৰ শিক্ষাৰ পেনেলটো বন্ধ হৈছে"
+ "এপ্ সলনি কৰিবলৈ টাস্কবাৰডাল ব্যৱহাৰ কৰক"
+ "এবাৰতে দুটা এপ্ ব্যৱহাৰ কৰিবলৈ কাষলৈ টানি আনি এৰক"
+ "টাস্কবাৰডাল লুকুৱাবলৈ স্পৰ্শ কৰি ধৰি ৰাখক"
+ "পৰৱৰ্তী"
+ "উভতি যাওক"
+ "বন্ধ কৰক"
+ "হ’ল"
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index bd88392075..3349e6e8f3 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -25,11 +25,12 @@
"Tətbiq istifadə ayarları"
"Hamısını silin"
"Son tətbiqlər"
+
+
"%1$s, %2$s"
"< 1 dəq"
"Bu gün %1$s qaldı"
"Tətbiq təklifləri"
- "Bütün tətbiqlər"
"Təklif edilən tətbiqlər"
"Ana ekranın aşağı sırasında tətbiq təklifləri alın"
"Ana ekranın sevimlilər sırasında tətbiq təklifləri alın"
@@ -45,40 +46,56 @@
"Tətbiq təklifləri aktivdir"
"Tətbiq təklifləri deaktivdir"
"Proqnozlaşdırılan tətbiq: %1$s"
- "Ən sol tərəfdən sürüşdürdüyünüzə əmin olun."
- "Ekranın sol kənarından ortasına sürüşdürüb buraxın."
- "Bu qədər! İndi isə sağ kənardan sürüşdürün."
- "Ən sağ tərəfdən sürüşdürdüyünüzə əmin olun."
- "Ekranın sağ kənarından ortasına sürüşdürüb buraxın."
- "Geri getmə jestini tamamladınız. Sonra Əsas səhifəyə keçməyi öyrənin."
+ "Ekranın ən sağ və ya sol kənarından sürüşdürün."
+ "Ekranın sağ və ya sol kənarından ortasına sürüşdürüb buraxın."
+ "Geri qayıtmaq üçün sağdan sürüşdürmək qaydasını öyrəndiniz. Sonra tətbiqləri keçirməyi öyrənin."
+ "Geri getmə jestini tamamladınız."
"Ekranın altına çox yaxın sürüşdürmədiyinizə əmin olun."
"Geri qayıtma jestinin həssaslığını dəyişmək üçün Ayarlara keçin"
"Geri qayıtmaq üçün sürüşdürün"
"Sonuncu ekrana qayıtmaq üçün ekranın sol, yaxud sağ kənarından mərkəzinə doğru sürüşdürün."
- "Ekranın aşağı kənarından yuxarı sürüşdürdüyünüzə əmin olun."
+ "Ekranın ən kənar aşağısından yuxarı sürüşdürün."
"Buraxmazdan əvvəl durdurmadığınıza əmin olun."
- "Birbaşa yuxarı sürüşdürdüyünüzə əmin olun."
- "Əsas səhifəyə keçmə jestini tamamladınız. Sonra tətbiqləri keçirməyi öyrənin."
+ "Birbaşa yuxarı sürüşdürün."
+ "Əsas səhifəyə keçmə jestini tamamladınız. Sonra geri qayıtmağı öyrənin."
+ "Əsas səhifəyə keçmə jestini tamamladınız."
"Əsas səhifəyə keçmək üçün sürüşdürün"
"Ekranın aşağısından yuxarısına sürüşdürün. Bu jest həmişə Əsas səhifəyə aparır."
- "Ekranın aşağı kənarından yuxarı sürüşdürdüyünüzə əmin olun."
- "Buraxmadan əvvəl pəncərəni daha uzun müddət saxlamağa çalışın."
- "Birbaşa yuxarı sürüşdürdüyünüzə, sonra durdurduğunuza əmin olun."
- "Tətbiqləri keçirmə jestini tamamladınız. Telefonunuzu istifadə etməyə hazırsınız!"
+ "Ekranın ən kənar aşağısından yuxarı sürüşdürün."
+ "Barmağı buraxmadan öncə displeydə bir müddət saxlayın."
+ "Sürüşdürüb ekranın yuxarı kənarında saxlayın."
+ "Jestlərdən istifadə qaydasını öyrəndiniz. Jestləri deaktiv etmək üçün Ayarlara keçin."
+ "Tətbiqləri keçirmə jestini tamamladınız."
"Tətbiqi keçirmək üçün sürüşdürün"
- "Ekranın aşağısından yuxarı doğru sürüşdürüb saxlayın, sonra buraxın."
+ "Tətbiqlər arasında keçid üçün ekranın aşağısından yuxarı doğru sürüşdürüb saxlayın, sonra buraxın."
"Tam hazır"
- "Sonra"
- "Oldu"
+ "Oldu"
"Ayarlar"
"Yenə sınayın"
"Əla!"
"Dərslik %1$d/%2$d"
+ "Hər şey hazırdır!"
+ "Əsas səhifəyə keçmək üçün yuxarı çəkin"
+ "Telefondan istifadəyə başlamağa hazırsınız"
+ "Sistem naviqasiya ayarları"
"Paylaşın"
- "Ekran şəkli"
+ "Skrinşot"
+ "Ayırın"
+ "Bölmə ekranını istifadə etmək üçün başqa tətbiqə toxunun"
+ "Tətbiq ekran bölünməsini dəstəkləmir."
"Bu əməliyyata tətbiq və ya təşkilatınız tərəfindən icazə verilmir"
"Naviqasiya dərsliyi ötürülsün?"
"Bunu sonra %1$s tətbiqində tapa bilərsiniz"
"Ləğv edin"
"Ötürün"
+ "Ekranı fırladın"
+ "Tapşırıq panelindəki təlim bölməsi görünür"
+ "Tapşırıq panelindəki təlim bölməsi bağlanıb"
+ "Tətbiqləri keçirmək üçün tapşırıq panelindən istifadə edin"
+ "Eyni anda iki tətbiqi istifadə etmək üçün yan tərəfə çəkin"
+ "Tapşırıq panelini toxunub saxlamaqla gizlədin"
+ "Sonra"
+ "Geri"
+ "Bağlayın"
+ "Hazırdır"
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index 21295fc31a..df49188f93 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -25,11 +25,12 @@
"Podešavanja korišćenja aplikacije"
"Obriši sve"
"Nedavne aplikacije"
+
+
"%1$s, %2$s"
"< 1 min"
"Još %1$s danas"
"Predlozi aplikacija"
- "Sve aplikacije"
"Predviđene aplikacije"
"Dobijajte predloge aplikacija u donjem redu početnog ekrana"
"Dobijajte predloge aplikacija u redu sa omiljenim stavkama na početnom ekranu"
@@ -76,9 +77,22 @@
"Vodič %1$d/%2$d"
"Deli"
"Snimak ekrana"
+ "Podeli"
+ "Dodirnite drugu aplikaciju za podeljeni ekran"
+ "Aplikacija ne podržava podeljeni ekran."
"Aplikacija ili organizacija ne dozvoljavaju ovu radnju"
"Želite da preskočite vodič za kretanje?"
"Možete da pronađete ovo kasnije u aplikaciji %1$s"
"Otkaži"
"Preskoči"
+ "Rotirajte ekran"
+ "Edukativno okno iz trake zadataka se pojavilo"
+ "Edukativno okno iz trake zadataka je zatvoreno"
+ "Koristite traku zadataka da biste menjali aplikacije"
+ "Prevucite na stranu da koristite dve aplikacije odjednom"
+ "Dodirnite i zadržite za skrivanje trake zadataka"
+ "Dalje"
+ "Nazad"
+ "Zatvori"
+ "Gotovo"
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index d31468c2fd..58c8100d4f 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -25,11 +25,12 @@
"Налады выкарыстання праграмы"
"Ачысціць усё"
"Нядаўнія праграмы"
+
+
"%1$s, %2$s"
"< 1 хв"
"Сёння засталося %1$s"
"Прапановы праграм"
- "Усе праграмы"
"Вашы праграмы з падказак"
"Атрымлівайце прапановы праграм у ніжнім радку на Галоўным экране."
"Атрымлівайце прапановы праграм у пераліку абраных на Галоўным экране"
@@ -76,9 +77,22 @@
"Дапаможнік %1$d/%2$d"
"Абагуліць"
"Здымак экрана"
+ "Падзелены экран"
+ "Для падзеленага экрана націсніце на іншую праграму"
+ "Праграма не падтрымлівае рэжым падзеленага экрана."
"Гэта дзеянне не дазволена праграмай ці вашай арганізацыяй"
"Прапусціць дапаможнік па навігацыі?"
"Знайсці дапаможнік можна ў праграме \"%1$s\""
"Скасаваць"
"Прапусціць"
+ "Павярнуць экран"
+ "З\'явілася панэль навучання на панэлі задач"
+ "Панэль навучання на панэлі задач закрыта"
+ "Выкарыстоўвайце панэль задач для пераключэння праграм"
+ "Перацягніце ўбок, каб адначасова скарыстаць дзве праграмы"
+ "Націсніце і ўтрымлівайце, каб схаваць панэль задач"
+ "Далей"
+ "Назад"
+ "Закрыць"
+ "Гатова"
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index d2554d5809..358ca85146 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -25,11 +25,12 @@
"Настройки за използването на приложенията"
"Изчистване на всички"
"Скорошни приложения"
+
+
"%1$s, %2$s"
"< 1 мин"
"Оставащо време днес: %1$s"
"Предложения за приложения"
- "Всички приложения"
"Предвидени приложения"
"Получавайте предложения за приложения на най-долния ред на началния си екран"
"Получаване на предложения за приложения в реда с любими на началния екран"
@@ -76,9 +77,22 @@
"Урок %1$d/%2$d"
"Споделяне"
"Екранна снимка"
+ "Разделяне на екрана"
+ "Докоснете друго прил., за да ползвате разд. екран"
+ "Приложението не поддържа разделен екран."
"Това действие не е разрешено от приложението или организацията ви"
"Пропускане на урока за навигиране?"
"Урокът е налице в приложението %1$s"
"Отказ"
"Пропускане"
+ "Завъртане на екрана"
+ "Показва се урокът за лентата на задачите"
+ "Урокът за лентата на задачите бе затворен"
+ "Използвайте лентата на задачите за превключване между прил."
+ "Плъзнете встрани, за да използвате едновременно 2 приложения"
+ "Докоснете и задръжте, за да скриете лентата на задачите"
+ "Напред"
+ "Назад"
+ "Затваряне"
+ "Готово"
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index 48d26905c0..1f60299fd7 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -25,11 +25,12 @@
"অ্যাপ ব্যবহারের সেটিংস"
"সবকিছু খালি করুন"
"সম্প্রতি ব্যবহৃত অ্যাপ"
+
+
"%1$s, %2$s"
"< ১ মি."
"আজকে %1$s বাকি আছে"
"অ্যাপের সাজেশন"
- "সব অ্যাপ"
"আপনার প্রয়োজন হতে পারে এমন অ্যাপ"
"আপনার হোম স্ক্রিনের নিচে সারিতে অ্যাপ সাজেশন পান"
"হোম স্ক্রিনের \'ফেভারিট রো\' বিকল্পের জন্য অ্যাপ সাজেশন পান"
@@ -76,9 +77,22 @@
"টিউটোরিয়াল %1$d/%2$d"
"শেয়ার করুন"
"স্ক্রিনশট নিন"
+ "স্প্লিট"
+ "স্প্লিটস্ক্রিন ব্যবহার করতে অন্য অ্যাপে ট্যাপ করুন"
+ "স্প্লিট-স্ক্রিনে এই অ্যাপ কাজ করে না।"
"এই অ্যাপ বা আপনার প্রতিষ্ঠান এই অ্যাকশনটি পারফর্ম করার অনুমতি দেয়নি"
"নেভিগেশন টিউটোরিয়াল এড়িয়ে যেতে চান?"
"আপনি %1$s অ্যাপে পরে এটি খুঁজে পাবেন"
"বাতিল করুন"
"এড়িয়ে যান"
+ "স্ক্রিন ঘোরান"
+ "টাস্কবার এডুকেশন দেখানো হয়েছে"
+ "টাস্কবার এডুকেশন বন্ধ করা আছে"
+ "অ্যাপ পাল্টানোর জন্য টাস্কবার ব্যবহার করুন"
+ "একসাথে দুটি অ্যাপ ব্যবহার করতে পাশে টেনে আনুন"
+ "টাস্কবার লুকানোর জন্য টাচ করে ধরে থাকুন"
+ "পরবর্তী"
+ "ফিরুন"
+ "বন্ধ করুন"
+ "হয়ে গেছে"
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 18494a9c19..993b36f189 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -25,11 +25,12 @@
"Postavke korištenja aplikacije"
"Obriši sve"
"Nedavne aplikacije"
+
+
"%1$s, %2$s"
"< 1 min"
"Preostalo vrijeme: %1$s"
"Prijedlozi aplikacija"
- "Sve aplikacije"
"Predviđene aplikacije"
"Primajte prijedloge aplikacija u donjem redu početnog ekrana"
"Primajte prijedloge aplikacija u redu omiljenih stavki početnog ekrana"
@@ -76,9 +77,22 @@
"Vodič %1$d/%2$d"
"Dijeli"
"Snimak ekrana"
+ "Podijeli"
+ "Dodirnite drugu apl. da koristite podijeljeni ekran"
+ "Aplikacija ne podržava podijeljeni ekran."
"Ovu radnju ne dozvoljava aplikacija ili vaša organizacija"
"Preskočiti vodič za navigiranje?"
"To možete pronaći kasnije u aplikaciji %1$s"
"Otkaži"
"Preskoči"
+ "Rotiranje ekrana"
+ "Edukacija o programskoj traci je prikazana"
+ "Edukacija o programskoj traci je zatvorena"
+ "Koristite programsku traku da promijenite aplikacije"
+ "Prevucite u stranu da istovremeno koristite dvije aplikacije"
+ "Dodirnite i držite da sakrijete programsku traku"
+ "Naprijed"
+ "Nazad"
+ "Zatvori"
+ "Gotovo"
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index 7c417effa0..aae94bb524 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -25,11 +25,12 @@
"Configuració d\'ús d\'aplicacions"
"Esborra-ho tot"
"Aplicacions recents"
+
+
"%1$s; %2$s"
"< 1 minut"
"temps restant avui: %1$s"
"Suggeriments d\'aplicacions"
- "Totes les aplicacions"
"Prediccions d\'aplicacions"
"Obtén suggeriments d\'aplicacions a la fila inferior de la pantalla d\'inici"
"Obtén suggeriments d\'aplicacions a la fila Preferides de la teva pantalla d\'inici"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Comparteix"
"Captura de pantalla"
+ "Divideix"
+ "Toca una altra aplicació per dividir la pantalla"
+ "L\'aplicació no admet la pantalla dividida."
"L\'aplicació o la teva organització no permeten aquesta acció"
"Vols ometre el tutorial de navegació?"
"Pots trobar-ho més tard a l\'aplicació %1$s"
"Cancel·la"
"Omet"
+ "Gira la pantalla"
+ "Ha aparegut el tauler educatiu de la barra de tasques"
+ "S\'ha tancat el tauler educatiu de la barra de tasques"
+ "Utilitza la barra de tasques per canviar d\'aplicació"
+ "Arrossega al costat per utilitzar dues aplicacions alhora"
+ "Mantén premut per amagar la barra de tasques"
+ "Següent"
+ "Enrere"
+ "Tanca"
+ "Fet"
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 9d1b94e9cd..40ec9a7e06 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -25,11 +25,12 @@
"Nastavení využití aplikací"
"Vymazat vše"
"Poslední aplikace"
+
+
"%1$s, %2$s"
"< 1 minuta"
"dnes zbývá: %1$s"
"Návrhy aplikací"
- "Všechny aplikace"
"Návrhy aplikací pro vás"
"Nechte si ve spodním řádku na ploše zobrazovat návrhy aplikací"
"Nechte si na řádku oblíbených na ploše zobrazovat návrhy aplikací"
@@ -76,9 +77,22 @@
"Výukový program %1$d/%2$d"
"Sdílet"
"Snímek obrazovky"
+ "Rozdělit"
+ "Klepnutím na jinou aplikaci rozdělíte obrazovku"
+ "Aplikace nepodporuje režim rozdělené obrazovky."
"Aplikace nebo organizace zakazuje tuto akci"
"Přeskočit výukový program k navigaci?"
"Program později najdete v aplikaci %1$s"
"Zrušit"
"Přeskočit"
+ "Otočit obrazovku"
+ "Zobrazila se výuka k hlavnímu panelu"
+ "Výuka k hlavnímu panelu byla zavřena"
+ "Aplikace lze přepínat pomocí hlavního panelu"
+ "Po přetažení na stranu lze používat dvě aplikace současně"
+ "Hlavní panel můžete skrýt podržením"
+ "Další"
+ "Zpět"
+ "Zavřít"
+ "Hotovo"
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index 42098dc036..f2931e937c 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -25,11 +25,12 @@
"Indstillinger for appforbrug"
"Ryd alt"
"Seneste apps"
+
+
"%1$s, %2$s"
"< 1 min"
"%1$s tilbage i dag"
"Appforslag"
- "Alle apps"
"Dine foreslåede apps"
"Få appforslag på den nederste række af din startskærm"
"Få appforslag i rækken med favoritter på din startskærm"
@@ -76,9 +77,22 @@
"Selvstudie %1$d/%2$d"
"Del"
"Screenshot"
+ "Opdel"
+ "Tryk på en anden app for at bruge opdelt skærm"
+ "Appen understøtter ikke opdelt skærm."
"Appen eller din organisation tillader ikke denne handling"
"Vil du springe selvstudiet for navigation over?"
"Du kan finde dette senere i appen %1$s"
"Annuller"
"Spring over"
+ "Roter skærm"
+ "Vejledningen om proceslinjen blev åbnet"
+ "Vejledningen om proceslinjen blev lukket"
+ "Skift mellem apps ved hjælp af proceslinjen"
+ "Træk til siden for at bruge to apps samtidig"
+ "Du kan skjule proceslinjen ved at holde fingeren nede"
+ "Næste"
+ "Tilbage"
+ "Luk"
+ "Luk"
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index e7d9523c14..1faa1413a3 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -24,12 +24,13 @@
"Keine kürzlich verwendeten Elemente"
"Einstellungen zur App-Nutzung"
"Alle Apps schließen"
- "Zuletzt aktive Apps"
+ "Kürzlich geöffnete Apps"
+
+
"%1$s, %2$s"
"< 1 Min."
"Heute noch %1$s"
"App-Vorschläge"
- "Alle Apps"
"App-Vorschläge für dich"
"Lass dir in der unteren Reihe auf deinem Startbildschirm Vorschläge für Apps anzeigen"
"Lass dir in der Favoritenleiste auf dem Startbildschirm App-Vorschläge anzeigen"
@@ -42,43 +43,59 @@
"Hier erscheinen die meistverwendeten Apps. Die Angaben können sich je nach deiner gewöhnlichen Nutzung ändern"
"Ziehe Apps aus der unteren Reihe heraus, um Vorschläge für Apps zu erhalten"
"App-Vorschläge in freiem Bereich hinzugefügt"
- "Funktion \"App-Vorschläge\" aktiviert"
+ "Funktion „App-Vorschläge“ aktiviert"
"Funktion \"App-Vorschläge\" deaktiviert"
"Vorgeschlagene App: %1$s"
- "Wische vom äußersten linken Bildschirmrand."
- "Wische vom linken Bildschirmrand zur Bildschirmmitte und lass los."
- "Gut gemacht. Versuch es jetzt mal vom rechten Rand aus."
- "Wische vom äußersten rechten Bildschirmrand."
- "Wische vom rechten Bildschirmrand zur Bildschirmmitte und lass los."
- "Du hast die „Zurück“-Touch-Geste abgeschlossen. Nun lernst du, wie du zum Startbildschirm gelangst."
- "Wische nicht zu nah am unteren Bildschirmrand."
+ "Wische vom äußersten rechten oder linken Displayrand."
+ "Wische vom rechten oder linken Displayrand zur Displaymitte und lass los."
+ "Du kannst jetzt vom rechten Displayrand aus wischen, um zurückzugehen. Gleich erfährst du, wie man zwischen Apps wechselt."
+ "Du hast die „Zurück“-Touch-Geste abgeschlossen."
+ "Wische nicht zu nah am unteren Displayrand."
"Du kannst die Empfindlichkeit von „Zurück“ in den Einstellungen ändern"
"Zum Zurückgehen wischen"
"Wenn du zum letzten Bildschirm zurückgehen möchtest, wische vom linken oder rechten Rand zur Mitte."
- "Wische vom unteren Bildschirmrand nach oben."
+ "Wische vom unteren Displayrand nach oben."
"Achte darauf, nicht innezuhalten, bevor du loslässt."
"Wische gerade nach oben."
- "Du hast die „Startbildschirm“-Touch-Geste abgeschlossen. Als Nächstes lernst du, wie du zwischen Apps wechseln kannst."
+ "Du hast die „Startbildschirm“-Touch-Geste abgeschlossen. Gleich erfährst du, wie du zurückgelangst."
+ "Du hast die „Startbildschirm“-Touch-Geste abgeschlossen."
"Zum Startbildschirm gehen"
- "Wenn du zum Startbildschirm gehen möchtest, wische einfach vom unteren Bildschirmrand nach oben."
- "Wische vom unteren Bildschirmrand nach oben."
+ "Wenn du zum Startbildschirm gehen möchtest, wische einfach vom unteren Displayrand nach oben."
+ "Wische vom unteren Displayrand nach oben."
"Versuche, das Fenster länger festzuhalten, bevor du es loslässt."
"Wische gerade nach oben und halte dann inne."
- "Du hast die „Apps wechseln“-Touch-Geste abgeschlossen. Nun bist du bereit, dein Smartphone zu verwenden!"
+ "Nun weißt du, wie Touch-Gesten funktionieren. Du kannst sie in den Einstellungen deaktivieren."
+ "Du hast die „Apps wechseln“-Touch-Geste abgeschlossen."
"Zwischen Apps wechseln"
- "Wische auf dem Bildschirm von unten nach oben, halte ihn gedrückt und lass ihn dann los."
+ "Wische auf dem Display von unten nach oben und lass dann los, um zwischen Apps zu wechseln."
"Fertig"
- "Weiter"
- "Fertig"
+ "Fertig"
"Einstellungen"
"Wiederholen"
"Sehr gut!"
"Anleitung %1$d/%2$d"
+ "Fertig!"
+ "Nach oben wischen, um den Startbildschirm aufzurufen"
+ "Du kannst dein Smartphone jetzt verwenden"
+ "Einstellungen der Systemsteuerung"
"Teilen"
"Screenshot"
+ "Teilen"
+ "Für „Bildschirm teilen“ auf weitere App tippen"
+ "„Bildschirm teilen“ wird von der App nicht unterstützt."
"Die App oder deine Organisation lässt diese Aktion nicht zu"
"Navigationstutorial überspringen?"
- "Das findest du später in der %1$s App"
+ "Du findest es später auch in der %1$s App"
"Abbrechen"
"Überspringen"
+ "Bildschirm drehen"
+ "Anleitung für Taskleiste eingeblendet"
+ "Anleitung für Taskleiste geschlossen"
+ "Über die Taskleiste zwischen Apps wechseln"
+ "Zur Seite ziehen, um zwei Apps gleichzeitig zu verwenden"
+ "Gedrückt halten, um die Taskleiste auszublenden"
+ "Weiter"
+ "Zurück"
+ "Schließen"
+ "Fertig"
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index 21c29844f5..70b5b96faa 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -25,11 +25,12 @@
"Ρυθμίσεις χρήσης εφαρμογής"
"Διαγραφή όλων"
"Πρόσφατες εφαρμογές"
+
+
"%1$s, %2$s"
"< 1 λ."
"Απομένουν %1$s σήμερα"
"Προτεινόμενες εφαρμογές"
- "Όλες οι εφαρμογές"
"Προβλέψεις εφαρμογών"
"Δείτε τις προτεινόμενες εφαρμογές στην κάτω σειρά της αρχικής οθόνης"
"Δείτε τις προτεινόμενες εφαρμογές στη σειρά Αγαπημένα της αρχικής οθόνης."
@@ -76,9 +77,22 @@
"Οδηγός %1$d/%2$d"
"Κοινοποίηση"
"Στιγμιότυπο οθόνης"
+ "Διαχωρισμός"
+ "Πατήστε άλλη εφαρμογή για χρήση διαχωρισμού οθόνης"
+ "Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."
"Αυτή η ενέργεια δεν επιτρέπεται από την εφαρμογή ή τον οργανισμό σας."
"Παράβλεψη οδηγού πλοήγησης;"
"Βρείτε τον αργότερα στην εφαρμογή %1$s"
"Ακύρωση"
"Παράβλεψη"
+ "Περιστροφή οθόνης"
+ "Η εκπαίδευση για τη γραμμή εργασιών εμφανίστηκε"
+ "Η εκπαίδευση για τη γραμμή εργασιών έκλεισε"
+ "Χρήση της γραμμής εργασιών για εναλλαγή εφαρμογών"
+ "Σύρετε στο πλάι για ταυτόχρονη χρήση δύο εφαρμογών"
+ "Αγγίξτε παρατεταμένα για απόκρυψη της γραμμής εργασιών."
+ "Επόμενο"
+ "Πίσω"
+ "Κλείσιμο"
+ "Τέλος"
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index 487d6ccd77..a085ab9bb2 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -25,11 +25,12 @@
"App usage settings"
"Clear all"
"Recent apps"
+
+
"%1$s, %2$s"
"< 1 minute"
"%1$s left today"
"App suggestions"
- "All apps"
"Your predicted apps"
"Get app suggestions on the bottom row of your home screen"
"Get app suggestions on the favourites row of your home screen"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Share"
"Screenshot"
+ "Split"
+ "Tap another app to use split-screen"
+ "App does not support split-screen."
"This action isn\'t allowed by the app or your organisation"
"Skip navigation tutorial?"
"You can find this later in the %1$s app"
"Cancel"
"Skip"
+ "Rotate screen"
+ "Taskbar education appeared"
+ "Taskbar education closed"
+ "Use the taskbar to switch apps"
+ "Drag to the side to use two apps at once"
+ "Touch & hold to hide the taskbar"
+ "Next"
+ "Back"
+ "Close"
+ "Done"
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index 487d6ccd77..a085ab9bb2 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -25,11 +25,12 @@
"App usage settings"
"Clear all"
"Recent apps"
+
+
"%1$s, %2$s"
"< 1 minute"
"%1$s left today"
"App suggestions"
- "All apps"
"Your predicted apps"
"Get app suggestions on the bottom row of your home screen"
"Get app suggestions on the favourites row of your home screen"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Share"
"Screenshot"
+ "Split"
+ "Tap another app to use split-screen"
+ "App does not support split-screen."
"This action isn\'t allowed by the app or your organisation"
"Skip navigation tutorial?"
"You can find this later in the %1$s app"
"Cancel"
"Skip"
+ "Rotate screen"
+ "Taskbar education appeared"
+ "Taskbar education closed"
+ "Use the taskbar to switch apps"
+ "Drag to the side to use two apps at once"
+ "Touch & hold to hide the taskbar"
+ "Next"
+ "Back"
+ "Close"
+ "Done"
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index 487d6ccd77..a085ab9bb2 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -25,11 +25,12 @@
"App usage settings"
"Clear all"
"Recent apps"
+
+
"%1$s, %2$s"
"< 1 minute"
"%1$s left today"
"App suggestions"
- "All apps"
"Your predicted apps"
"Get app suggestions on the bottom row of your home screen"
"Get app suggestions on the favourites row of your home screen"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Share"
"Screenshot"
+ "Split"
+ "Tap another app to use split-screen"
+ "App does not support split-screen."
"This action isn\'t allowed by the app or your organisation"
"Skip navigation tutorial?"
"You can find this later in the %1$s app"
"Cancel"
"Skip"
+ "Rotate screen"
+ "Taskbar education appeared"
+ "Taskbar education closed"
+ "Use the taskbar to switch apps"
+ "Drag to the side to use two apps at once"
+ "Touch & hold to hide the taskbar"
+ "Next"
+ "Back"
+ "Close"
+ "Done"
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index 487d6ccd77..a085ab9bb2 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -25,11 +25,12 @@
"App usage settings"
"Clear all"
"Recent apps"
+
+
"%1$s, %2$s"
"< 1 minute"
"%1$s left today"
"App suggestions"
- "All apps"
"Your predicted apps"
"Get app suggestions on the bottom row of your home screen"
"Get app suggestions on the favourites row of your home screen"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Share"
"Screenshot"
+ "Split"
+ "Tap another app to use split-screen"
+ "App does not support split-screen."
"This action isn\'t allowed by the app or your organisation"
"Skip navigation tutorial?"
"You can find this later in the %1$s app"
"Cancel"
"Skip"
+ "Rotate screen"
+ "Taskbar education appeared"
+ "Taskbar education closed"
+ "Use the taskbar to switch apps"
+ "Drag to the side to use two apps at once"
+ "Touch & hold to hide the taskbar"
+ "Next"
+ "Back"
+ "Close"
+ "Done"
diff --git a/quickstep/res/values-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml
index 36df9d1523..6d8d30755d 100644
--- a/quickstep/res/values-en-rXC/strings.xml
+++ b/quickstep/res/values-en-rXC/strings.xml
@@ -25,11 +25,12 @@
"App usage settings"
"Clear all"
"Recent apps"
+
+
"%1$s, %2$s"
"< 1 minute"
"%1$s left today"
"App suggestions"
- "All apps"
"Your predicted apps"
"Get app suggestions on the bottom row of your Home screen"
"Get app suggestions on favorites row of your Home screen"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Share"
"Screenshot"
+ "Split"
+ "Tap another app to use splitscreen"
+ "App does not support split-screen."
"This action isn\'t allowed by the app or your organization"
"Skip navigation tutorial?"
"You can find this later in the %1$s app"
"Cancel"
"Skip"
+ "Rotate screen"
+ "Taskbar education appeared"
+ "Taskbar education closed"
+ "Use the taskbar to switch apps"
+ "Drag to the side to use two apps at once"
+ "Touch & hold to hide the taskbar"
+ "Next"
+ "Back"
+ "Close"
+ "Done"
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 478d1cdb1e..f60f4006d8 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -25,11 +25,12 @@
"Configuración de uso de la app"
"Borrar todo"
"Apps recientes"
+
+
"%1$s (%2$s)"
"< 1 minuto"
"Tiempo restante: %1$s"
"Sugerencias de aplicaciones"
- "Todas las apps"
"Predicción de tus apps"
"Obtén sugerencias de aplicaciones en la fila inferior de la pantalla principal"
"Obtén sugerencias de apps en la fila de favoritos de la pantalla principal"
@@ -76,9 +77,22 @@
"Instructivo %1$d/%2$d"
"Compartir"
"Captura de pantalla"
+ "Pantalla dividida"
+ "Presiona otra app para usar la pantalla dividida"
+ "La app no es compatible con la función de pantalla dividida."
"La app o tu organización no permiten realizar esta acción"
"¿Omitir el instructivo de navegación?"
"Puedes encontrarlo en la app de %1$s"
"Cancelar"
"Omitir"
+ "Girar pantalla"
+ "Se abrió la barra de herramientas Educación"
+ "Se cerró la barra de herramientas Educación"
+ "Usa la barra de tareas para cambiar de app"
+ "Arrastra a un lado para usar dos apps a la vez"
+ "Mantén presionado para ocultar la barra de tareas"
+ "Siguiente"
+ "Atrás"
+ "Cerrar"
+ "Listo"
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 454bea7784..32f8fcc62c 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -25,11 +25,12 @@
"Ajustes de uso de la aplicación"
"Borrar todo"
"Aplicaciones recientes"
+
+
"%1$s (%2$s)"
"<1 minuto"
"tiempo restante: %1$s"
"Sugerencias de aplicaciones"
- "Todas las aplicaciones"
"Predicción de aplicaciones"
"Obtén sugerencias de aplicaciones en la fila inferior de la pantalla de inicio"
"Recibe sugerencias de aplicaciones en la fila de aplicaciones favoritas de la pantalla de inicio"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Compartir"
"Hacer captura"
+ "Dividir"
+ "Toca otra aplicación para usar la pantalla dividida"
+ "La aplicación no admite la pantalla dividida."
"No puedes hacerlo porque la aplicación o tu organización no lo permiten"
"¿Saltar tutorial de navegación?"
"Puedes consultarlo en otro momento en la aplicación %1$s"
"Cancelar"
"Saltar"
+ "Girar la pantalla"
+ "Ha aparecido una nota sobre la barra de tareas"
+ "Nota sobre la barra de tareas cerrada"
+ "Usa la barra de tareas para cambiar de aplicación"
+ "Arrastra hacia un lado para usar dos aplicaciones a la vez"
+ "Mantén pulsada la barra de tareas para ocultarla"
+ "Siguiente"
+ "Atrás"
+ "Cerrar"
+ "Hecho"
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index b7299345ab..92f9be0fe6 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -25,11 +25,12 @@
"Rakenduse kasutuse seaded"
"Sule kõik"
"Hiljutised rakendused"
+
+
"%1$s %2$s"
"< 1 minut"
"Tääna jäänud %1$s"
"Rakenduste soovitused"
- "Kõik rakendused"
"Teie ennustatud rakendused"
"Hankige avakuva alumisel real rakenduste soovitusi"
"Hankige avakuva lemmikute reale rakenduste soovitusi"
@@ -76,9 +77,22 @@
"Õpetus %1$d/%2$d"
"Jaga"
"Ekraanipilt"
+ "Eralda"
+ "Jagatud kuva kasutamiseks puudutage muud rakendust"
+ "Rakendus ei toeta jagatud ekraani."
"Rakendus või teie organisatsioon on selle toimingu keelanud"
"Kas jätta navigeerimise õpetused vahele?"
"Leiate selle hiljem rakendusest %1$s"
"Tühista"
"Jäta vahele"
+ "Pöörake ekraani"
+ "Tegumiriba juhised kuvati"
+ "Tegumiriba juhised on suletud"
+ "Kasutage rakenduste vahetamiseks tegumiriba"
+ "Kahe rakenduse korraga kasutamiseks lohistage külje poole"
+ "Tegumiriba peitmiseks puudutage pikalt"
+ "Järgmine"
+ "Tagasi"
+ "Sule"
+ "Valmis"
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index f6d3f235c4..3d04fcd52b 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -25,11 +25,12 @@
"Aplikazioen erabileraren ezarpenak"
"Garbitu guztiak"
"Azken aplikazioak"
+
+
"%1$s (%2$s)"
"< 1 min"
"%1$s gelditzen dira gaur"
"Aplikazioen iradokizunak"
- "Aplikazio guztiak"
"Iradokitako aplikazioak"
"Jaso aplikazioen iradokizunak hasierako pantailaren beheko errenkadan"
"Jaso aplikazioen iradokizunak hasierako pantailako gogokoen errenkadan"
@@ -37,7 +38,7 @@
"Atzitu erraz aplikazio erabilienak hasierako pantailatik bertatik. Ohituren arabera aldatuko dira iradokizunak. Gogokoen errenkadako aplikazioak hasierako pantailara eramango ditugu."
"Atzitu erraz aplikazio erabilienak hasierako pantailatik bertatik. Ohituren arabera aldatuko dira iradokizunak. Karpeta berri batera eramango dira beheko errenkadan dauden aplikazioak."
"Jaso aplikazioen iradokizunak"
- "Ez"
+ "Ez, eskerrik asko"
"Ezarpenak"
"Hemen agertzen dira aplikazio erabilienak, eta ohituren arabera aldatzen dira"
"Arrastatu aplikazioak beheko errenkadatik aplikazioen iradokizunak jasotzeko"
@@ -76,9 +77,22 @@
"Tutoriala: %1$d/%2$d"
"Partekatu"
"Atera pantaila-argazki bat"
+ "Zatitu"
+ "Sakatu beste aplikazio bat pantaila zatitzeko"
+ "Aplikazioak ez du onartzen pantaila zatitua."
"Aplikazioak edo erakundeak ez du eman ekintza hori gauzatzeko baimena"
"Nabigazio-tutoriala saltatu nahi duzu?"
"Tutorial hau %1$s aplikazioan aurki dezakezu geroago"
"Utzi"
"Saltatu"
+ "Biratu pantaila"
+ "Agertu egin da zereginen barraren tutoriala"
+ "Itxi egin da zereginen barraren tutoriala"
+ "Erabili zereginen barra aplikazioz aldatzeko"
+ "Bi aplikazio batera erabiltzeko, arrastatu hatza albo batera"
+ "Zereginen barra ezkutatzeko, eduki ezazu sakatuta"
+ "Hurrengoa"
+ "Atzera"
+ "Itxi"
+ "Eginda"
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index 8ca0f95ec9..abbb5e95dd 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -25,11 +25,12 @@
"تنظیمات استفاده از برنامه"
"پاک کردن همه"
"برنامههای اخیر"
+
+
"%1$s، %2$s"
"< ۱ دقیقه"
"%1$s باقیمانده برای امروز"
"پیشنهادهای برنامه"
- "همه برنامهها"
"برنامههای پیشبینیشده"
"دریافت پیشنهادهای برنامه در ردیف پایین صفحه اصلی"
"دریافت «پیشنهاد برنامه» در ردیف موارد دلخواه صفحه اصلی"
@@ -76,9 +77,22 @@
"آموزش گامبهگام %1$d/%2$d"
"همرسانی"
"نماگرفت"
+ "دونیمه"
+ "برای استفاده از صفحهٔ دونیمه، روی برنامه دیگری ضربه بزنید"
+ "برنامه از صفحهٔ دونیمه پشتیبانی نمیکند."
"برنامه یا سازمان شما اجازه نمیدهد این کنش انجام شود."
"آموزش گامبهگام پیمایش رد شود؟"
"میتوانید آن را بعداً در برنامه %1$s پیدا کنید"
"لغو"
"رد شدن"
+ "چرخاندن صفحه"
+ "پانل آموزشی نوار وظیفه نمایان شد"
+ "پانل آموزشی نوار وظیفه بسته شد"
+ "برای جابهجایی بین برنامهها، از نوار وظیفه استفاده کنید"
+ "برای استفاده همزمان از دو برنامه، آن را به کنار بکشید"
+ "برای پنهان کردن نوار وظیفه، لمس کنید و نگه دارید"
+ "بعدی"
+ "برگشت"
+ "بستن"
+ "تمام"
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 9ce7e6b4f2..57446a7ba3 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -25,11 +25,12 @@
"Sovelluksen käyttöasetukset"
"Poista kaikki"
"Viimeisimmät sovellukset"
+
+
"%1$s, %2$s"
"< 1 min"
"%1$s jäljellä tänään"
"Sovellusehdotukset"
- "Kaikki sovellukset"
"Sovellusennusteet"
"Näytä sovellusehdotuksia aloitusnäytön alimmaisella rivillä"
"Näytä sovellusehdotuksia aloitusnäytön Suosikit-rivillä"
@@ -76,9 +77,22 @@
"Ohje %1$d/%2$d"
"Jaa"
"Kuvakaappaus"
+ "Jaa"
+ "Avaa jaettu näyttö napauttamalla toista sovellusta"
+ "Sovellus ei tue jaetun näytön tilaa."
"Sovellus tai organisaatio ei salli tätä toimintoa"
"Ohitetaanko navigointiohje?"
"Löydät tämän myöhemmin sovelluksesta: %1$s"
"Peru"
"Ohita"
+ "Käännä näyttö"
+ "Tehtäväpalkin ohje näkyvissä"
+ "Tehtäväpalkin ohje suljettu"
+ "Vaihda sovellusta tehtäväpalkin kautta"
+ "Vetämällä sivuun voit käyttää kahta sovellusta samaan aikaan"
+ "Piilota tehtäväpalkki koskettamalla pitkään"
+ "Seuraava"
+ "Takaisin"
+ "Sulje"
+ "Valmis"
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index da738495c7..67433da4f7 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -25,11 +25,12 @@
"Paramètres d\'utilisation de l\'application"
"Tout effacer"
"Applications récentes"
+
+
"%1$s : %2$s"
"< 1 min"
"Il reste %1$s aujourd\'hui"
"Suggestions d\'applications"
- "Toutes les applications"
"Vos prédictions d\'applications"
"Obtenir des suggestions d\'applications dans la rangée du bas de votre écran d\'accueil"
"Retrouvez des suggestions d\'applications dans la rangée des favoris de votre écran d\'accueil"
@@ -76,9 +77,22 @@
"Étape %1$d sur %2$d du tutoriel"
"Partager"
"Capture d\'écran"
+ "Séparé"
+ "Touchez une autre appli pour partager l\'écran"
+ "L\'appli n\'est pas compatible avec l\'écran partagé."
"L\'application ou votre organisation n\'autorise pas cette action"
"Ignorer le tutoriel sur la navigation?"
"Vous trouverez le tutoriel dans l\'application %1$s"
"Annuler"
"Ignorer"
+ "Faire pivoter l\'écran"
+ "La barre des tâches éducatives s\'est affichée"
+ "La barre des tâches éducatives est fermée"
+ "Utilisez la barre des tâches pour changer les applications"
+ "Glissez sur le côté pour utiliser 2 applications à la fois"
+ "Maintenez le doigt sur la barre des tâches pour la masquer"
+ "Suivant"
+ "Retour"
+ "Fermer"
+ "OK"
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index b6abea7bc9..75a7ac1628 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -25,11 +25,12 @@
"Paramètres de consommation de l\'application"
"Tout effacer"
"Applications récentes"
+
+
"%1$s, %2$s"
"< 1 min"
"Encore %1$s aujourd\'hui"
"Applications suggérées"
- "Toutes les applications"
"Applications prévues pour vous"
"Retrouvez vos applications favorites au bas de votre écran d\'accueil"
"Retrouvez des suggestions d\'applications dans la zone des favoris de votre écran d\'accueil"
@@ -76,9 +77,22 @@
"Tutoriel %1$d sur %2$d"
"Partager"
"Capture d\'écran"
+ "Partager"
+ "Appuyez sur autre appli pour utiliser écran partagé"
+ "Appli incompatible avec l\'écran partagé."
"Cette action n\'est pas autorisée par l\'application ou par votre organisation"
"Ignorer le tutoriel de navigation ?"
"Vous le retrouverez dans l\'appli %1$s"
"Annuler"
"Passer"
+ "Faire pivoter l\'écran"
+ "Infos sur la barre des tâches affichées"
+ "Infos sur la barre des tâches fermées"
+ "Utilisez la barre des tâches pour changer d\'application"
+ "Faites glisser sur côté pour utiliser deux applis à la fois"
+ "Appuyez de manière prolongée pour masquer barre des tâches"
+ "Suivant"
+ "Retour"
+ "Fermer"
+ "OK"
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index 9310d71f7d..c9facc16d5 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -25,11 +25,12 @@
"Configuración do uso de aplicacións"
"Borrar todo"
"Apps recentes"
+
+
"%1$s (%2$s)"
"<1 min"
"Tempo restante hoxe %1$s"
"Suxestións de aplicacións"
- "Todas as aplicacións"
"Previsión das túas aplicacións"
"Recibe suxestións de aplicacións na fila inferior da pantalla de inicio"
"Recibe suxestións de aplicacións na fila de Favoritos da pantalla de inicio"
@@ -76,9 +77,22 @@
"Titorial %1$d/%2$d"
"Compartir"
"Facer captura"
+ "Dividir"
+ "Para usar a pantalla dividida, toca outra app"
+ "A app non admite a función de pantalla dividida."
"A aplicación ou a túa organización non permite realizar esta acción"
"Queres omitir o titorial de navegación?"
"Podes atopar isto máis tarde na aplicación %1$s"
"Cancelar"
"Omitir"
+ "Xira a pantalla"
+ "Panel de información de barra de tarefas aberto"
+ "Panel de información de barra de tarefas pechado"
+ "Usa a barra de ferramentas para cambiar de aplicación"
+ "Para usar dúas aplicacións á vez, arrastra cara ao lado"
+ "Mantén premida a barra de tarefas para ocultala"
+ "Seguinte"
+ "Atrás"
+ "Pechar"
+ "Feito"
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index 496d33b79a..9f325b1fda 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -25,11 +25,12 @@
"ઍપ વપરાશનું સેટિંગ"
"બધું સાફ કરો"
"તાજેતરની ઍપ"
+
+
"%1$s, %2$s"
"< 1 મિનિટ"
"%1$s આજે બાકી"
"ઍપ સૂચનો"
- "બધી ઍપ"
"તમારી પૂર્વાનુમાનિત ઍપ"
"તમારી હોમ સ્ક્રીનની નીચલી પંક્તિમાં ઍપના સુઝાવો મેળવો"
"તમારી હોમ સ્ક્રીનની મનપસંદ પંક્તિમાં ઍપના સુઝાવો મેળવો"
@@ -76,9 +77,22 @@
"ટ્યૂટૉરિઅલ %1$d/%2$d"
"શેર કરો"
"સ્ક્રીનશૉટ"
+ "વિભાજિત કરો"
+ "સ્પલિટસ્ક્રીનના વપરાશ માટે, કોઈ અન્ય ઍપ પર ટૅપ કરો"
+ "ઍપ સ્ક્રીન-વિભાજનને સપોર્ટ કરતી નથી."
"ઍપ કે તમારી સંસ્થા દ્વારા આ ક્રિયા કરવાની મંજૂરી નથી"
"નૅવિગેશન ટ્યૂટૉરિઅલ છોડી દઈએ?"
"તમે આને પછીથી %1$s ઍપમાં જોઈ શકો છો"
"રદ કરો"
"છોડો"
+ "સ્ક્રીન ફેરવો"
+ "ટાસ્કબારનું શિક્ષણ આપતી પૅનલ દેખાય છે"
+ "ટાસ્કબારનું શિક્ષણ આપતી પૅનલ બંધ થઈ છે"
+ "ઍપ સ્વિચ કરવા માટે, ટાસ્કબારનો ઉપયોગ કરો"
+ "એક જ સમયે બે ઍપનો ઉપયોગ કરવા માટે, ખેંચીને બાજુ પર લઈ જાઓ"
+ "ટાસ્કબાર છુપાવવા, તેને ટચ કરીને થોડીવાર દબાવી રાખો"
+ "આગળ"
+ "પાછળ"
+ "બંધ કરો"
+ "થઈ ગયું"
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index a7711fe10c..885a52dc52 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -25,11 +25,12 @@
"ऐप्लिकेशन इस्तेमाल की सेटिंग"
"सभी ऐप्लिकेशन बंद करें"
"हाल ही में इस्तेमाल किए गए ऐप्लिकेशन"
+
+
"%1$s, %2$s"
"<1 मिनट"
"आज %1$s और चलेगा"
"सुझाए गए ऐप्लिकेशन"
- "सभी ऐप्लिकेशन"
"आपके काम के ऐप्लिकेशन"
"अपने होम स्क्रीन की सबसे नीचे वाली पंक्ति में ऐप्लिकेशन के सुझाव पाएं"
"अपने होम स्क्रीन की सबसे नीचे वाली पंक्ति में पसंदीदा ऐप्लिकेशन के सुझाव पाएं"
@@ -45,40 +46,56 @@
"सुझाए गए ऐप्लिकेशन की सुविधा चालू है"
"सुझाए गए ऐप्लिकेशन की सुविधा बंद है"
"सुझाया गया ऐप्लिकेशन: %1$s"
- "देख लें कि आप स्क्रीन की बाईं तरफ़ के बिल्कुल किनारे से स्वाइप कर रहे हों."
- "देख लें कि आप स्क्रीन के बाएं किनारे से बीचों-बीच तक स्वाइप कर रहे हों और फिर अपनी उंगली उठा लें."
- "हो गया! अब, दाएं किनारे से स्वाइप करने की कोशिश करें."
- "देख लें कि आप स्क्रीन की दाईं तरफ़ के बिल्कुल किनारे से स्वाइप कर रहे हों."
- "देख लें कि आप स्क्रीन के दाएं किनारे से बीचों-बीच तक स्वाइप कर रहे हों और फिर अपनी उंगली उठा लें."
- "आपने पीछे जाने के लिए इस्तेमाल होने वाले हाथ के जेस्चर (हाव-भाव) के बारे में जान लिया है. अब, होम स्क्रीन पर जाने का तरीका जानें."
- "देखे लें कि आप स्क्रीन पर बिल्कुल नीचे तक स्वाइप न कर रहे हों."
+ "पक्का करें कि आप स्क्रीन की दाईं या बाईं ओर के बिल्कुल किनारे से स्वाइप कर रहे हों."
+ "स्क्रीन के दाएं या बाएं किनारे से स्क्रीन के बीच तक स्वाइप करें और अपनी उंगली उठा लें."
+ "आपने स्क्रीन के दाएं किनारे से स्वाइप करके, पिछली स्क्रीन पर वापस जाने का तरीका सीख लिया है. अब, एक ऐप से दूसरे ऐप पर जाने का तरीका सीखें."
+ "आपने पेज पर पीछे ले जाने वाले हाथ के जेस्चर (हाव-भाव) के बारे में जान लिया है."
+ "देख लें कि आप स्क्रीन पर बिल्कुल नीचे तक स्वाइप न कर रहे हों."
"\'सेटिंग\' में जाकर, पीछे जाने के लिए इस्तेमाल होने वाले हाथ के जेस्चर (हाव-भाव) की संवेदनशीलता बदलें"
"पिछली स्क्रीन पर वापस जाने के लिए स्वाइप करें"
"पिछली स्क्रीन पर वापस जाने के लिए, स्क्रीन के बाएं या दाएं किनारे से बीचों-बीच तक स्वाइप करें."
"देख लें कि आप स्क्रीन के निचले किनारे से ऊपर की ओर स्वाइप कर रहे हों."
"देख लें कि आप स्क्रीन से अपनी उंगली उठाने से पहले, इसे कहीं न रोक रहे हों."
"देख लें कि आप ऊपर की ओर बिल्कुल सीधे स्वाइप कर रहे हों."
- "आपने होम स्क्रीन पर जाने के लिए इस्तेमाल होने वाले हाथ के जेस्चर (हाव-भाव) के बारे में जान लिया है. अब, एक से दूसरे ऐप्लिकेशन पर जाने का तरीका जानें."
+ "आपने होम स्क्रीन पर ले जाने वाले हाथ के जेस्चर के बारे में जान लिया है. अब, वापस जाने का तरीका जानें."
+ "आपने होम स्क्रीन पर ले जाने वाले हाथ के जेस्चर (हाव-भाव) के बारे में जान लिया है."
"होम स्क्रीन पर जाने के लिए स्वाइप करें"
"स्क्रीन पर नीचे से ऊपर की ओर स्वाइप करें. हाथ का यह जेस्चर आपको हमेशा होम स्क्रीन पर ले जाता है."
"देख लें कि आप स्क्रीन के निचले किनारे से ऊपर की ओर स्वाइप कर रहे हों."
"कोशिश करें कि स्क्रीन से उंगली उठाने से पहले, इसे कुछ देर स्क्रीन पर दबाकर रखें."
"देख लें कि आप स्क्रीन पर ऊपर की तरफ़, बिल्कुल सीधे स्वाइप कर रहे हों और फिर रुकें."
- "आपने एक ऐप्लिकेशन से दूसरे पर जाने के लिए इस्तेमाल होने वाले हाथ के जेस्चर (हाव-भाव) के बारे में जान लिया है. आप अपना फ़ोन इस्तेमाल करने के लिए तैयार हैं!"
+ "आपने हाथ के जेस्चर (हाव-भाव) इस्तेमाल करने सीख लिए हैं. जेस्चर बंद करने के लिए, सेटिंग में जाएं."
+ "आपने एक ऐप्लिकेशन से दूसरे पर जाने के लिए इस्तेमाल होने वाले हाथ के जेस्चर के बारे में जान लिया है."
"एक ऐप्लिकेशन से दूसरे पर जाने के लिए स्वाइप करें"
- "अपनी स्क्रीन पर नीचे से ऊपर की तरफ़ स्वाइप करें, दबाकर रखें, फिर छोड़ दें."
+ "एक ऐप से दूसरे पर जाने के लिए, स्क्रीन पर नीचे से ऊपर की ओर स्वाइप करें, दबाकर रखें, और फिर छोड़ दें."
"आप पूरी तरह तैयार हैं"
- "आगे बढ़ें"
- "हो गया"
+ "हो गया"
"सेटिंग"
"फिर से कोशिश करें"
"बहुत बढ़िया!"
"ट्यूटोरियल %1$d/%2$d"
+ "हो गया!"
+ "होम स्क्रीन पर जाने के लिए, ऊपर की ओर स्वाइप करें"
+ "अब आप अपना फ़ोन इस्तेमाल कर सकते हैं"
+ "सिस्टम नेविगेशन सेटिंग"
"शेयर करें"
- "स्क्रीनशॉट"
+ "स्क्रीनशॉट लें"
+ "स्प्लिट स्क्रीन मोड"
+ "स्प्लिट स्क्रीन मोड के लिए, दूसरे ऐप पर टैप करें"
+ "यह ऐप्लिकेशन, स्प्लिट स्क्रीन पर काम नहीं करता है."
"ऐप्लिकेशन या आपका संगठन इस कार्रवाई की अनुमति नहीं देता"
"नेविगेशन ट्यूटोरियल छोड़ना चाहते हैं?"
- "आप बाद में, %1$s ऐप्लिकेशन पर इसे देख सकते हैं"
+ "आप बाद में %1$s ऐप्लिकेशन पर इसे देख सकते हैं"
"अभी नहीं"
- "छोड़ें"
+ "अभी नहीं"
+ "स्क्रीन घुमाएं"
+ "टास्कबार ट्यूटोरियल दिखाया गया"
+ "टास्कबार ट्यूटोरियल बंद किया गया"
+ "ऐप्लिकेशन स्विच करने के लिए, टास्कबार का इस्तेमाल करें"
+ "एक साथ दो ऐप्लिकेशन इस्तेमाल करने के लिए, उन्हें किनारे की ओर खींचें और छोड़ें"
+ "टास्कबार को छिपाने के लिए, उसे दबाकर रखें"
+ "आगे बढ़ें"
+ "वापस जाएं"
+ "बंद करें"
+ "हो गया"
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index d5e4b9dd93..ac18e423a3 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -25,13 +25,14 @@
"Postavke upotrebe aplikacija"
"Izbriši sve"
"Nedavne aplikacije"
+
+
"%1$s, %2$s"
"< 1 min"
"Još %1$s danas"
- "Predložene aplikacije"
- "Sve aplikacije"
+ "Prijedlozi aplikacija"
"Vaše predviđene aplikacije"
- "Prijedloge aplikacija vidjet ćete u donjem retku početnog zaslona"
+ "Primajte prijedloge aplikacija u donjem retku početnog zaslona"
"Primajte prijedloge aplikacija u retku omiljenih na početnom zaslonu"
"Lako pristupite najčešće upotrebljavanim aplikacijama s početnog zaslona. Prijedlozi će se mijenjati na temelju vaših rutina. Aplikacije iz donjeg retka pomaknut će se na početni zaslon."
"Lako pristupite najčešće upotrebljavanim aplikacijama s početnog zaslona. Prijedlozi će se mijenjati na temelju vaših rutina. Aplikacije koje se nalaze u retku omiljenih pomaknut će se na početni zaslon."
@@ -45,12 +46,10 @@
"Predlaganje apl. omogućeno"
"Predlaganje apl. onemogućeno"
"Predviđena aplikacija: %1$s"
- "Pazite da prijeđete prstom od krajnjeg lijevog ruba."
- "Pazite da prijeđete prstom od lijevog ruba do sredine zaslona i podignite prst."
- "To je to! Sad pokušajte prijeći prstom od desnog ruba."
- "Pazite da prijeđete prstom od krajnjeg desnog ruba."
- "Pazite da prijeđete prstom od desnog ruba do sredine zaslona i podignite prst."
- "Izvršili ste pokret za povratak. Sad saznajte kako otvoriti početni zaslon."
+ "Pazite da prijeđete prstom od krajnjeg desnog ili krajnjeg lijevog ruba."
+ "Pazite da prijeđete prstom od desnog ili lijevog ruba do sredine zaslona i podignite prst."
+ "Naučili ste kako prijeći prstom zdesna da biste se vratili. Sad saznajte kako promijeniti aplikaciju."
+ "Izvršili ste pokret za povratak."
"Pazite da ne prijeđete prstom preblizu dnu zaslona."
"Osjetljivost pokreta povratka promijenite u postavkama"
"Prijeđite prstom da biste se vratili"
@@ -58,27 +57,45 @@
"Pazite da prijeđete prstom prema gore od donjeg ruba zaslona."
"Pazite da ne zastanete prije podizanja prsta."
"Pazite da prijeđete prstom ravno prema gore."
- "Izvršili ste pokret za otvaranje početnog zaslona. Sad saznajte kako promijeniti aplikaciju."
+ "Izvršili ste pokret za otvaranje početnog zaslona. Sad saznajte kako se vratiti."
+ "Izvršili ste pokret za otvaranje početnog zaslona."
"Prijeđite prstom da biste otvorili početni zaslon"
"Prijeđite prstom od dna zaslona prema gore. Tim pokretom uvijek će se otvoriti početni zaslon."
"Pazite da prijeđete prstom prema gore od donjeg ruba zaslona."
"Pokušajte zadržati prozor dulje prije podizanja prsta."
"Pazite da prijeđete prstom ravno prema gore, a zatim zastanete."
- "Izvršili ste pokret za promjenu aplikacije. Spremni ste za upotrebu telefona!"
- "Prijeđite prstom da biste promijenili aplikaciju"
- "Prijeđite prstom od dna zaslona prema gore, zadržite pritisak pa podignite prst."
+ "Naučili ste koristiti pokrete. Pokrete možete isključiti u postavkama."
+ "Izvršili ste pokret za promjenu aplikacije."
+ "Povlačenje prstom za promjenu aplikacije"
+ "Za promjenu aplikacije prijeđite prstom od dna zaslona prema gore, zadržite pritisak pa pustite."
"Sve je spremno"
- "Dalje"
- "Gotovo"
+ "Gotovo"
"Postavke"
"Pokušajte ponovo"
"Odlično!"
"Vodič %1$d/%2$d"
+ "Sve je spremno!"
+ "Prijeđite prstom prema gore da biste otvorili početni zaslon"
+ "Spremni ste za početak upotrebe telefona"
+ "Postavke navigacije sustavom"
"Podijeli"
"Snimka zaslona"
+ "Podijeli"
+ "Dodirnite drugu aplikaciju za podijeljeni zaslon"
+ "Aplikacija ne podržava podijeljeni zaslon."
"Aplikacija ili vaša organizacija ne dopuštaju ovu radnju"
"Želite li preskočiti vodič za kretanje?"
"Kasnije ga možete pronaći u aplikaciji %1$s"
"Odustani"
"Preskoči"
+ "Zakretanje zaslona"
+ "Upute za programsku traku su se pojavile"
+ "Upute za programsku traku su zatvorene"
+ "Upotrijebite program. traku da biste promijenili aplikaciju"
+ "Povucite u stranu da biste istovremeno koristili dvije aplikacije"
+ "Dodirnite i zadržite da biste sakrili programsku traku"
+ "Dalje"
+ "Natrag"
+ "Zatvori"
+ "Gotovo"
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 386ef3ee3f..d368aed527 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -25,11 +25,12 @@
"Alkalmazáshasználati beállítások"
"Összes törlése"
"Legutóbbi alkalmazások"
+
+
"%1$s, %2$s"
"< 1 perc"
"Ma még %1$s van hátra"
"Alkalmazásjavaslatok"
- "Az összes alkalmazás"
"Várható alkalmazások"
"Alkalmazásjavaslatokat kaphat a kezdőképernyő alsó sorában"
"Alkalmazásjavaslatokat kaphat a kezdőképernyőn megjelenő kedvencek sorában"
@@ -76,9 +77,22 @@
"Útmutató (%2$d/%1$d.)"
"Megosztás"
"Képernyőkép"
+ "Felosztás"
+ "Koppintson másik appra a képernyőmegosztáshoz"
+ "Az alkalmazás nem támogatja az osztott képernyőt."
"Az alkalmazás vagy az Ön szervezete nem engedélyezi ezt a műveletet"
"Kihagyja a navigáció bemutatóját?"
"Ezt később megtalálhatja a(z) %1$s alkalmazásban"
"Mégse"
"Kihagyás"
+ "Képernyő elforgatása"
+ "Az eszköztár használatát ismertető panel megjelent"
+ "Az eszköztár használatát ismertető panel bezárult"
+ "Az eszköztárral válthat az alkalmazások között"
+ "Húzza oldalra, ha egyszerre két appot szeretne használni"
+ "Nyomva tartással elrejthető az eszköztár"
+ "Tovább"
+ "Vissza"
+ "Bezárás"
+ "Kész"
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index 3ff9d0d5c2..4bd28f1dc0 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -25,11 +25,12 @@
"Հավելվածի օգտագործման կարգավորումներ"
"Փակել բոլորը"
"Վերջին օգտագործած հավելվածները"
+
+
"%1$s, %2$s"
"< 1 ր"
"Այսօր մնացել է՝ %1$s"
"Առաջարկվող հավելվածներ"
- "Բոլոր հավելվածները"
"Ձեր կանխատեսված հավելվածները"
"Ստացեք հավելվածների առաջարկներ հիմնական էկրանի ներքևում"
"Ստացեք հավելվածների առաջարկներ հիմնական էկրանի «Ընտրանի» տողում"
@@ -76,9 +77,22 @@
"Ուղեցույց %1$d/%2$d"
"Կիսվել"
"Սքրինշոթ անել"
+ "Տրոհել"
+ "Հպեք այլ հավելվածի՝ էկրանը տրոհելու համար"
+ "Հավելվածը չի աջակցում էկրանի տրոհումը։"
"Այս գործողությունն արգելված է հավելվածի կամ ձեր կազմակերպության կողմից"
"Բաց թողնե՞լ նավիգացիայի ուղեցույցը"
"Հետագայում սա կարող եք գտնել «%1$s» հավելվածում"
"Չեղարկել"
"Բաց թողնել"
+ "Պտտել էկրանը"
+ "Խնդրագոտու «Կրթություն» վահանակը բացվեց"
+ "Խնդրագոտու «Կրթություն» վահանակը փակվեց"
+ "Օգտագործեք խնդրագոտին՝ մի հավելվածից մյուսին անցնելու համար"
+ "Քաշեք մի կողմ՝ միաժամանակ երկու հավելված օգտագործելու համար"
+ "Հպեք և պահեք՝ խնդրագոտին թաքցնելու համար"
+ "Առաջ"
+ "Հետ"
+ "Փակել"
+ "Պատրաստ է"
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 941bdb7dd0..ac9cea61e1 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -25,11 +25,12 @@
"Setelan penggunaan aplikasi"
"Hapus semua"
"Aplikasi terbaru"
+
+
"%1$s, %2$s"
"< 1 menit"
"%1$s tersisa hari ini"
"Saran aplikasi"
- "Semua aplikasi"
"Aplikasi yang diprediksi"
"Dapatkan saran aplikasi di baris paling bawah Layar utama"
"Dapatkan saran aplikasi di baris favorit Layar utama"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Bagikan"
"Screenshot"
+ "Pisahkan"
+ "Ketuk aplikasi lain untuk menggunakan layar terpisah"
+ "Aplikasi tidak mendukung layar terpisah."
"Tindakan ini tidak diizinkan oleh aplikasi atau organisasi Anda"
"Lewati tutorial navigasi?"
"Anda dapat menemukan tutorial ini di lain waktu di aplikasi %1$s"
"Batal"
"Lewati"
+ "Putar layar"
+ "Edukasi taskbar ditampilkan"
+ "Edukasi taskbar ditutup"
+ "Gunakan taskbar untuk beralih aplikasi"
+ "Tarik ke samping untuk menggunakan dua aplikasi sekaligus"
+ "Sentuh lama untuk menyembunyikan taskbar"
+ "Berikutnya"
+ "Kembali"
+ "Tutup"
+ "Selesai"
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 6042b47e45..2f7545aa5c 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -25,11 +25,12 @@
"Notkunarstillingar forrits"
"Hreinsa allt"
"Nýleg forrit"
+
+
"%1$s, %2$s"
"< 1 mín."
"%1$s eftir í dag"
"Tillögur að forritum"
- "Öll forrit"
"Spáð forrit"
"Fáðu tillögur að forritum í neðstu röð heimaskjásins"
"Fáðu tillögur að forritum á eftirlætissvæði heimaskjásins"
@@ -76,9 +77,22 @@
"Leiðsögn %1$d/%2$d"
"Deila"
"Skjámynd"
+ "Skipta"
+ "Ýttu á annað forrit til að nota skjáskiptingu"
+ "Forritið styður ekki að skjánum sé skipt."
"Forritið eða fyrirtækið leyfir ekki þessa aðgerð"
"Sleppa flettileiðsögn?"
"Þú getur fundið þetta síðar í forritinu %1$s"
"Hætta við"
"Sleppa"
+ "Snúa skjánum"
+ "Leiðsögn verkefnastiku sýnileg"
+ "Leiðsögn verkefnastiku lokað"
+ "Notaðu verkefnastikuna til að skipta á milli forrita"
+ "Dragðu til hliðar til að nota tvö forrit í einu"
+ "Haltu inni til að fela verkefnastikuna"
+ "Áfram"
+ "Til baka"
+ "Loka"
+ "Lokið"
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index 6c81d1720e..9c2893b4da 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -25,11 +25,12 @@
"Impostazioni di utilizzo delle app"
"Cancella tutto"
"App recenti"
+
+
"%1$s, %2$s"
"< 1 min"
"Rimanente oggi: %1$s"
"App suggerite"
- "Tutte le app"
"App previste per te"
"Visualizza app suggerite nella riga inferiore della schermata Home"
"Visualizza app suggerite nella riga dei Preferiti della schermata Home"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Condividi"
"Screenshot"
+ "Dividi"
+ "Tocca un\'altra app per usare lo schermo diviso"
+ "L\'app non supporta la modalità Schermo diviso."
"Questa azione non è consentita dall\'app o dall\'organizzazione"
"Saltare il tutorial di navigazione?"
"Puoi trovarlo in un secondo momento nell\'app %1$s"
"Annulla"
"Salta"
+ "Ruota lo schermo"
+ "Riquadro Formazione barra delle applicazioni visualizzato"
+ "Riquadro Formazione barra delle applicazioni chiuso"
+ "Usa la barra delle applicazioni per cambiare app"
+ "Trascina di lato per usare due app contemporaneamente"
+ "Tocca e tieni premuto per nascondere barra applicazioni"
+ "Avanti"
+ "Indietro"
+ "Chiudi"
+ "Fine"
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index b77faeb4fa..b73edbcbc2 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -25,11 +25,12 @@
"הגדרות שימוש באפליקציה"
"ניקוי הכול"
"אפליקציות אחרונות"
+
+
"%1$s, %2$s"
"< דקה"
"הזמן שנותר להיום: %1$s"
"הצעות לאפליקציות"
- "כל האפליקציות"
"האפליקציות החזויות שלך"
"קבלת הצעות לאפליקציות בשורה התחתונה של מסך הבית"
"קבלת הצעות לאפליקציות בשורת המועדפות של מסך הבית"
@@ -76,9 +77,22 @@
"מדריך %1$d/%2$d"
"שיתוף"
"צילום מסך"
+ "פיצול"
+ "צריך להקיש על אפליקציה אחרת כדי להשתמש במסך מפוצל"
+ "האפליקציה אינה תומכת במסך מפוצל."
"האפליקציה או הארגון שלך אינם מתירים את הפעולה הזאת"
"לדלג על המדריך לניווט?"
"ניתן למצוא את המדריך מאוחר יותר באפליקציה %1$s"
"ביטול"
"דילוג"
+ "סיבוב המסך"
+ "חלונית ההסברים על שורת המשימות מופיעה"
+ "חלונית ההסברים על שורת המשימות נסגרה"
+ "כדי לעבור בין אפליקציות, משתמשים בשורת המשימות"
+ "כדי להשתמש בשתי אפליקציות בו-זמנית, צריך לגרור לצד"
+ "כדי להסתיר את שורת המשימות, לוחצים לחיצה ארוכה"
+ "הבא"
+ "חזרה"
+ "סגירה"
+ "סיום"
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index 1cbdf7331c..b6a5480788 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -25,11 +25,12 @@
"アプリの使用状況の設定"
"すべてクリア"
"最近使ったアプリ"
+
+
"%1$s、%2$s"
"1 分未満"
"今日はあと %1$sです"
"アプリの候補"
- "すべてのアプリ"
"予測されたアプリ"
"ホーム画面の一番下の行でアプリの候補を利用できます"
"ホーム画面のお気に入りの行でアプリの候補を利用できます"
@@ -76,9 +77,22 @@
"チュートリアル %1$d/%2$d"
"共有"
"スクリーンショット"
+ "分割"
+ "分割画面を使用するには、他のアプリをタップします"
+ "アプリで分割画面がサポートされていません。"
"この操作はアプリまたは組織で許可されていません"
"操作チュートリアルをスキップしますか?"
"これは後から %1$s アプリで確認できます"
"キャンセル"
"スキップ"
+ "画面を回転"
+ "タスクバーの説明を開きました"
+ "タスクバーの説明を閉じました"
+ "アプリを切り替えるには、タスクバーを使用します"
+ "2 個のアプリを同時に使用するには、横にドラッグします"
+ "タスクバーを長押しすると非表示になります"
+ "次へ"
+ "戻る"
+ "閉じる"
+ "完了"
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index 5dd818d9ac..e813238c29 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -25,11 +25,12 @@
"აპების გამოყენების პარამეტრები"
"ყველას გასუფთავება"
"ბოლოდროინდელი აპები"
+
+
"%1$s, %2$s"
"< 1 წუთი"
"დღეს დარჩენილია %1$s"
"აპის შემოთავაზებები"
- "ყველა აპი"
"თქვენი პროგნოზირებული აპები"
"მიიღეთ აპის შეთავაზებები მთავარი ეკრანის ქვედა რიგში"
"მიიღეთ აპების შემოთავაზებები მთავარი ეკრანის რჩეულების მწკრივში"
@@ -45,12 +46,10 @@
"აპის შეთავაზებები ჩართულია"
"აპის შეთავაზებები გათიშულია"
"ნაწინასწარმეტყველები აპი: %1$s"
- "არ გადაფურცლოთ მარცხენა კიდის ბოლოდან."
- "გადაფურცლეთ მარცხენა კიდიდან ეკრანის ცენტრისკენ და თითი აუშვით."
- "სულ ეს არის! ახლა კი ცადეთ, გადაფურცლოთ მარჯვენა კიდიდან."
- "არ გადაფურცლოთ მარჯვენა კიდის ბოლოდან."
- "გადაფურცლეთ მარჯვენა კიდიდან ეკრანის ცენტრისკენ და თითი აუშვით."
- "თქვენ შეასრულეთ უკან დაბრუნების ჟესტი. ახლა კი შევიტყოთ, თუ როგორ დავბრუნდეთ მთავარ გვერდზე."
+ "გადაფურცლეთ უკიდურესი მარჯვენა ან მარცხენა ბოლოდან."
+ "გადაფურცლეთ მარჯვენა ან მარცხენა კიდიდან ეკრანის ცენტრისკენ და თითი აუშვით."
+ "თქვენ ისწავლეთ მარჯვნიდან გადაფურცვლა უკან დასაბრუნებლად. ახლა კი შეიტყვეთ, როგორ გადართოთ აპები."
+ "თქვენ შეასრულეთ უკან დაბრუნების ჟესტი."
"არ გადაფურცლოთ ეკრანის ბოლოსთან ახლოს."
"დაბრუნების ჟესტის მგრძნობელობის შესაცვლელად გადადით პარამეტრებზე"
"უკან დასაბრუნებლად გადაფურცლეთ"
@@ -58,27 +57,45 @@
"გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ."
"არ დააპაუზოთ თითის აშვებამდე."
"გადაფურცლეთ ზემოთ."
- "თქვენ შეასრულეთ მთავარ ეკრანზე დაბრუნების ჟესტი. ახლა კი შევიტყოთ, როგორ გადავრთოთ აპები."
- "მთავარ გვერდზე გადასასვლელად გადაფურცლეთ"
+ "თქვენ შეასრულეთ მთავარ ეკრანზე დაბრუნების ჟესტი. ახლა კი შევიტყოთ, თუ როგორ დავბრუნდეთ უკან."
+ "თქვენ შეასრულეთ მთავარ ეკრანზე დაბრუნების ჟესტი."
+ "მთავარი გვერდის სანახავად გადაფურცლეთ"
"გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ. ამ ჟესტს ყოველთვის მთავარი გვერდის ეკრანზე გადაყავხართ."
"გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ."
"უფრო ხანგრძლივად დააჭირეთ თითი ფანჯარას, რომ არ დაიხუროს."
"გადაფურცლეთ პირდაპირ ზემოთ და შემდეგ დააპაუზეთ."
- "თქვენ შეასრულეთ აპების გადართვის ჟესტი. შეგიძლიათ ტელეფონით სარგებლობა!"
+ "თქვენ ისწავლეთ ჟესტების გამოყენება. ჟესტების გამოსართავად გადადით პარამეტრებში."
+ "თქვენ შეასრულეთ აპების გადართვის ჟესტი."
"აპების გადასართავად გადაფურცლეთ"
- "გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ, დააყოვნეთ, შემდეგ თითი აუშვით."
+ "აპების გადასართავად, გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ, დააყოვნეთ, შემდეგ თითი აუშვით."
"მზად არის"
- "შემდეგ"
- "მზადაა"
+ "მზადაა"
"პარამეტრები"
"ხელახლა ცდა"
"მშვენიერია!"
"სახელმძღვანელო %1$d/%2$d"
+ "მზადაა!"
+ "მთავარ გვერდზე გადასასვლელად გადაფურცლეთ ზევით"
+ "მზად ხართ ტელეფონის გამოსაყენებლად"
+ "სისტემის ნავიგაციის პარამეტრები"
"გაზიარება"
"ეკრანის ანაბეჭდი"
+ "გაყოფა"
+ "შეეხეთ სხვა აპს ეკრანის გასაყოფად"
+ "ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ."
"ეს მოქმედება არ არის დაშვებული აპის ან თქვენი ორგანიზაციის მიერ"
"გსურთ, გამოტოვოთ ნავიგაციის სახელმძღვანელო?"
"ამის მოგვიანებით პოვნა %1$s აპში შეგიძლიათ"
"გაუქმება"
"გამოტოვება"
+ "ეკრანის შეტრიალება"
+ "ამოცანების ზოლის სასწავლო არე გამოჩნდა"
+ "ამოცანების ზოლის სასწავლო არე დაიხურა"
+ "აპების გადასართავად გამოიყენეთ ამოცანათა ზოლი"
+ "გადაათრიეთ კიდეზე ორი აპის ერთდოულად გამოსაყენებლად"
+ "ხანგრძლივად შეეხეთ ამოცანების ზოლის დასამალად"
+ "შემდეგი"
+ "უკან"
+ "დახურვა"
+ "მზადაა"
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 554d10dc82..55142d446e 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -25,11 +25,12 @@
"Қолданбаны пайдалану параметрлері"
"Барлығын өшіру"
"Соңғы пайдаланылған қолданбалар"
+
+
"%1$s, %2$s"
"< 1 мин"
"Бүгін %1$s қалды"
"Ұсынылған қолданбалар"
- "Барлық қолданбалар"
"Ұсынылатын қолданбалар"
"Негізгі экранның төменгі жолында қолданбаларды ұсыну"
"Ұсынылған қолданбалар негізгі экранда таңдаулылар арасында көрсетілетін болады"
@@ -42,15 +43,13 @@
"Жиі пайдаланылатын қолданбалар осы жерде көрсетіледі. Олар күнделікті әрекеттеріңізге сәйкес өзгереді."
"Ұсынылған қолданбаларды көру үшін төменгі қатардан керектерін сүйреп шығарыңыз."
"Ұсынылған қолданбалар бос орынға қосылды."
- "\"Ұсынылған қолданбалар\" функциясы қосулы."
+ "Ұсынылған қолданбалар функциясы қосылды."
"\"Ұсынылған қолданбалар\" функциясы өшірулі."
"Болжалды қолданба: %1$s"
- "Сол жақтағы ең шеткі нүктеден бастап сырғытыңыз."
- "Экранның сол жақ шетінен ортасына қарай сырғытыңыз да, жіберіңіз."
- "Дайын! Енді оң жақ шеттен сырғытып көріңіз."
- "Оң жақтағы ең шеткі нүктеден бастап сырғытыңыз."
- "Экранның оң жақ шетінен ортасына қарай сырғытыңыз да, жіберіңіз."
- "Артқа қайту қимылын аяқтадыңыз. Енді негізгі экранға өтуді үйреніңіз."
+ "Экранның оң немесе сол жиегінен сырғытыңыз."
+ "Экранның оң немесе сол жиегінен ортасына қарай сырғытып, саусағыңызды жіберіңіз."
+ "Оңнан солға сырғыту арқылы артқа қайтуды үйрендіңіз. Енді қолданбаларды ауыстыруды үйреніңіз."
+ "Артқа қайту қимылын аяқтадыңыз."
"Саусағыңызбен сырғыту кезінде экранның төменгі жағына тым жақындамаңыз."
"Артқа қайту қимылы сезгіштігін параметрлерден өзгертіңіз."
"Артқа қайту үшін сырғытыңыз"
@@ -58,27 +57,45 @@
"Экранның төменгі шетінен жоғары қарай сырғытыңыз."
"Жіберер алдында кідіріс жасамаңыз."
"Тігінен жоғары қарай сырғытыңыз."
- "Негізгі экранға қайту қимылын аяқтадыңыз. Енді қолданбаларды ауыстыруды үйреніңіз."
+ "Негізгі экранға қайту қимылын аяқтадыңыз. Енді артқа қайтуды үйреніңіз."
+ "Негізгі экранға қайту қимылын аяқтадыңыз."
"Негізгі экранға өту үшін сырғытыңыз"
"Экранның төменгі жағынан жоғары қарай сырғытыңыз. Сонда негізгі экран ашылады."
"Экранның төменгі шетінен жоғары қарай сырғытыңыз."
"Жіберер алдында терезені ұзағырақ ұстап тұруға тырысыңыз."
"Тігінен жоғары қарай сырғытыңыз да, кідіріңіз."
- "Қолданбаларды ауыстыру қимылын аяқтадыңыз. Телефоныңызды пайдалануға дайынсыз!"
+ "Қимылдарды қолдануды үйрендіңіз. Қимылдарды өшіру үшін \"Параметрлер\" бөліміне өтіңіз."
+ "Қолданбаларды ауыстыру қимылын аяқтадыңыз."
"Қолданбаларды ауыстыру үшін сырғытыңыз"
- "Экранның төменгі жағынан жоғары қарай сырғытып, ұстап тұрыңыз да, жіберіңіз."
+ "Бір қолданбадан екіншісіне ауысу үшін экранның төменгі жағынан жоғары қарай сырғытып, ұстап тұрып жіберіңіз."
"Бәрі дайын"
- "Келесі"
- "Дайын"
+ "Дайын"
"Параметрлер"
"Қайталау"
"Жақсы!"
"Оқулық: %1$d/%2$d"
+ "Бәрі дайын!"
+ "Негізгі экранға өту үшін жоғары қарай сырғытыңыз."
+ "Телефоныңыз пайдалануға дайын."
+ "Навигацияның жүйелік параметрлері"
"Бөлісу"
"Скриншот"
+ "Бөлу"
+ "Экранды бөлу режимін пайдалану үшін басқа қолданбаны түртіңіз."
+ "Қолданбада экранды бөлу мүмкін емес."
"Бұл әрекетке қолданба не ұйым рұқсат етпейді."
"Қимылдар оқулығын өткізіп жіберу керек пе?"
"Мұны кейін %1$s қолданбасынан таба аласыз."
"Бас тарту"
"Өткізіп жіберу"
+ "Экранды бұру"
+ "Тапсырмалар тақтасы бойынша нұсқаулық ашылды."
+ "Тапсырмалар тақтасы бойынша нұсқаулық жабылды."
+ "Қолданбаларды ауыстыру үшін тапсырма тақтасын пайдаланыңыз."
+ "Екі қолданбаны бір уақытта пайдалану үшін шетке сүйреңіз."
+ "Тапсырмалар тақтасын жасыру үшін басып тұрыңыз."
+ "Келесі"
+ "Артқа"
+ "Жабу"
+ "Дайын"
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 62858ba625..7e1244715f 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -25,11 +25,12 @@
"ការកំណត់ការប្រើប្រាស់កម្មវិធី"
"សម្អាតទាំងអស់"
"កម្មវិធីថ្មីៗ"
+
+
"%1$s, %2$s"
"< 1 នាទី"
"នៅសល់ %1$s ទៀតនៅថ្ងៃនេះ"
"ការណែនាំកម្មវិធី"
- "កម្មវិធីទាំងអស់"
"កម្មវិធីដែលបានព្យាកររបស់អ្នក"
"ទទួលបានការណែនាំកម្មវិធីនៅជួរខាងក្រោមនៃអេក្រង់ដើមរបស់អ្នក"
"ទទួលបានការណែនាំកម្មវិធីនៅលើជួរដេកសំណព្វនៃអេក្រង់ដើមរបស់អ្នក"
@@ -76,9 +77,22 @@
"មេរៀនទី %1$d/%2$d"
"ចែករំលែក"
"រូបថតអេក្រង់"
+ "បំបែក"
+ "ចុចកម្មវិធីផ្សេងទៀត ដើម្បីប្រើមុខងារបំបែកអេក្រង់"
+ "កម្មវិធីមិនអាចប្រើមុខងារបំបែកអេក្រង់បានទេ។"
"សកម្មភាពនេះមិនត្រូវបានអនុញ្ញាតដោយកម្មវិធី ឬស្ថាប័នរបស់អ្នកទេ"
"រំលងមេរៀនអំពីការរុករកឬ?"
"អ្នកអាចស្វែងរកមេរៀននេះនៅពេលក្រោយក្នុងកម្មវិធី %1$s"
"បោះបង់"
"រំលង"
+ "បង្វិលអេក្រង់"
+ "ការបង្រៀនអំពីរបារកិច្ចការបានបង្ហាញ"
+ "ការបង្រៀនអំពីរបារកិច្ចការត្រូវបានបិទ"
+ "ប្រើរបារកិច្ចការ ដើម្បីប្ដូរកម្មវិធី"
+ "អូសទៅចំហៀង ដើម្បីប្រើកម្មវិធីពីរក្នុងពេលតែមួយ"
+ "ចុចឱ្យជាប់ ដើម្បីលាក់របារកិច្ចការ"
+ "បន្ទាប់"
+ "ថយក្រោយ"
+ "បិទ"
+ "រួចរាល់"
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 8fdba693bf..a505a580da 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -25,11 +25,12 @@
"ಆ್ಯಪ್ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್ಗಳು"
"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"
"ಇತ್ತೀಚಿನ ಅಪ್ಲಿಕೇಶನ್ಗಳು"
+
+
"%1$s, %2$s"
"< 1 ನಿ"
"ಇಂದು %1$s ಸಮಯ ಉಳಿದಿದೆ"
"ಆ್ಯಪ್ ಸಲಹೆಗಳು"
- "ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳು"
"ನಿಮ್ಮ ಮುನ್ಸೂಚಿತ ಆ್ಯಪ್ಗಳು"
"ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್ನ ಕೆಳಭಾಗದ ಸಾಲಿನಲ್ಲಿ ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ಪಡೆಯಿರಿ"
"ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್ನ ಮೆಚ್ಚಿನವುಗಳ ಸಾಲಿನಲ್ಲಿ ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ಪಡೆಯಿರಿ"
@@ -76,9 +77,22 @@
"ಟ್ಯುಟೋರಿಯಲ್ %1$d/%2$d"
"ಹಂಚಿಕೊಳ್ಳಿ"
"ಸ್ಕ್ರೀನ್ಶಾಟ್"
+ "ವಿಭಜಿಸಿ"
+ "ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಬಳಸಲು ಬೇರೊಂದು ಆ್ಯಪ್ ಮೇಲೆ ಟ್ಯಾಪ್ ಮಾಡಿ"
+ "ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಆ್ಯಪ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."
"ಆ್ಯಪ್ ಅಥವಾ ನಿಮ್ಮ ಸಂಸ್ಥೆಯು ಈ ಕ್ರಿಯೆಯನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"
"ನ್ಯಾವಿಗೇಷನ್ ಟ್ಯುಟೋರಿಯಲ್ ಸ್ಕಿಪ್ ಮಾಡಿ?"
"%1$s ಆ್ಯಪ್ನಲ್ಲಿ ಇದನ್ನು ನಂತರ ಕಾಣಬಹುದು"
"ರದ್ದುಮಾಡಿ"
"ಸ್ಕಿಪ್ ಮಾಡಿ"
+ "ಸ್ಕ್ರೀನ್ ತಿರುಗಿಸಿ"
+ "ಟಾಸ್ಕ್ಬಾರ್ ಶಿಕ್ಷಣ ಕಾಣಿಸಿಕೊಂಡಿದೆ"
+ "ಟಾಸ್ಕ್ಬಾರ್ ಶಿಕ್ಷಣ ಮುಚ್ಚಿದೆ"
+ "ಆ್ಯಪ್ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಟಾಸ್ಕ್ ಬಾರ್ ಬಳಸಿ"
+ "ಏಕಕಾಲದಲ್ಲಿ ಎರಡು ಆ್ಯಪ್ಗಳನ್ನು ಬಳಸಲು, ಬದಿಗೆ ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"
+ "ಟಾಸ್ಕ್ಬಾರ್ ಅನ್ನು ಮರೆಮಾಡಲು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹೋಲ್ಡ್ ಮಾಡಿ"
+ "ಮುಂದೆ"
+ "ಹಿಂದೆ"
+ "ಮುಚ್ಚಿರಿ"
+ "ಮುಗಿದಿದೆ"
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index fbbddd6699..3c1973f1f9 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -25,11 +25,12 @@
"앱 사용 설정"
"모두 삭제"
"최근 앱"
+
+
"%1$s, %2$s"
"< 1분"
"오늘 %1$s 남음"
"앱 제안"
- "모든 앱"
"추천 앱"
"홈 화면 하단에서 앱 제안 보기"
"홈 화면의 즐겨찾기 행에서 앱 제안 보기"
@@ -76,9 +77,22 @@
"튜토리얼 %1$d/%2$d"
"공유"
"스크린샷"
+ "분할"
+ "다른 앱을 탭하여 화면 분할 사용"
+ "앱이 화면 분할을 지원하지 않습니다."
"이 작업은 앱 또는 조직에서 허용되지 않습니다."
"이동 방법 튜토리얼을 건너뛰시겠습니까?"
"이 튜토리얼은 %1$s 앱에서 다시 볼 수 있습니다"
"취소"
"건너뛰기"
+ "화면 회전"
+ "작업 표시줄 튜토리얼 패널 표시됨"
+ "작업 표시줄 튜토리얼 패널 닫힘"
+ "작업 표시줄을 사용하여 앱 전환"
+ "옆으로 드래그하여 한 번에 앱 두 개 사용"
+ "작업 표시줄을 숨기려면 길게 터치하세요."
+ "다음"
+ "뒤로"
+ "닫기"
+ "완료"
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index bd50eed506..609bb8649b 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -25,11 +25,12 @@
"Колдонмону пайдалануу жөндөөлөрү"
"Баарын тазалоо"
"Акыркы колдонмолор"
+
+
"%1$s, %2$s"
"< 1 мүнөт"
"Бүгүн %1$s мүнөт калды"
"Сунушталган колдонмолор"
- "Бардык колдонмолор"
"Божомолдонгон колдонмолоруңуз"
"Сунушталган колдонмолор башкы экрандын ылдый жагында көрүнөт."
"Сунушталган колдонмолор башкы экрандагы тандалмалардын катарында көрүнөт."
@@ -40,45 +41,61 @@
"Жок, рахмат"
"Жөндөөлөр"
"Көп иштетилген колдонмолор ушул жерде көрүнүп, тартиптин негизинде өзгөрөт"
- "Сунуштарды алып туруу үчүн, ылдый жактагы тилкедеги колдонмолорду сүйрөп келиңиз"
+ "Сунуштарды алып туруу үчүн ылдый жактагы тилкедеги колдонмолорду сүйрөп келиңиз"
"Сунушталган колдонмолор бош жерге кошулат"
"Сунушталган колдонмолор функциясы иштетилди"
"Сунушталган колдонмолор функциясы өчүрүлгөн"
"Божомолдонгон колдонмо: %1$s"
- "Экранды эң четинен солдон оңго карай сүрүңүз."
- "Экранды сол жагынан ортосуна карай сүрүп, манжаңызды алыңыз."
- "Бүттү! Эми экранды оң жагынан сүрүп көрүңүз."
- "Экранды эң четинен оңдой солго карай сүрүңүз."
- "Экранды оң жагынан ортосуна карай сүрүп, манжаңызды алыңыз."
- "\"Артка\" жаңсоосу боюнча үйрөткүчтү бүтүрдүңүз. Эми башкы бетке кантип өтүүнү үйрөнүп алыңыз."
+ "Экранды эң четинен оңдон солго же солдон оңго карай сүрүңүз."
+ "Экранды оң же сол жагынан ортосуна карай сүрүп, манжаңызды алыңыз."
+ "Артка кайтуу үчүн экранды оңдон солго карай сүрүүнү үйрөндүңүз. Эми колдонмолорду которуштурганды үйрөнүп алыңыз."
+ "\"Артка\" жаңсоосун үйрөндүңүз."
"Манжаңызды экрандын ылдый жагына өтө жакындатпай сүрүңүз."
- "\"Артка\" жаң-нун сезгичтигин өзгөртүү үчүн Жөндөөлөргө өтүңүз"
+ "\"Артка\" жаң-нун сезгичтигин өзгөртүү үчүн жөндөөлөргө өтүңүз"
"Артка кайтуу үчүн сүрүңүз"
"Акыркы экранга кайтуу үчүн экранды сол же оң жагынан ортосуна карай сүрүңүз."
- "Экранды ылдыйдан өйдө карай сүрүңүз."
+ "Экранды ылдыйдан өйдө сүрүңүз."
"Манжаңызды алганга чейин токтотпоңуз."
- "Экранды өйдө карай сүрүңүз."
- "\"Башкы бетке өтүү\" жаңсоосу боюнча үйрөткүчтү бүтүрдүңүз. Эми колдонмолорду которуштуруу ыкмасын үйрөнүп алыңыз."
+ "Экранды өйдө сүрүңүз."
+ "\"Башкы бетке өтүү\" жаңсоосун үйрөндүңүз. Эми артка кайтууну үйрөнүп алыңыз."
+ "\"Башкы бетке өтүү\" жаңсоосун үйрөндүңүз."
"Башкы бетке өтүү үчүн сүрүп коюңуз"
- "Экранды ылдый жагынан өйдө карай сүрүңүз. Бул жаңсоо сизди ар дайым Башкы экранга алып барат."
- "Экранды ылдыйдан өйдө карай сүрүңүз."
+ "Экранды ылдый жагынан өйдө сүрүңүз. Бул жаңсоо сизди ар дайым Башкы экранга алып барат."
+ "Экранды ылдыйдан өйдө сүрүңүз."
"Манжаңызды алуудан мурун экранда узагыраак кармаңыз."
"Экранды өйдө карай сүрүп, токтоп туруңуз."
- "\"Колдонмолорду которуштуруу\" жаңсоосу боюнча үйрөткүчтү бүтүрдүңүз. Телефонуңузду колдоно берсеңиз болот."
+ "Жаңсоолорду колдонгонду үйрөндүңүз. Жаңсоолорду өчүрүү үчүн жөндөөлөргө өтүңүз."
+ "\"Колдонмолорду которуштуруу\" жаңсоосун үйрөндүңүз."
"Колдонмолорду которуштуруу үчүн сүрүңүз"
- "Экранды ылдыйдан өйдө карай сүрүп, бир аз коё бербей кармап туруңуз."
+ "Бир колдонмодон экинчисине өтүү үчүн экранды ылдыйдан өйдө карай сүрүп, бир аз коё бербей туруңуз."
"Дапдаяр!"
- "Кийинки"
- "Бүттү"
+ "Бүттү"
"Жөндөөлөр"
- "Кайра аракет кылыңыз"
+ "Кайталап көрүңүз"
"Сонун!"
"Үйрөткүч: %1$d/%2$d"
+ "Бүттү!"
+ "Башкы бетке өтүү үчүн экранды өйдө сүрүңүз"
+ "Телефонуңузду колдоно берсеңиз болот"
+ "Өтүү аракетинин тутумдук жөндөөлөрү"
"Бөлүшүү"
"Скриншот"
+ "Бөлүү"
+ "Экранды бөлүү үчүн башка колдонмону таптап коюңуз"
+ "Колдонмодо экран бөлүнбөйт."
"Бул аракетти аткарууга колдонмо же ишканаңыз тыюу салган"
- "Жаңсап өтүү үйрөткүчүн өт-рүп жибер-би?"
+ "Жаңсоолор үйрөткүчүн өткөрүп жибересизби?"
"Аны кийин %1$s колдонмосунан табасыз"
"Жокко чыгаруу"
- "Өтк-п жиберүү"
+ "Өткрп жиберүү"
+ "Экранды буруу"
+ "Тапшырмалар тактасынын окутуу панели көрсөтүлдү"
+ "Тапшырмалар тактасынын окутуу панели жабылды"
+ "Тапшырмалар тактасы аркылуу башка колдонмого которула аласыз"
+ "Эки колдонмону бир убакта пайдалануу үчүн капталга сүрүңүз"
+ "Тапшырмалар тактасын жашыруу үчүн коё бербей басып туруңуз"
+ "Кийинки"
+ "Артка"
+ "Жабуу"
+ "Бүттү"
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index e05b18e613..3179103e19 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -25,11 +25,12 @@
"ການຕັ້ງຄ່າການນຳໃຊ້ແອັບ"
"ລຶບລ້າງທັງໝົດ"
"ແອັບຫຼ້າສຸດ"
+
+
"%1$s, %2$s"
"< 1 ນາທີ"
"ເຫຼືອ %1$s ມື້ນີ້"
"ການແນະນຳແອັບ"
- "ແອັບທັງໝົດ"
"ແອັບທີ່ຄາດເດົາໄວ້ແລ້ວຂອງທ່ານ"
"ຮັບການແນະນຳແອັບຢູ່ແຖວລຸ່ມສຸດຂອງໜ້າຈໍຫຼັກທ່ານ"
"ຮັບການແນະນຳແອັບຢູ່ແຖວລາຍການທີ່ມັກຂອງໜ້າຈໍຫຼັກຂອງທ່ານ"
@@ -76,9 +77,22 @@
"ການສອນການນຳໃຊ້ທີ %1$d/%2$d"
"ແບ່ງປັນ"
"ຮູບໜ້າຈໍ"
+ "ແບ່ງ"
+ "ແຕະແອັບອື່ນເພື່ອໃຊ້ການແຍກໜ້າຈໍ"
+ "ແອັບບໍ່ຮອງຮັບການແບ່ງໜ້າຈໍ."
"ແອັບ ຫຼື ອົງການຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ໃຊ້ຄຳສັ່ງນີ້"
"ຂ້າມການສອນການນຳໃຊ້ການນຳທາງບໍ?"
"ທ່ານສາມາດຊອກສ່ວນນີ້ພາຍຫຼັງໄດ້ໃນແອັບ %1$s"
"ຍົກເລີກ"
"ຂ້າມ"
+ "ໝຸນໜ້າຈໍ"
+ "ສະແດງການສຶກສາແຖບໜ້າວຽກແລ້ວ"
+ "ປິດການສຶກສາແຖບໜ້າວຽກແລ້ວ"
+ "ໃຊ້ແຖບໜ້າວຽກເພື່ອສະຫຼັບແອັບ"
+ "ລາກໄປທາງຂ້າງເພື່ອໃຊ້ສອງແອັບພ້ອມກັນ"
+ "ແຕະຄ້າງໄວ້ເພື່ອເຊື່ອງແຖບໜ້າວຽກ"
+ "ຕໍ່ໄປ"
+ "ກັບຄືນ"
+ "ປິດ"
+ "ແລ້ວໆ"
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 2f1db5038a..6d9c303d5b 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -25,11 +25,12 @@
"Programos naudojimo nustatymai"
"Išvalyti viską"
"Naujausios programos"
+
+
"%1$s, %2$s"
"< 1 min."
"Šiandien liko: %1$s"
"Programų pasiūlymai"
- "Visos programos"
"Numatomos programos"
"Gaukite programų pasiūlymų apatinėje pagrindinio ekrano eilutėje"
"Gaukite programų pasiūlymų pagrindinio ekrano eilutėje „Mėgstamiausios“"
@@ -76,9 +77,22 @@
"Mokymo programa: %1$d iš %2$d"
"Bendrinti"
"Ekrano kopija"
+ "Išskaidymo režimas"
+ "Pal. kitą progr., kad gal. naud. išsk. ekr. rež."
+ "Programoje nepalaikomas išskaidyto ekrano režimas."
"Jūsų organizacijoje arba naudojant šią programą neleidžiama atlikti šio veiksmo"
"Praleisti naršymo mokymo programą?"
"Tai galėsite rasti vėliau programoje „%1$s“"
"Atšaukti"
"Praleisti"
+ "Pasukti ekraną"
+ "Užduočių juostos patarimai rodomi"
+ "Užduočių juostos patarimai uždaryti"
+ "Naudokite užduočių juostą, kad gal. perjungti programas"
+ "Nuvilkite į šoną, kad gal. vienu metu naudoti dvi programas"
+ "Palieskite ir palaikykite, kad paslėptumėte užduočių juostą"
+ "Kitas"
+ "Atgal"
+ "Uždaryti"
+ "Atlikta"
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index ce941bc3d9..2f372bcdf2 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -25,11 +25,12 @@
"Lietotņu izmantošanas iestatījumi"
"Notīrīt visu"
"Pēdējās izmantotās lietotnes"
+
+
"%1$s, %2$s"
"<1 minūte"
"Šodien atlicis: %1$s"
"Ieteicamās lietotnes"
- "Visas lietotnes"
"Jūsu prognozētās lietotnes"
"Sākuma ekrāna apakšējā rindā tiks rādītas ieteicamās lietotnes"
"Saņemiet lietotņu ieteikumus izlases rindā sākuma ekrānā"
@@ -76,9 +77,22 @@
"%1$d. mācību darbība no %2$d"
"Kopīgot"
"Veikt ekrānuzņēmumu"
+ "Sadalīt"
+ "Piesk. citai lietotnei, lai izm. ekrāna sadalīšanu"
+ "Lietotnē netiek atbalstīta ekrāna sadalīšana."
"Lietotne vai jūsu organizācija neatļauj veikt šo darbību."
"Vai izlaist navigācijas mācības?"
"Varēsiet to vēlāk atrast lietotnē %1$s."
"Atcelt"
"Izlaist"
+ "Pagriezt ekrānu"
+ "Tika atvērta uzdevumjoslas apmācība"
+ "Tika aizvērta uzdevumjoslas apmācība"
+ "Izmantojiet uzdevumjoslu, lai pārslēgtu lietotnes."
+ "Velciet uz malu, lai izmantotu divas lietotnes vienlaikus."
+ "Pieskarieties un turiet, lai paslēptu uzdevumjoslu."
+ "Tālāk"
+ "Atpakaļ"
+ "Aizvērt"
+ "Gatavs"
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index f9fd8b3d06..c060e70a5d 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -25,11 +25,12 @@
"Поставки за користење на апликациите"
"Избриши ги сите"
"Неодамнешни апликации"
+
+
"%1$s, %2$s"
"< 1 минута"
"Уште %1$s за денес"
"Предлози за апликации"
- "Сите апликации"
"Вашите предвидени апликации"
"Добивајте предлози за апликации на долниот ред од почетниот екран"
"Добивајте предлози за апликации во редот со омилени на почетниот екран"
@@ -76,9 +77,22 @@
"Упатство %1$d/%2$d"
"Сподели"
"Слика од екранот"
+ "Раздели"
+ "Допрете друга апликација за да користите поделен екран"
+ "Апликацијата не поддржува поделен екран."
"Апликацијата или вашата организација не го дозволува дејствово"
"Да се прескокне упатството за навигација?"
"Ова може да го најдете подоцна во апликацијата %1$s"
"Откажи"
"Прескокни"
+ "Ротирајте го екранот"
+ "Се појави лентата за задачи за образование"
+ "Затворена е лентата за задачи за образование"
+ "Префрлувајте се меѓу апликации преку лентата за задачи"
+ "Повлечете кон страната за да користите две апликации одеднаш"
+ "Допрете и задржете за да се сокрие лентата за задачи"
+ "Следна"
+ "Назад"
+ "Затвори"
+ "Готово"
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index fc6bc82d01..404b4d4f37 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -25,11 +25,12 @@
"ആപ്പ് ഉപയോഗ ക്രമീകരണം"
"എല്ലാം മായ്ക്കുക"
"സമീപകാല ആപ്പുകൾ"
+
+
"%1$s, %2$s"
"< 1 മിനിറ്റ്"
"ഇന്ന് %1$s ശേഷിക്കുന്നു"
"ആപ്പ് നിർദ്ദേശങ്ങൾ"
- "എല്ലാ ആപ്പുകളും"
"നിങ്ങളുടെ പ്രവചിക്കപ്പെട്ട ആപ്പുകൾ"
"നിങ്ങളുടെ ഹോം സ്ക്രീനിന്റെ താഴത്തെ നിരയിൽ ആപ്പ് നിർദ്ദേശങ്ങൾ നേടുക"
"നിങ്ങളുടെ ഹോം സ്ക്രീനിന്റെ \'പ്രിയപ്പെട്ടവ\' വരിയിൽ ആപ്പ് നിർദ്ദേശങ്ങൾ നേടുക"
@@ -76,9 +77,22 @@
"ട്യൂട്ടോറിയൽ %1$d/%2$d"
"പങ്കിടുക"
"സ്ക്രീൻഷോട്ട്"
+ "വിഭജിക്കുക"
+ "സ്പ്ലിറ്റ് സ്ക്രീനിനായി മറ്റൊരു ആപ്പ് ടാപ്പുചെയ്യൂ"
+ "സ്പ്ലിറ്റ്-സ്ക്രീനിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല."
"ഈ നടപടി എടുക്കുന്നത് ആപ്പോ നിങ്ങളുടെ സ്ഥാപനമോ അനുവദിക്കുന്നില്ല"
"നാവിഗേഷൻ ട്യൂട്ടോറിയൽ ഒഴിവാക്കണോ?"
"%1$s ആപ്പിൽ നിങ്ങൾക്ക് ഇത് പിന്നീട് കാണാനാകും"
"റദ്ദാക്കുക"
"ഒഴിവാക്കുക"
+ "സ്ക്രീൻ റൊട്ടേറ്റ് ചെയ്യുക"
+ "ടാസ്ക്ക്ബാർ വിവര പാനൽ ദൃശ്യമായി"
+ "ടാസ്ക്ക്ബാർ വിവര പാനൽ അടച്ചു"
+ "ആപ്പുകൾ മാറാൻ ടാസ്ക്ക്ബാർ ഉപയോഗിക്കുക"
+ "രണ്ട് ആപ്പുകൾ ഒരുമിച്ച് ഉപയോഗിക്കാൻ അരികിലേക്ക് വലിച്ചിടുക"
+ "ടാസ്ക്ക്ബാർ മറയ്ക്കാൻ സ്പർശിച്ച് പിടിക്കുക"
+ "അടുത്തത്"
+ "മടങ്ങുക"
+ "അടയ്ക്കുക"
+ "പൂർത്തിയായി"
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 48513eb928..eba0ad056f 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -25,11 +25,12 @@
"Апп ашиглалтын тохиргоо"
"Бүгдийг устгах"
"Саяхны аппууд"
+
+
"%1$s, %2$s"
"< 1 минут"
"Өнөөдөр %1$s үлдсэн"
"Санал болгож буй аппууд"
- "Бүх апп"
"Таны таамагласан аппууд"
"Үндсэн нүүрнийхээ доод мөрөнд санал болгож буй аппуудыг аваарай"
"Үндсэн нүүрний дуртай мөрнөөсөө санал болгож буй аппуудыг аваарай"
@@ -76,9 +77,22 @@
"%1$d/%2$d практик хичээл"
"Хуваалцах"
"Дэлгэцийн агшин дарах"
+ "Хуваах"
+ "Дэлгэц хуваахыг ашиглах бол өөр аппыг товшино уу"
+ "Апп дэлгэцийг хуваах горимыг дэмждэггүй."
"Энэ үйлдлийг апп эсвэл танай байгууллага зөвшөөрдөггүй"
"Навигацын практик хичээлийг алгасах уу?"
"Та үүнийг дараа нь %1$s аппаас олох боломжтой"
"Цуцлах"
"Алгасах"
+ "Дэлгэцийг эргүүлэх"
+ "Боловсролын ажлын талбар гарч ирсэн"
+ "Боловсролын ажлын талбарыг хаасан"
+ "Аппуудыг сэлгэхийн тулд талбарыг ашиглана уу"
+ "Хоёр аппыг зэрэг ашиглахын тулд хажуу тал руу чирнэ үү"
+ "Ажлын талбарыг нуухын тулд хүрээд удаан дарна уу"
+ "Дараах"
+ "Буцах"
+ "Хаах"
+ "Дууссан"
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index c576cf139d..fe8335026a 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -25,11 +25,12 @@
"अॅप वापर सेटिंग्ज"
"सर्व साफ करा"
"अलीकडील अॅप्स"
+
+
"%1$s, %2$s"
"१मिहून कमी"
"आज %1$sशिल्लक आहे"
"अॅप सूचना"
- "सर्व अॅप्स"
"तुमची पूर्वानुमानीत अॅप्स"
"तुमच्या होम स्क्रीनच्या तळाशी अॅप सूचना मिळवा"
"तुमच्या होम स्क्रीनच्या पसंतीच्या पंक्तीवर अॅप सूचना मिळवा"
@@ -45,12 +46,10 @@
"अॅप सूचना सुरू केल्या आहेत"
"अॅप सूचना बंद केल्या आहेत"
"पूर्वानुमान केलेले अॅप: %1$s"
- "तुम्ही स्क्रीनच्या अगदी डाव्या कडेपासून स्वाइप करत आहात याची खात्री करा."
- "तुम्ही स्क्रीनच्या डाव्या कडेपासून मध्यावर स्वाइप करून बोट उचलत आहात याची खात्री करा."
- "पूर्ण झाले! आता उजव्या कोपऱ्यावरून स्वाइप करून पहा."
- "तुम्ही स्क्रीनच्या अगदी उजव्या कडेपासून स्वाइप करत आहात याची खात्री करा."
- "तुम्ही स्क्रीनच्या उजव्या कडेपासून मध्यावर स्वाइप करून बोट उचलत आहात याची खात्री करा."
- "तुम्ही गो बॅक जेश्चर पूर्ण केले. आता, होम वर कसे जायचे ते जाणून घ्या."
+ "तुम्ही स्क्रीनच्या अगदी उजव्या किंवा अगदी डाव्या कडेला स्वाइप केल्याची खात्री करा."
+ "तुम्ही स्क्रीनच्या उजव्या किंवा डाव्या कडेपासून मध्यभागी स्वाइप करून सोडून दिल्याची खात्री करा."
+ "मागे जाण्यासाठी उजवीकडून कसे स्वाइप करावे ते शिकलात. आता पुढे, ॲप्स कशी स्विच करायची ते जाणून घ्या."
+ "तुम्ही गो बॅक जेश्चर पूर्ण केले."
"तुम्ही स्क्रीनच्या तळाच्या अगदी जवळून स्वाइप करत नाही याची खात्री करा."
"बॅक जेश्चरची संवेदनशीलता बदलण्यासाठी, सेटिंग्ज वर जा"
"परत जाण्यासाठी स्वाइप करा"
@@ -58,27 +57,45 @@
"तुम्ही स्क्रीनच्या तळाच्या कडेपासून वर स्वाइप करत आहात याची खात्री करा."
"तुम्ही स्क्रीनवरून बोट उचलण्यापूर्वी ते थांबवत नाही याची खात्री करा."
"तुम्ही सरळ वर स्वाइप करत आहात याची खात्री करा."
- "तुम्ही गो होम जेश्चर पूर्ण केले. आता, ॲप्स कशी स्विच करायची ते जाणून घ्या."
+ "तुम्ही गो होम जेश्चर पूर्ण केले. आता, मागे कसे जायचे ते जाणून घ्या."
+ "तुम्ही गो होम जेश्चर पूर्ण केले."
"होमवर जाण्यासाठी स्वाइप करा"
"तुमच्या स्क्रीनच्या तळाकडून वर स्वाइप करा. हे जेश्चर तुम्हाला नेहमी होम स्क्रीनवर घेऊन जाते."
"तुम्ही स्क्रीनच्या तळाच्या कडेपासून वर स्वाइप करत आहात याची खात्री करा."
"विंडोवरून बोट उचलण्यापूर्वी थोडा वेळ ते तेथेच धरून ठेवा."
"तुम्ही सरळ वर स्वाइप करून, त्यानंतर बोट थांबवत आहात याची खात्री करा."
- "तुम्ही स्विच ॲप्स जेश्चर पूर्ण केले. तुम्ही तुमचा फोन वापरण्यासाठी तयार आहात!"
+ "तुम्ही जेश्चर कसे वापरायचे हे शिकलात. जेश्चर बंद करण्यासाठी, सेटिंग्ज वर जा."
+ "तुम्ही ॲप्स स्विच करण्याचे जेश्चर पूर्ण केले."
"अॅप्स स्विच करण्यासाठी स्वाइप करा"
- "तुमच्या स्क्रीनच्या तळाकडून वर स्वाइप करा, धरून ठेवा, त्यानंतर बोट उचला."
+ "ॲप्सदरम्यान स्विच करण्यासाठी, स्क्रीनच्या तळापासून वर स्वाइप करा, धरून ठेवा, त्यानंतर सोडून द्या."
"सर्व तयार आहे"
- "पुढील"
- "पूर्ण झाले"
+ "पूर्ण झाले"
"सेटिंग्ज"
"पुन्हा प्रयत्न करा"
"छान!"
- "ट्युटोरियल %1$d/%2$d"
+ "ट्यूटोरियल %1$d/%2$d"
+ "सर्व तयार आहे!"
+ "होम वर जाण्यासाठी वरती स्वाइप करा"
+ "तुम्ही तुमचा फोन वापरण्यास सुरुवात करू शकता"
+ "सिस्टम नेव्हिगेशन सेटिंग्ज"
"शेअर करा"
"स्क्रीनशॉट"
+ "स्प्लिट"
+ "स्प्लिटस्क्रीन वापरण्यासाठी दुसऱ्या ॲपवर टॅप करा"
+ "अॅप हे स्प्लिट-स्क्रीनला सपोर्ट करत नाही."
"अॅप किंवा तुमच्या संस्थेद्वारे ही क्रिया करण्याची अनुमती नाही"
- "नेव्हिगेशन ट्युटोरियल वगळायचे आहे का?"
+ "नेव्हिगेशन ट्यूटोरियल वगळायचे आहे का?"
"तुम्हाला हे नंतर %1$s ॲपमध्ये मिळेल"
"रद्द करा"
"वगळा"
+ "स्क्रीन फिरवा"
+ "टास्कबारशी संबंधित माहिती देणारे पॅनल उघडले आहे"
+ "टास्कबारशी संबंधित माहिती देणारे पॅनल बंद केले आहे"
+ "ॲप्स स्विच करण्यासाठी टास्कबार वापरा"
+ "दोन ॲप्स एकत्र वापरण्यासाठी, त्यांना बाजूला नेऊन ड्रॅग करा"
+ "टास्कबार लपवण्यासाठी स्पर्श करा आणि धरून ठेवा"
+ "पुढे"
+ "मागे जा"
+ "बंद करा"
+ "पूर्ण झाले"
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index 74b9d086e8..4e625c3354 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -25,11 +25,12 @@
"Tetapan penggunaan apl"
"Kosongkan semua"
"Apl terbaharu"
+
+
"%1$s, %2$s"
"< 1 minit"
"%1$s lagi hari ini"
"Cadangan apl"
- "Semua apl"
"Apl ramalan anda"
"Dapatkan cadangan apl di baris bawah Skrin utama anda"
"Dapatkan cadangan apl di baris kegemaran Skrin utama anda"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Kongsi"
"Tangkapan skrin"
+ "Pisah"
+ "Ketik apl lain untuk menggunakan skrin pisah"
+ "Apl tidak menyokong skrin pisah."
"Tindakan ini tidak dibenarkan oleh apl atau organisasi anda"
"Langkau tutorial navigasi?"
"Anda boleh mendapatkan tutorial ini kemudian dalam apl %1$s"
"Batal"
"Langkau"
+ "Putar skrin"
+ "Pendidikan bar tugas muncul"
+ "Pendidikan bar tugas ditutup"
+ "Gunakan bar tugas untuk menukar apl"
+ "Seret ke tepi untuk menggunakan dua apl serentak"
+ "Sentuh & tahan untuk menyembunyikan bar tugas"
+ "Seterusnya"
+ "Kembali"
+ "Tutup"
+ "Selesai"
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index 919ad6efa7..803319da62 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -23,13 +23,14 @@
"အလွတ်ပုံစံ"
"မကြာမီကဖွင့်ထားသည်များ မရှိပါ"
"အက်ပ်အသုံးပြုမှု ဆက်တင်များ"
- "အားလုံးကို ရှင်းရန်"
+ "အားလုံးရှင်းရန်"
"လတ်တလောသုံး အက်ပ်များ"
+
+
"%1$s၊ %2$s"
"< ၁ မိနစ်"
"ယနေ့ %1$s ခု ကျန်သည်"
"အက်ပ်အကြံပြုချက်များ"
- "အက်ပ်အားလုံး"
"သင်၏ ခန့်မှန်းအက်ပ်များ"
"သင်၏ \'ပင်မစာမျက်နှာ\' အောက်ခြေအတန်းတွင် အက်ပ်အကြံပြုချက်များ ရယူခြင်း"
"သင်၏ \'ပင်မစာမျက်နှာ\' ၏ အနှစ်သက်ဆုံးများအတန်းတွင် အက်ပ်အကြံပြုချက်များ ရယူခြင်း"
@@ -45,12 +46,10 @@
"အက်ပ်အကြံပြုချက်များ ဖွင့်ထားသည်"
"အက်ပ်အကြံပြုချက်များကို ပိတ်ထားသည်"
"ကြိုတင်မှန်းဆထားသော အက်ပ်− %1$s"
- "ဘယ်ဘက်အစွန် ခပ်လှမ်းလှမ်းမှ ပွတ်ဆွဲကြောင်း သေချာပါစေ။"
- "ဘယ်ဘက်အစွန်မှ ဖန်သားပြင်အလယ်သို့ ပွတ်ဆွဲပြီး လွှတ်လိုက်ကြောင်း သေချာပါစေ။"
- "ရပါပြီ။ ညာဘက်အစွန်မှနေ၍ ယခု ပွတ်ဆွဲကြည့်ပါ။"
- "ညာဘက်အစွန် ခပ်လှမ်းလှမ်းမှ ပွတ်ဆွဲကြောင်း သေချာပါစေ။"
- "ညာဘက်အစွန်မှ ဖန်သားပြင်အလယ်သို့ ပွတ်ဆွဲပြီး လွှတ်လိုက်ကြောင်း သေချာပါစေ။"
- "နောက်ဆုတ်လက်ဟန် ရှင်းလင်းပို့ချချက် ပြီးပါပြီ။ နောက်အဆင့်တွင် ပင်မစာမျက်နှာသို့ သွားနည်းကို လေ့လာပါ။"
+ "ညာ သို့မဟုတ် ဘယ်ဘက်အစွန် ခပ်လှမ်းလှမ်းမှ ပွတ်ဆွဲကြောင်း သေချာပါစေ။"
+ "စခရင်၏ ညာ သို့မဟုတ် ဘက်ဘက်အစွန်မှ အလယ်သို့ ပွတ်ဆွဲပြီး လွှတ်လိုက်ကြောင်း သေချာပါစေ။"
+ "နောက်ပြန်သွားရန် ညာဘက်မှပွတ်ဆွဲနည်းကို သိသွားပါပြီ။ နောက်အဆင့်တွင် အက်ပ်များပြောင်းနည်းကို လေ့လာပါ။"
+ "နောက်ဆုတ်လက်ဟန် ရှင်းလင်းပို့ချချက် ပြီးပါပြီ။"
"ဖန်သားပြင် အောက်ခြေနှင့် အလွန်နီးကပ်စွာ ပွတ်ဆွဲခြင်းမရှိကြောင်း သေချာပါစေ။"
"နောက်ဆုတ်လက်ဟန်၏ အာရုံခံစွမ်းကိုပြောင်းရန် ‘ဆက်တင်များ’ သို့ သွားပါ"
"နောက်ပြန်သွားရန် ပွတ်ဆွဲပါ"
@@ -58,27 +57,45 @@
"ဖန်သားပြင် အောက်ခြေအစွန်မှ အပေါ်သို့ ပွတ်ဆွဲကြောင်း သေချာပါစေ။"
"လက်မလွှတ်ခင် ခဏရပ်ခြင်းမရှိကြောင်း သေချာပါစေ။"
"အပေါ်တည့်တည့်သို့ ပွတ်ဆွဲကြောင်း သေချာပါစေ။"
- "ပင်မစာမျက်နှာသို့သွားသည့် လက်ဟန် ရှင်းလင်းပို့ချချက် ပြီးပါပြီ။ နောက်အဆင့်တွင် အက်ပ်များပြောင်းနည်းကို လေ့လာပါ။"
+ "ပင်မစာမျက်နှာသို့သွားသည့် လက်ဟန် ရှင်းလင်းပို့ချချက် ပြီးပါပြီ။ နောက်အဆင့်တွင် နောက်သို့ပြန်သွားနည်းကို လေ့လာပါ။"
+ "ပင်မစာမျက်နှာသို့သွားသည့် လက်ဟန် ရှင်းလင်းပို့ချချက် ပြီးပါပြီ။"
"ပင်မစာမျက်နှာသို့သွားရန် ပွတ်ဆွဲပါ"
"သင့်ဖန်သားပြင် အောက်ခြေမှ အပေါ်သို့ပွတ်ဆွဲပါ။ ဤလက်ဟန်ဖြင့် ပင်မစာမျက်နှာသို့ အမြဲပြန်သွားနိုင်သည်။"
"ဖန်သားပြင် အောက်ခြေအစွန်မှ အပေါ်သို့ ပွတ်ဆွဲကြောင်း သေချာပါစေ။"
"မလွှတ်ခင် ဝင်းဒိုးကို အချိန်ကြာကြာ ဖိထားကြည့်ပါ။"
"အပေါ်တည့်တည့်သို့ ပွတ်ဆွဲပြီးနောက် ခဏရပ်ကြောင်း သေချာပါစေ။"
- "အက်ပ်များပြောင်းသည့် လက်ဟန် ရှင်းလင်းပို့ချချက် ပြီးပါပြီ။ သင့်ဖုန်းကို သုံးရန် အဆင်သင့်ဖြစ်ပါပြီ။"
+ "လက်ဟန်များသုံးနည်းကို သင်သိသွားပါပြီ။ လက်ဟန်များကို ပိတ်ရန် ဆက်တင်များသို့ သွားပါ။"
+ "အက်ပ်များပြောင်းသည့် လက်ဟန် ရှင်းလင်းပို့ချချက် ပြီးပါပြီ။"
"အက်ပ်များပြောင်းရန် ပွတ်ဆွဲပါ"
- "သင့်ဖန်သားပြင် အောက်ခြေမှ အပေါ်သို့ ပွတ်ဆွဲပါ၊ ဖိထားပြီးနောက် လွှတ်လိုက်ပါ။"
+ "အက်ပ်တစ်ခုမှတစ်ခုသို့ ပြောင်းရန် စခရင်အောက်ခြေမှ အပေါ်သို့ ပွတ်ဆွဲ၍ ဖိထားပြီးနောက် လွှတ်ပါ။"
"အားလုံးအဆင်သင့်ဖြစ်ပါပြီ"
- "ရှေ့သို့"
- "ပြီးပြီ"
+ "ပြီးပြီ"
"ဆက်တင်များ"
"ထပ်စမ်းကြည့်ရန်"
"ကောင်းသည်။"
"ရှင်းလင်းပို့ချချက် %1$d/%2$d"
+ "အားလုံး အဆင်သင့်ပါ။"
+ "ပင်မစာမျက်နှာသို့သွားရန် အပေါ်သို့ ပွတ်ဆွဲပါ"
+ "သင့်ဖုန်းကို စတင်အသုံးပြုရန် အသင့်ဖြစ်ပါပြီ"
+ "စနစ် လမ်းညွှန် ဆက်တင်များ"
"မျှဝေရန်"
"ဖန်သားပြင်ဓာတ်ပုံ"
+ "ခွဲထုတ်ရန်"
+ "မျက်နှာပြင်ခွဲ၍ပြသရန် အက်ပ်နောက်တစ်ခုကို တို့ပါ"
+ "အက်ပ်တွင် မျက်နှာပြင် ခွဲ၍ပြသခြင်း သုံး၍မရပါ။"
"ဤလုပ်ဆောင်ချက်ကို အက်ပ် သို့မဟုတ် သင်၏အဖွဲ့အစည်းက ခွင့်မပြုပါ"
"လမ်းညွှန်ခြင်း ရှင်းလင်းပို့ချချက်ကို ကျော်မလား။"
"၎င်းကို နောက်မှ %1$s အက်ပ်တွင် ရှာနိုင်သည်"
"မလုပ်တော့"
"ကျော်ရန်"
+ "ဖန်သားပြင်လှည့်ရန်"
+ "ပညာရေး လုပ်ဆောင်စရာဘား ပြထားသည်"
+ "ပညာရေး လုပ်ဆောင်စရာဘား ပိတ်ထားသည်"
+ "အက်ပ်များပြောင်းရန် လုပ်ဆောင်စရာဘားကို သုံးပါ"
+ "အက်ပ်နှစ်ခု တစ်ပြိုင်တည်းသုံးရန် တစ်ဖက်သို့ ဖိဆွဲပါ"
+ "လုပ်ဆောင်စရာဘားကို ဖျောက်ရန် ထိပြီးဖိထားနိုင်သည်"
+ "ရှေ့သို့"
+ "နောက်သို့"
+ "ပိတ်ရန်"
+ "ပြီးပြီ"
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index 6e7e4a3cf7..edaf75ac5d 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -25,11 +25,12 @@
"Innstillinger for appbruk"
"Fjern alt"
"Nylige apper"
+
+
"%1$s, %2$s"
"< 1 minutt"
"%1$s gjenstår i dag"
"Appanbefalinger"
- "Alle apper"
"Forslag til apper"
"Få appforslag i den nederste raden på startskjermen"
"Få appforslag i favoritter-raden på startskjermen"
@@ -76,9 +77,22 @@
"Veiledning %1$d/%2$d"
"Del"
"Skjermdump"
+ "Del opp"
+ "Trykk på en annen app for å bruke delt skjerm"
+ "Appen støtter ikke delt skjerm."
"Appen eller organisasjonen din tillater ikke denne handlingen"
"Vil du hoppe over navigeringsveiledning?"
"Du kan finne dette i %1$s-appen senere"
"Avbryt"
"Hopp over"
+ "Rotér skjermen"
+ "Opplæringen for oppgavelinjen vises"
+ "Opplæringen for oppgavelinjen er lukket"
+ "Bruk oppgavelinjen for å bytte app"
+ "Dra til siden for å bruke to apper samtidig"
+ "Trykk og hold for å skjule oppgavelinjen"
+ "Neste"
+ "Tilbake"
+ "Lukk"
+ "Ferdig"
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 99ad8143e9..2b0cbdb9ae 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -23,94 +23,79 @@
"फ्रिफर्म"
"हालसालैको कुनै पनि वस्तु छैन"
"एपको उपयोगका सेटिङहरू"
- "सबै खाली गर्नुहोस्"
+ "सबै मेटाउनुहोस्"
"हालसालैका एपहरू"
+
+
"%1$s, %2$s"
"< १ मिनेट"
"आज: %1$s बाँकी"
"एपसम्बन्धी सुझावहरू"
- "सबै एपहरू"
"तपाईंलाई चाहिने एपहरू"
- "तपाईंको गृह स्क्रिनको पुछारको पङ्क्तिमा सिफारिस गरिएका एपहरू प्राप्त गर्नुहोस्"
- "आफ्नो होम स्क्रिनको मन पर्ने नामक पङ्क्तिमा सिफारिस गरिएका एपहरू प्राप्त गर्नुहोस्"
- "गृह स्क्रिनबाटै आफूले सबैभन्दा बढी प्रयोग गर्ने एप सजिलै चलाउनुहोस्। सिफारिस गरिने एपहरूको क्रम तपाईंले एप प्रयोग गर्ने समयतालिकाअनुसार बदलिने छ। फेदको पङ्क्तिमा रहेका एपहरू तपाईंको गृह स्क्रिनको सिरानमा सर्ने छन्।"
+ "आफ्नो होम स्क्रिनको पुछारको रोमा एपसम्बन्धी सिफारिस प्राप्त गर्नुहोस्"
+ "आफ्नो होम स्क्रिनको मन पर्ने नामक पङ्क्तिमा एपसम्बन्धी सिफारिस प्राप्त गर्नुहोस्"
+ "आफूले सबैभन्दा बढी प्रयोग गर्ने एप होम स्क्रिनबाट सजिलै चलाउनुहोस्। सिफारिस गरिने एपहरूको क्रम तपाईंले एप प्रयोग गर्ने समयतालिकाअनुसार बदलिने छ। फेदको रोमा रहेका एपहरू तपाईंको होम स्क्रिनको सिरानमा सर्ने छन्।"
"आफूले सबैभन्दा बढी प्रयोग गर्ने एपहरू गृह स्क्रिनबाटै सजिलैसँग खोल्नुहोस्। सिफारिस गरिने एपहरूको क्रम तपाईंको दिनचर्याअनुसार बदलिने छ। मन पर्ने नामक पङ्क्तिमा रहेका एपहरू सारेर होम स्क्रिनमा लगिने छन्।"
"गृह स्क्रिनबाटै आफूले सबैभन्दा बढी प्रयोग गर्ने एप सजिलै चलाउनुहोस्। सिफारिस गरिने एपहरूको क्रम तपाईंले एप प्रयोग गर्ने समयतालिकाअनुसार बदलिने छ। फेदको पङ्क्तिमा रहेका एपहरू एउटा नयाँ फोल्डरमा सर्ने छन्।"
- "सिफारिस गरिएका एपहरू प्राप्त गर्नुहोस्"
+ "एपसम्बन्धी सिफारिस प्राप्त गर्नुहोस्"
"पर्दैन धन्यवाद"
"सेटिङ"
"सबैभन्दा बढी प्रयोग हुने एपहरू यहाँ देखिन्छन् र यी एपहरूको क्रम तपाईंले एप प्रयोग गर्ने समयतालिकाअनुसार बदलिरहन्छ"
- "सिफारिस गरिएका एपहरू प्राप्त गर्न फेदको पङ्क्तिमा रहेका एपहरू ड्र्याग गरी हटाउनुहोस्"
+ "एपसम्बन्धी सिफारिस प्राप्त गर्न फेदको रोमा रहेका एपहरू ड्र्याग गरी हटाउनुहोस्"
"खाली ठाउँमा सिफारिस गरिएका एपहरू थपिए"
"सिफारिस गरिएका एपहरू देखाउने सुविधा सक्षम पारिएका छन्"
"सिफारिस गरिएका एपहरू देखाउने सुविधा असक्षम पारिएको छ"
"पूर्वानुमान गरिएको एप: %1$s"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ "स्क्रिनको सबैभन्दा दायाँ किनारा वा सबैभन्दा बायाँ किनाराबाट स्वाइप गर्नुहोस्।"
+ "स्क्रिनको दायाँ वा बायाँ किनाराबाट मध्य भागसम्म स्वाइप गर्नुहोस् अनि औँला उठाउनुहोस्।"
+ "तपाईंले स्क्रिनको दायाँ किनाराबाट स्वाइप गरेर अघिल्लो स्क्रिनमा फर्कने तरिका सिक्नुभयो। अब एउटा एपबाट अर्को एपमा जाने तरिका सिक्नुहोस्।"
+ "तपाईंले \'पछाडि जानुहोस्\' नामक इसारा प्रयोग गर्ने तरिका सिक्नुभयो।"
+ "स्क्रिनको फेदको धेरै नजिकसम्म स्वाइप नगर्नुहोस्।"
+ "\'पछाडि\' नामक इसाराको संवेदनशीलता बदल्न सेटिङमा जानुहोस्"
+ "पछाडि जान स्वाइप गर्नुहोस्"
+ "यसअघिको स्क्रिनमा फर्कन स्क्रिनको बायाँ वा दायाँ किनाराबाट मध्य भागसम्म स्वाइप गर्नुहोस्।"
+ "स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्।"
+ "औँला उठाउनुअघि नरोकिनुहोस्।"
+ "सीधै माथितिर स्वाइप गर्नुहोस्।"
+ "तपाईंले \'होम स्क्रिनमा जानुहोस्\' नामक इसारा प्रयोग गर्ने तरिका सिक्नुभयो। अब पछाडि जाने तरिका सिक्नुहोस्।"
+ "तपाईंले \'होम स्क्रिनमा जानुहोस्\' नामक इसारा प्रयोग गर्ने तरिका सिक्नुभयो।"
+ "होम स्क्रिनमा जान स्वाइप गर्नुहोस्"
+ "स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्। यो इसारा प्रयोग गर्दा सधैँ होम स्क्रिन खुल्छ।"
+ "स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्।"
+ "स्क्रिनबाट औँला उठाउनुअघि एपको विन्डोमा केही बेर छोइराख्नुहोस्।"
+ "सीधै माथितिर स्वाइप गर्नुहोस् अनि रोकिनुहोस्।"
+ "तपाईंले इसाराहरू प्रयोग गर्ने तरिका सिक्नुभयो। इसारा अफ गर्न सेटिङमा जानुहोस्।"
+ "तपाईंले \'एउटा एपबाट अर्को एपमा जानुहोस्\' नामक इसारा प्रयोग गर्ने तरिका सिक्नुभयो।"
+ "एउटा एपबाट अर्को एपमा जान स्वाइप गर्नुहोस्"
+ "एउटा एपबाट अर्कोमा जान स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्, छोइराख्नुहोस् अनि औँला उठाउनुहोस्।"
+ "सबै तयार छ"
+ "सम्पन्न भयो"
+ "सेटिङ"
+ "फेरि प्रयास गर्नुहोस्"
+ "राम्रो!"
+ "ट्युटोरियल %1$d/%2$d"
+ "सबै तयार भयो!"
+ "होममा जान माथितिर स्वाइप गर्नुहोस्"
+ "तपाईं आफ्नो फोन चलाउन थाल्न सक्नुहुन्छ"
+ "सिस्टम नेभिगेसनसम्बन्धी सेटिङ"
"सेयर गर्नुहोस्"
"स्क्रिनसट"
+ "स्प्लिट गर्नुहोस्"
+ "स्प्लिटक्रिन प्रयोग गर्न अर्को एपमा ट्याप गर्नुहोस्"
+ "यो एपको स्क्रिन विभाजन गर्न मिल्दैन।"
"यो एप वा तपाईंको सङ्गठनले यो कारबाही गर्ने अनुमति दिँदैन"
-
-
- "तपाईं पछि %1$s एपमा गई यो ट्युटोरियल भेट्टाउन सक्नुहुन्छ"
-
-
-
-
+ "नेभिगेसन ट्युटोरियल स्किप गर्ने हो?"
+ "तपाईं पछि %1$s नामक एपमा गई यो ट्युटोरियल भेट्टाउन सक्नुहुन्छ"
+ "रद्द गर्नुहोस्"
+ "स्किप गर्नु…"
+ "स्क्रिन घुमाउनुहोस्"
+ "टास्कबार एजुकेसन देखिएको छ"
+ "टास्कबार एजुकेसन बन्द गरिएको छ"
+ "एउटा एपबाट अर्को एपमा जान टास्कबार प्रयोग गर्नुहोस्"
+ "टास्कबार छेउतिर ड्र्याग गरेर एकै पटक दुई वटा एप चलाउनुहोस्"
+ "टास्कबार लुकाउन टास्कबार थिचिराख्नुहोस्"
+ "अर्को"
+ "पछाडि"
+ "बन्द गर्नुहोस्"
+ "सम्पन्न भयो"
diff --git a/quickstep/res/values-night/colors.xml b/quickstep/res/values-night/colors.xml
new file mode 100644
index 0000000000..af6e0647a1
--- /dev/null
+++ b/quickstep/res/values-night/colors.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ #99000000
+
+ #000000
+
+ #202124
+ #3c4043
+
+ #FF000000
+
+
\ No newline at end of file
diff --git a/quickstep/res/values-night/styles.xml b/quickstep/res/values-night/styles.xml
index 1bd3f5d138..e6b345010d 100644
--- a/quickstep/res/values-night/styles.xml
+++ b/quickstep/res/values-night/styles.xml
@@ -21,7 +21,7 @@
- @android:color/transparent
- false
- false
- - #FF000000
+ - @android:color/transparent
\ No newline at end of file
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index be9329f000..efa2e78295 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -25,11 +25,12 @@
"Instellingen voor app-gebruik"
"Alles wissen"
"Recente apps"
+
+
"%1$s, %2$s"
"< 1 minuut"
"Nog %1$s vandaag"
"App-suggesties"
- "Alle apps"
"Je voorspelde apps"
"App-suggesties ontvangen op de onderste rij van je startscherm"
"App-suggesties ontvangen op de rij met favorieten op het startscherm"
@@ -42,8 +43,8 @@
"De meestgebruikte apps worden hier weergegeven en kunnen veranderen op basis van je routines"
"Sleep apps weg van de onderste rij om app-suggesties te ontvangen"
"App-suggesties toegevoegd aan lege ruimte"
- "App-suggesties zijn ingeschakeld"
- "App-suggesties zijn uitgeschakeld"
+ "App-suggesties staan aan"
+ "App-suggesties staan uit"
"Voorspelde app: %1$s"
"Swipe helemaal vanaf de linkerrand."
"Swipe vanaf de linkerrand naar het midden van het scherm en laat los."
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Delen"
"Screenshot"
+ "Splitsen"
+ "Tik op nog een app om je scherm te splitsen"
+ "App ondersteunt geen gesplitst scherm."
"Deze actie wordt niet toegestaan door de app of je organisatie"
"Navigatietutorial overslaan?"
"Je vindt dit later terug in de app %1$s"
"Annuleren"
"Overslaan"
+ "Scherm draaien"
+ "Uitleg van taakbalk geopend"
+ "Uitleg van taakbalk gesloten"
+ "Gebruik de taakbalk om van app te wisselen"
+ "Sleep naar de zijkant om 2 apps tegelijk te gebruiken"
+ "Tik en houd vast om de taakbalk te verbergen"
+ "Volgende"
+ "Terug"
+ "Sluiten"
+ "Klaar"
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 8c3697296f..d2c3945c05 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -25,11 +25,12 @@
"ଆପ୍ ବ୍ୟବହାର ସେଟିଂସ୍"
"ସବୁ ଖାଲି କରନ୍ତୁ"
"ବର୍ତ୍ତମାନର ଆପ୍"
+
+
"%1$s %2$s"
"< 1 ମିନିଟ୍"
"ଆଜି %1$s ବାକି ଅଛି"
"ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକ"
- "ସମସ୍ତ ଆପ୍ସ"
"ଆପଣ ପୂର୍ବାନୁମାନ କରିଥିବା ଆପ୍ସ"
"ଆପଣଙ୍କ ମୂଳ ସ୍କ୍ରିନର ତଳ ଧାଡ଼ିରେ ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକ ପାଆନ୍ତୁ"
"ଆପଣଙ୍କ ମୂଳ ସ୍କ୍ରିନର ପସନ୍ଦର ଧାଡ଼ିରେ ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକ ପାଆନ୍ତୁ"
@@ -76,9 +77,22 @@
"ଟ୍ୟୁଟୋରିଆଲ୍ %1$d/%2$d"
"ସେୟାର୍ କରନ୍ତୁ"
"ସ୍କ୍ରିନସଟ୍"
+ "ସ୍ପ୍ଲିଟ୍"
+ "ସ୍ପ୍ଲିଟସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପରେ ଟାପ କର"
+ "ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନକୁ ଆପ ସମର୍ଥନ କରେ ନାହିଁ।"
"ଆପ୍ କିମ୍ବା ଆପଣଙ୍କ ସଂସ୍ଥା ଦ୍ୱାରା ଏହି କାର୍ଯ୍ୟକୁ ଅନୁମତି ଦିଆଯାଇ ନାହିଁ"
"ନାଭିଗେସନ୍ ଟ୍ୟୁଟୋରିଆଲକୁ ବାଦ୍ ଦେବେ?"
"ଆପଣ ପରେ ଏହାକୁ %1$s ଆପରେ ପାଇପାରିବେ"
"ବାତିଲ୍ କରନ୍ତୁ"
"ବାଦ୍ ଦିଅନ୍ତୁ"
+ "ସ୍କ୍ରିନ ଘୂରାନ୍ତୁ"
+ "ଟାସ୍କବାର୍ ଶିକ୍ଷା ଦେଖାଯାଇଛି"
+ "ଟାସ୍କବାର୍ ଶିକ୍ଷା ବନ୍ଦ ହୋଇଯାଇଛି"
+ "ଆପଗୁଡ଼ିକୁ ସ୍ୱିଚ କରିବା ପାଇଁ ଟାସ୍କବାର ବ୍ୟବହାର କରନ୍ତୁ"
+ "ଥରକେ ଦୁଇଟି ଆପ ବ୍ୟବହାର କରିବା ପାଇଁ ପାର୍ଶ୍ୱକୁ ଡ୍ରାଗ କରନ୍ତୁ"
+ "ଟାସ୍କବାରକୁ ଲୁଚାଇବା ପାଇଁ ସ୍ପର୍ଶ କରି ଧରି ରଖନ୍ତୁ"
+ "ପରବର୍ତ୍ତୀ"
+ "ପଛକୁ ଫେରନ୍ତୁ"
+ "ବନ୍ଦ କରନ୍ତୁ"
+ "ହୋଇଗଲା"
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 227c060230..75ae0f3320 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -25,11 +25,12 @@
"ਐਪ ਵਰਤੋਂ ਦੀਆਂ ਸੈਟਿੰਗਾਂ"
"ਸਭ ਕਲੀਅਰ ਕਰੋ"
"ਹਾਲੀਆ ਐਪਾਂ"
+
+
"%1$s, %2$s"
"< 1 ਮਿੰਟ"
"ਅੱਜ %1$s ਬਾਕੀ"
"ਐਪ ਸੁਝਾਅ"
- "ਸਾਰੀਆਂ ਐਪਾਂ"
"ਤੁਹਾਡੀਆਂ ਪੂਰਵ ਅਨੁਮਾਨਿਤ ਐਪਾਂ"
"ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ ਦੀ ਹੇਠਲੀ ਕਤਾਰ \'ਤੇ ਐਪ ਸੁਝਾਅ ਪ੍ਰਾਪਤ ਕਰੋ"
"ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ ਦੀ ਮਨਪਸੰਦ ਕਤਾਰ \'ਤੇ ਐਪ ਸੁਝਾਅ ਹਾਸਲ ਕਰੋ"
@@ -76,9 +77,22 @@
"ਟਿਊਟੋਰੀਅਲ %1$d/%2$d"
"ਸਾਂਝਾ ਕਰੋ"
"ਸਕ੍ਰੀਨਸ਼ਾਟ"
+ "ਸਪਲਿਟ"
+ "ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ \'ਤੇ ਟੈਪ ਕਰੋ"
+ "ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ।"
"ਐਪ ਜਾਂ ਤੁਹਾਡੀ ਸੰਸਥਾ ਵੱਲੋਂ ਇਸ ਕਾਰਵਾਈ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ"
"ਕੀ ਨੈਵੀਗੇਸ਼ਨ ਟਿਊਟੋਰੀਅਲ ਨੂੰ ਛੱਡਣਾ ਹੈ?"
"ਤੁਸੀਂ ਇਸਨੂੰ ਬਾਅਦ ਵਿੱਚ %1$s ਐਪ ਵਿੱਚ ਲੱਭ ਸਕਦੇ ਹੋ"
"ਰੱਦ ਕਰੋ"
"ਛੱਡੋ"
+ "ਸਕ੍ਰੀਨ ਘੁਮਾਓ"
+ "ਟਾਸਕਵਾਰ ਸਿੱਖਿਆ ਪੈਨਲ ਦਿਖਾਇਆ ਗਿਆ"
+ "ਟਾਸਕਵਾਰ ਸਿੱਖਿਆ ਪੈਨਲ ਬੰਦ ਕੀਤਾ ਗਿਆ"
+ "ਐਪਾਂ ਸਵਿੱਚ ਕਰਨ ਲਈ ਟਾਸਕਬਾਰ ਵਰਤੋ"
+ "ਇੱਕ ਵਾਰ ਵਿੱਚ ਦੋ ਐਪਾਂ ਵਰਤਣ ਲਈ ਉਨ੍ਹਾਂ ਨੂੰ ਸਾਈਡ ਵੱਲ ਘਸੀਟੋ"
+ "ਟਾਸਕਬਾਰ ਨੂੰ ਲੁਕਾਉਣ ਲਈ ਸਪਰਸ਼ ਕਰਕੇ ਰੱਖੋ"
+ "ਅੱਗੇ"
+ "ਪਿੱਛੇ"
+ "ਬੰਦ ਕਰੋ"
+ "ਹੋ ਗਿਆ"
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 61d9b9377a..0b80ce7d8c 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -25,11 +25,12 @@
"Ustawienia użycia aplikacji"
"Wyczyść wszystko"
"Ostatnie aplikacje"
+
+
"%1$s, %2$s"
"> 1 min"
"Na dziś zostało %1$s"
"Sugestie aplikacji"
- "Wszystkie aplikacje"
"Przewidywane aplikacje"
"Otrzymuj sugestie aplikacji w dolnym wierszu ekranu głównego"
"Otrzymuj sugestie aplikacji w wierszu ulubionych na ekranie głównym"
@@ -76,9 +77,22 @@
"Samouczek %1$d/%2$d"
"Udostępnij"
"Zrzut ekranu"
+ "Podziel"
+ "Kliknij drugą aplikację, aby podzielić ekran"
+ "Aplikacja nie obsługuje podzielonego ekranu."
"Nie możesz wykonać tego działania, bo nie zezwala na to aplikacja lub Twoja organizacja"
"Pominąć samouczek nawigacji?"
"Znajdziesz to później w aplikacji %1$s"
"Anuluj"
"Pomiń"
+ "Obróć ekran"
+ "Wskazówki na temat paska zadań zostały wyświetlone"
+ "Wskazówki na temat paska zadań zostały zamknięte"
+ "Używaj paska zadań, aby przełączać aplikacje"
+ "Przeciągnij w bok, aby używać 2 aplikacji jednocześnie"
+ "Naciśnij i przytrzymaj, aby ukryć pasek zadań"
+ "Dalej"
+ "Wstecz"
+ "Zamknij"
+ "Gotowe"
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index 0feead77b1..630a048e64 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -25,11 +25,12 @@
"Definições de utilização de aplicações"
"Limpar tudo"
"Apps recentes"
+
+
"%1$s, %2$s"
"< 1 minuto"
"Resta(m) %1$s hoje."
"Sugestões de apps"
- "Todas as apps"
"As suas apps previstas"
"Obtenha sugestões de apps na última fila do ecrã principal"
"Obtenha sugestões de apps na fila dos favoritos do ecrã principal"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Partilhar"
"Fazer captura de ecrã"
+ "Dividir"
+ "Toque noutra app para utilizar o ecrã dividido"
+ "A app não é compatível com o ecrã dividido."
"Esta ação não é permitida pela app ou a sua entidade."
"Ignorar o tutorial de navegação?"
"Pode encontrar isto mais tarde na app %1$s"
"Cancelar"
"Ignorar"
+ "Rodar ecrã"
+ "Informação da barra de tarefas apresentada"
+ "Informação da barra de tarefas fechada"
+ "Utilize a barra de ferramentas para alternar entre apps"
+ "Arraste para o lado para utilizar duas apps em simultâneo"
+ "Toque sem soltar para ocultar a barra de tarefas"
+ "Seguinte"
+ "Anterior"
+ "Fechar"
+ "Concluir"
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index f533bbcbc3..d1eef0e2a4 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -25,11 +25,12 @@
"Configurações de uso do app"
"Limpar tudo"
"Apps recentes"
+
+
"%1$s, %2$s"
"< 1 min"
"%1$s restante(s) hoje"
"Sugestões de apps"
- "Todos os apps"
"Suas predições de apps"
"Receba sugestões de apps na linha inferior da tela inicial"
"Receba sugestões de apps na linha \"Favoritos\" da tela inicial"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Compartilhar"
"Capturar tela"
+ "Dividir"
+ "Toque em outro app para dividir a tela"
+ "O app não tem suporte para a divisão de tela."
"Essa ação não é permitida pelo app ou pela organização"
"Pular o tutorial de navegação?"
"Veja o tutorial mais tarde no app %1$s"
"Cancelar"
"Pular"
+ "Girar a tela"
+ "As dicas sobre a barra de tarefas foram abertas"
+ "As dicas sobre a barra de tarefas foram fechadas"
+ "Use a barra de tarefas para alternar entre apps"
+ "Arraste para o lado e use dois apps ao mesmo tempo"
+ "Mantenha a barra de tarefas pressionada para ocultá-la"
+ "Próxima"
+ "Voltar"
+ "Fechar"
+ "Concluído"
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index 37d2935683..39020e8846 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -25,11 +25,12 @@
"Setări de utilizare a aplicației"
"Ștergeți tot"
"Aplicații recente"
+
+
"%1$s, %2$s"
"< 1 minut"
"Au mai rămas %1$s astăzi"
"Sugestii de aplicații"
- "Toate aplicațiile"
"Aplicațiile estimate"
"Primiți sugestii de aplicații în rândul de jos al ecranului de pornire"
"Primiți sugestii de aplicații în rândul de preferințe al ecranului de pornire"
@@ -76,9 +77,22 @@
"Tutorialul %1$d / %2$d"
"Distribuiți"
"Captură de ecran"
+ "Împărțit"
+ "Atingeți altă aplicație pentru a folosi ecranul împărțit"
+ "Aplicația nu acceptă ecranul împărțit."
"Această acțiune nu este permisă de aplicație sau de organizația dvs."
"Omiteți tutorialul de navigare?"
"Îl puteți găsi mai târziu în aplicația %1$s"
"Anulați"
"Omiteți"
+ "Rotiți ecranul"
+ "Panoul cu informații despre bara de activități s-a afișat"
+ "Panoul cu informații despre bara de activități s-a închis"
+ "Folosiți bara de activități ca să comutați între aplicații"
+ "Trageți în lateral ca să folosiți două aplicații deodată"
+ "Atingeți lung oricând pentru a ascunde bara de activități"
+ "Înainte"
+ "Înapoi"
+ "Închideți"
+ "Gata"
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 8d4c40a210..a505aea523 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -25,11 +25,12 @@
"Настройки использования приложения"
"Очистить все"
"Недавние приложения"
+
+
"%1$s: %2$s"
"< 1 мин."
"Осталось сегодня: %1$s"
"Рекомендуемые приложения"
- "Все приложения"
"Ваши рекомендуемые приложения"
"Рекомендуемые приложения будут появляться в нижнем ряду на главном экране"
"Рекомендуемые приложения будут появляться в разделе избранных на главном экране"
@@ -76,9 +77,22 @@
"Руководство (шаг %1$d из %2$d)"
"Поделиться"
"Скриншот"
+ "Разделить"
+ "Для разделения экрана нажмите на другое приложение."
+ "Приложение не поддерживает разделение экрана."
"Это действие заблокировано приложением или организацией."
"Пропустить руководство по жестам?"
"Его можно найти в приложении \"%1$s\"."
"Отмена"
"Пропустить"
+ "Повернуть экран"
+ "Обучение по работе с панелью задач показано"
+ "Обучение по работе с панелью задач скрыто"
+ "Используйте панель задач, чтобы переключать приложения."
+ "Перетащите в сторону, чтобы использовать два приложения сразу."
+ "Чтобы скрыть панель задач, коснитесь ее и удерживайте."
+ "Далее"
+ "Назад"
+ "Закрыть"
+ "Готово"
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index d526e139ee..f84244c334 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -25,11 +25,12 @@
"යෙදුම් භාවිත සැකසීම්"
"සියල්ල හිස් කරන්න"
"මෑත යෙදුම්"
+
+
"%1$s, %2$s"
"< 1 විනාඩියක්"
"අද %1$sක් ඉතුරුයි"
"යෙදුම් යෝජනා"
- "සියලු යෙදුම්"
"ඔබේ පුරෝකථන කළ යෙදුම්"
"ඔබගේ මුල් තිරයේ පහළ පේළියේ යෙදුම් යෝජනා ලබා ගන්න"
"ඔබේ මුල් තිරයේ ප්රියතම පේළියේ යෙදුම් යෝජනා ලබා ගන්න"
@@ -76,9 +77,22 @@
"නිබන්ධනය %1$d/%2$d"
"බෙදා ගන්න"
"තිර රුව"
+ "බෙදන්න"
+ "බෙදුම් තිරය භාවිත කිරීමට තවත් යෙදුමක් තට්ටු කරන්න"
+ "යෙදුම බෙදුම් තිරය සඳහා සහාය නොදක්වයි."
"මෙම ක්රියාව යෙදුම හෝ ඔබේ සංවිධානය මගින් ඉඩ නොදේ"
"නිබන්ධනය සංචාලනය මඟ හරින්නද?"
"ඔබට මෙය පසුව %1$s යෙදුම තුළ සොයා ගත හැකිය"
"අවලංගු කරන්න"
"මඟ හරින්න"
+ "තිරය කරකවන්න"
+ "කාර්ය තීරු අධ්යාපනය දිස් විය"
+ "කාර්ය තීරු අධ්යාපනය වසා ඇත"
+ "යෙදුම් මාරු කිරීමට කාර්ය තීරුව භාවිත කරන්න"
+ "එකවර යෙදුම් දෙකක් භාවිතා කිරීමට පැත්තට අදින්න"
+ "කාර්ය තීරුව සැඟවීමට ස්පර්ශ කර අල්ලා ගෙන සිටින්න"
+ "ඊළඟ"
+ "ආපසු"
+ "වසන්න"
+ "නිමයි"
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 24a4ac974f..1d4acca80f 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -25,11 +25,12 @@
"Nastavenia využívania aplikácie"
"Vymazať všetko"
"Nedávne aplikácie"
+
+
"%1$s, %2$s"
"Menej ako 1 minúta"
"Dnes ešte zostáva: %1$s"
"Návrhy aplikácií"
- "Všetky aplikácie"
"Vaše predpovedané aplikácie"
"Nechajte si v spodnom riadku na ploche zobrazovať návrhy aplikácií"
"Nechajte si na ploche na riadku obľúbených zobrazovať návrhy aplikácií"
@@ -76,9 +77,22 @@
"Návod %1$d/%2$d"
"Zdieľať"
"Snímka obrazovky"
+ "Rozdeliť"
+ "Rozdel. obrazovku spustíte klepnutím na inú aplik."
+ "Aplikácia nepodporuje rozdelenú obrazovku."
"Aplikácia alebo vaša organizácia túto akciu nepovoľuje"
"Chcete preskočiť návod na navigáciu?"
"Tento návod nájdete v aplikácii %1$s"
"Zrušiť"
"Preskočiť"
+ "Otočiť obrazovku"
+ "Zobrazila sa výuka k hlavnému panelu"
+ "Výuka k hlavnému panelu bola zatvorená"
+ "Aplikácie je možné prepínať pomocou panela úloh"
+ "Po presunutí na stranu je možné používať dve aplikácie naraz"
+ "Panel úloh skryjete pridržaním"
+ "Ďalej"
+ "Späť"
+ "Zavrieť"
+ "Hotovo"
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index fdc8a6a4b4..4e1f46d170 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -25,11 +25,12 @@
"Nastavitve uporabe aplikacij"
"Počisti vse"
"Nedavne aplikacije"
+
+
"%1$s, %2$s"
"< 1 min"
"Danes je ostalo še %1$s"
"Predlagane aplikacije"
- "Vse aplikacije"
"Predvidene aplikacije"
"Oglejte si predlagane aplikacije v spodnji vrstici začetnega zaslona"
"Prejemajte predloge aplikacij v vrstici s priljubljenimi na začetnem zaslonu"
@@ -76,9 +77,22 @@
"Vadnica %1$d/%2$d"
"Deli"
"Posnetek zaslona"
+ "Razdeli"
+ "Za uporabo razdeljenega zaslona se dotaknite še ene aplikacije."
+ "Aplikacija ne podpira načina razdeljenega zaslona."
"Aplikacija ali vaša organizacija ne dovoljuje tega dejanja"
"Želite preskočiti vadnico za krmarjenje?"
"To lahko pozneje najdete v aplikaciji %1$s."
"Prekliči"
"Preskoči"
+ "Sukanje zaslona"
+ "Poučni nasveti o opravilni vrstici so prikazani."
+ "Poučni nasveti o opravilni vrstici so zaprti."
+ "Za preklop aplikacij uporabite opravilno vrstico."
+ "Povlecite vstran za uporabo dveh aplikacij hkrati."
+ "Pridržite, če želite opravilno vrstico skriti."
+ "Naprej"
+ "Nazaj"
+ "Zapri"
+ "Končano"
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 36e0afc371..33b4495828 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -25,11 +25,12 @@
"Cilësimet e përdorimit të aplikacionit"
"Pastroji të gjitha"
"Aplikacionet e fundit"
+
+
"%1$s, %2$s"
"< 1 minutë"
"%1$s të mbetura sot"
"Aplikacionet e sugjeruara"
- "Të gjitha aplikacionet"
"Aplikacionet e tua të parashikuara"
"Merr aplikacione të sugjeruara në rreshtin e poshtëm të ekranit tënd bazë"
"Merr aplikacione të sugjeruara në rreshtin e të preferuarave të ekranit tënd bazë"
@@ -76,9 +77,22 @@
"Udhëzuesi %1$d/%2$d"
"Ndaj"
"Pamja e ekranit"
+ "Ndaj"
+ "Trokit aplikacion tjetër e përdor ekranin e ndarë"
+ "Aplikacioni nuk mbështet ekranin e ndarë."
"Ky veprim nuk lejohet nga aplikacioni ose organizata jote"
"Të kapërcehet udhëzuesi i navigimit?"
"Këtë mund ta gjesh më vonë tek aplikacioni %1$s"
"Anulo"
"Kapërce"
+ "Rrotullo ekranin"
+ "Edukimi i shiritit të detyrave u shfaq"
+ "Edukimi nga shiriti i detyrave u mbyll"
+ "Përdor shiritin e detyrave për të ndryshuar aplikacionet"
+ "Zvarrit anash për të përdorur të dyja aplikacionet njëherësh"
+ "Preke dhe mbaje prekur për ta fshehur shiritin e detyrave"
+ "Para"
+ "Pas"
+ "Mbyll"
+ "U krye"
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index 8a462f982d..2f544403a5 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -25,11 +25,12 @@
"Подешавања коришћења апликације"
"Обриши све"
"Недавне апликације"
+
+
"%1$s, %2$s"
"< 1 мин"
"Још %1$s данас"
"Предлози апликација"
- "Све апликације"
"Предвиђене апликације"
"Добијајте предлоге апликација у доњем реду почетног екрана"
"Добијајте предлоге апликација у реду са омиљеним ставкама на почетном екрану"
@@ -76,9 +77,22 @@
"Водич %1$d/%2$d"
"Дели"
"Снимак екрана"
+ "Подели"
+ "Додирните другу апликацију за подељени екран"
+ "Апликација не подржава подељени екран."
"Апликација или организација не дозвољавају ову радњу"
"Желите да прескочите водич за кретање?"
"Можете да пронађете ово касније у апликацији %1$s"
"Откажи"
"Прескочи"
+ "Ротирајте екран"
+ "Едукативно окно из траке задатака се појавило"
+ "Едукативно окно из траке задатака је затворено"
+ "Користите траку задатака да бисте мењали апликације"
+ "Превуците на страну да користите две апликације одједном"
+ "Додирните и задржите за скривање траке задатака"
+ "Даље"
+ "Назад"
+ "Затвори"
+ "Готово"
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index bf6985030c..691c41474b 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -25,11 +25,12 @@
"Inställningar för appanvändning"
"Rensa alla"
"Senaste apparna"
+
+
"%1$s, %2$s"
"< 1 min"
"%1$s kvar i dag"
"Appförslag"
- "Alla appar"
"Föreslagna appar"
"Få appförslag på den nedersta raden på startskärmen"
"Få appförslag på raden Favoriter på startskärmen"
@@ -76,9 +77,22 @@
"Självstudie %1$d/%2$d"
"Dela"
"Skärmbild"
+ "Delat"
+ "Tryck på en annan app för att använda delad skärm"
+ "Appen har inte stöd för delad skärm."
"Appen eller organisationen tillåter inte den här åtgärden"
"Vill du hoppa över självstudierna?"
"Du kan hitta det här igen i %1$s-appen"
"Avbryt"
"Hoppa över"
+ "Rotera skärmen"
+ "Information om aktivitetsfältet visades"
+ "Information om aktivitetsfältet stängdes"
+ "Använd aktivitetsfältet för att byta mellan appar"
+ "Dra till sidan om du vill använda två appar samtidigt"
+ "Tryck länge för att dölja aktivitetsfältet"
+ "Nästa"
+ "Tillbaka"
+ "Stäng"
+ "Klar"
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index b761caee7d..639417e7f8 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -25,11 +25,12 @@
"Mipangilio ya matumizi ya programu"
"Ondoa zote"
"Programu za hivi karibuni"
+
+
"%1$s, %2$s"
"< dak 1"
"Umebakisha %1$s leo"
"Mapendekezo ya programu"
- "Programu zote"
"Programu zako zinazopendekezwa"
"Pata mapendekezo ya programu kwenye sehemu ya chini ya Skrini yako ya kwanza"
"Pata mapendekezo ya programu katika safu ya vipendwa ya Skrini yako ya kwanza"
@@ -76,9 +77,22 @@
"Mafunzo ya %1$d kati ya %2$d"
"Shiriki"
"Picha ya skrini"
+ "Iliyogawanywa"
+ "Gusa programu nyingine ili utumie skrini iliyogawanywa"
+ "Programu haiwezi kutumia skrini iliyogawanywa."
"Kitendo hiki hakiruhusiwi na programu au shirika lako"
"Ungependa kuruka mafunzo ya usogezaji?"
"Utapata mafunzo haya baadaye katika programu ya %1$s"
"Ghairi"
"Ruka"
+ "Zungusha skrini"
+ "Paneli ya elimu kwenye upau wa shughuli inaonyeshwa"
+ "Paneli ya elimu kwenye upau wa shughuli imefungwa"
+ "Tumia upau wa shughuli kubadilisha programu"
+ "Buruta pembeni ili utumie programu mbili kwa wakati mmoja"
+ "Gusa na ushikilie ili ufiche upau wa shughuli"
+ "Endelea"
+ "Nyuma"
+ "Funga"
+ "Imemaliza"
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000000..5d9e0596b5
--- /dev/null
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -0,0 +1,19 @@
+
+
+ 25dp
+
diff --git a/quickstep/res/values-sw720dp/dimens.xml b/quickstep/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000000..2831a6f7f5
--- /dev/null
+++ b/quickstep/res/values-sw720dp/dimens.xml
@@ -0,0 +1,21 @@
+
+
+ 44dp
+ 44dp
+ 44dp
+
diff --git a/quickstep/res/values-sw900dp/dimens.xml b/quickstep/res/values-sw900dp/dimens.xml
new file mode 100644
index 0000000000..3efa5e3ecf
--- /dev/null
+++ b/quickstep/res/values-sw900dp/dimens.xml
@@ -0,0 +1,23 @@
+
+
+
+ 76dp
+
+
+ 0dp
+
\ No newline at end of file
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 089c95de7b..f29709b0d2 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -25,17 +25,18 @@
"ஆப்ஸ் உபயோக அமைப்புகள்"
"எல்லாம் அழி"
"சமீபத்திய ஆப்ஸ்"
+
+
"%1$s, %2$s"
"< 1 நி"
"இன்று %1$s மீதமுள்ளது"
"ஆப்ஸ் பரிந்துரைகள்"
- "அனைத்து ஆப்ஸும்"
"நீங்கள் கணித்த ஆப்ஸ்"
"முகப்புத் திரையின் கடைசி வரிசையில் ஆப்ஸ் பரிந்துரைகளைப் பெறலாம்"
"உங்கள் முகப்புத் திரையின் \'பிடித்தவை\' வரிசையில் ஆப்ஸ் பரிந்துரைகளைப் பெறலாம்"
"அதிகமாகப் பயன்படுத்திய ஆப்ஸை முகப்புத் திரையிலேயே அணுகலாம். உங்கள் வழக்கங்களின் அடிப்படையில் பரிந்துரைகள் மாறும். கடைசி வரிசையிலுள்ள ஆப்ஸ் உங்கள் முகப்புத் திரைக்கு நகர்த்தப்படும்."
"அதிகமாகப் பயன்படுத்திய ஆப்ஸை முகப்புத் திரையிலேயே எளிதாக அணுகலாம். உங்கள் வழக்கங்களின் அடிப்படையில் பரிந்துரைகள் மாறும். பிடித்தவை வரிசையில் உள்ள ஆப்ஸ் உங்கள் முகப்புத் திரைக்கு நகர்த்தப்படும்."
- "அதிகமாகப் பயன்படுத்திய ஆப்ஸை முகப்புத் திரையிலேயே அணுகலாம். உங்கள் வழக்கங்களின் அடிப்படையில் பரிந்துரைகள் மாறும். கடைசி வரிசையிலுள்ள ஆப்ஸ் புதிய கோப்புறைக்கு நகர்த்தப்படும்."
+ "அதிகமாகப் பயன்படுத்திய ஆப்ஸை முகப்புத் திரையிலேயே அணுகலாம். உங்கள் வழக்கங்களின் அடிப்படையில் பரிந்துரைகள் மாறும். கடைசி வரிசையிலுள்ள ஆப்ஸ் புதிய ஃபோல்டருக்கு நகர்த்தப்படும்."
"ஆப்ஸ் பரிந்துரைகளைப் பெறுக"
"வேண்டாம்"
"அமைப்புகள்"
@@ -76,9 +77,22 @@
"பயிற்சி %1$d/%2$d"
"பகிர்"
"ஸ்கிரீன்ஷாட்"
+ "பிரி"
+ "ஸ்பிளிட் ஸ்கிரீனுக்கு மற்றொரு ஆப்ஸைத் தட்டவும்"
+ "திரைப் பிரிப்பு அம்சத்தை ஆப்ஸ் ஆதரிக்கவில்லை."
"ஆப்ஸோ உங்கள் நிறுவனமோ இந்த செயலை அனுமதிப்பதில்லை"
"வழிகாட்டுதல் பயிற்சியைத் தவிர்க்கவா?"
"%1$s ஆப்ஸில் பிறகு இதைக் கண்டறியலாம்"
"ரத்துசெய்"
"தவிர்"
+ "திரையைச் சுழற்றும்"
+ "பணிப்பட்டியை எவ்வாறு பயன்படுத்துவது என்பது பற்றிய பலகம் காட்டப்படுகிறது"
+ "பணிப்பட்டியை எவ்வாறு பயன்படுத்துவது என்பது பற்றிய பலகம் மூடப்பட்டது"
+ "ஆப்ஸிற்கு இடையே மாற பணிப்பட்டியைப் பயன்படுத்தவும்"
+ "ஒரே நேரத்தில் இரு ஆப்ஸை உபயோகிக்க பக்கவாட்டிற்கு இழுக்கவும்"
+ "பணிப்பட்டியை மறைக்கத் தொட்டுப் பிடிக்கவும்"
+ "அடுத்து"
+ "பின்செல்"
+ "மூடுக"
+ "முடிந்தது"
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index c73c83ebb5..d2bc6ad5e4 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -25,60 +25,77 @@
"యాప్ వినియోగ సెట్టింగ్లు"
"అన్నీ తీసివేయండి"
"ఇటీవలి యాప్లు"
+
+
"%1$s, %2$s"
"< 1 నిమిషం"
"నేటికి %1$s మిగిలి ఉంది"
"యాప్ సలహాలు"
- "అన్ని యాప్లు"
"మీ సూచించబడిన యాప్లు"
- "మీ హోమ్ స్క్రీన్ దిగువ వరుసలో యాప్ సలహాలను పొందండి"
+ "మీ మొదటి స్క్రీన్ దిగువ వరుసలో యాప్ సలహాలను పొందండి"
"మీ హోమ్ స్క్రీన్లోని ఇష్టమైన వాటి వరుసలో యాప్ సూచనలు పొందండి"
- "మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా హోమ్ స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ కార్యకలాపాలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు మీ హోమ్ స్క్రీన్ పైకి చేరుకుంటాయి."
- "మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా హోమ్ స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ కార్యకలాపాలను బట్టి సూచనలు మారతాయి. ఇష్టమైన వాటి వరుసలోని యాప్లు మీ హోమ్ స్క్రీన్కు చేరుకుంటాయి."
- "మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా హోమ్ స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ కార్యకలాపాలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు కొత్త ఫోల్డర్కు తరలించబడతాయి."
+ "మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు మీ మొదటి స్క్రీన్ పైకి చేరుకుంటాయి."
+ "మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. ఇష్టమైన వాటి వరుసలోని యాప్లు మీ మొదటి స్క్రీన్కు చేరుకుంటాయి."
+ "మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు కొత్త ఫోల్డర్కు తరలించబడతాయి."
"యాప్ సూచనలను పొందండి"
"వద్దు"
"సెట్టింగ్లు"
- "ఎక్కువగా ఉపయోగించిన యాప్లు ఇక్కడ కనిపిస్తాయి, అవి రోజువారీ కార్యకలాపాలను బట్టి మారుతూ ఉంటాయి"
+ "ఎక్కువగా ఉపయోగించిన యాప్లు ఇక్కడ కనిపిస్తాయి, అవి రోజువారీ యాక్టివిటీలను బట్టి మారుతూ ఉంటాయి"
"యాప్ సలహాలను పొందడానికి దిగువ వరుస నుండి యాప్లను లాగండి"
"యాప్ సూచనలు ఖాళీ స్పేస్కు జోడించబడ్డాయి"
"యాప్ సలహాలు ఎనేబుల్ చేయబడ్డాయి"
"యాప్ సూచనలు డిజేబుల్ చేయబడ్డాయి"
"సూచించబడిన యాప్: %1$s"
- "మీరు చాలా-ఎడమ అంచు నుండి స్వైప్ చేశారని నిర్ధారించుకోండి."
- "మీరు ఎడమ అంచు నుండి స్క్రీన్ మధ్యలోకి స్వైప్ చేశారని నిర్ధారించుకోని, ఆపై మీ వేలిని ఎత్తండి."
- "అంతే! ఇప్పుడు కుడి అంచు నుండి స్వైప్ చేయడానికి ట్రై చేయండి."
- "మీరు చాలా-కుడి అంచు నుండి స్వైప్ చేశారని నిర్ధారించుకోండి."
- "మీరు కుడి అంచు నుండి స్క్రీన్ మధ్యలోకి స్వైప్ చేశారని నిర్ధారించుకోని, ఆపై మీ వేలిని ఎత్తండి."
- "మీరు తిరిగి వెళ్లే సంజ్ఞను పూర్తి చేశారు. తర్వాత, మొదటి ట్యాబ్కు ఎలా వెళ్లాలో తెలుసుకోండి."
+ "కుడి వైపు చిట్ట చివరి లేదా ఎడమ వైపు చిట్ట చివరి అంచు నుండి స్వైప్ చేస్తున్నారని నిర్ధారించుకోండి."
+ "మీరు కుడి లేదా ఎడమ అంచు నుండి స్క్రీన్ మధ్యలోకి స్వైప్ చేశారని నిర్ధారించుకోని, మీ వేలిని ఎత్తండి."
+ "వెనుకకు వెళ్లడానికి కుడి నుండి స్వైప్ ఎలానో మీకు తెలుసు. తర్వాత, యాప్ల మధ్య ఎలా మారాలో తెలుసుకోండి."
+ "మీరు తిరిగి వెనక్కు వెళ్లే సంజ్ఞను పూర్తి చేశారు."
"మీరు స్క్రీన్ దిగువకు చాలా దగ్గరగా స్వైప్ చేయలేదని నిర్ధారించుకోండి."
"వెనుక సంజ్ఞ సున్నితత్వం మార్చడానికి, సెట్టింగ్లకు వెళ్లండి"
"వెనుకకు వెళ్ళడం కోసం స్వైప్ చేయండి"
"మునుపటి స్క్రీన్కు తిరిగి వెళ్లడానికి, ఎడమ లేదా కుడి అంచు నుండి స్క్రీన్ మధ్యలోకి స్వైప్ చేయండి."
- "మీరు స్క్రీన్ దిగువ అంచు నుండి పైకి స్వైప్ చేశారని నిర్ధారించుకోండి."
+ "మీరు స్క్రీన్ దిగువ అంచు నుండి పైకి స్వయిప్ చేస్తున్నారని నిర్ధారించుకోండి."
"బయలుదేరే ముందు మీరు పాజ్ చేయకుండా చూసుకోండి."
"మీరు నేరుగా పైకి స్వైప్ చేశారని నిర్ధారించుకోండి."
- "మీరు మొదటి ట్యాబ్కు వెళ్లే సంజ్ఞను పూర్తి చేశారు. తర్వాత, యాప్ల మధ్య ఎలా మార్చాలో తెలుసుకోండి."
- "వర్చువల్ హోమ్కి వెళ్లడానికి స్వైప్ చేయండి"
- "మీ స్క్రీన్ కింది నుండి పైకి స్వైప్ చేయి. ఈ సంజ్ఞ ఎప్పుడూ మిమ్మల్ని మొదటి స్క్రీన్కు తీసుకెళ్తుంది."
- "మీరు స్క్రీన్ దిగువ అంచు నుండి పైకి స్వైప్ చేశారని నిర్ధారించుకోండి."
+ "మీరు మొదటి స్క్రీన్కు వెళ్లే సంజ్ఞను పూర్తి చేశారు. తర్వాత, వెనుకకు ఎలా వెళ్లాలో తెలుసుకోండి."
+ "మీరు మొదటి ట్యాబ్కు వెళ్లే సంజ్ఞను పూర్తి చేశారు."
+ "మొదటి స్క్రీన్కు వెళ్లడానికి స్వైప్ చేయండి"
+ "స్క్రీన్ కింది నుండి పైకి స్వైప్ చేయండి. ఈ సంజ్ఞ ఎప్పుడూ మిమ్మల్ని మొదటి స్క్రీన్కు తీసుకెళ్తుంది."
+ "మీరు స్క్రీన్ దిగువ అంచు నుండి పైకి స్వయిప్ చేస్తున్నారని నిర్ధారించుకోండి."
"రిలీజ్ చేయడానికి ముందు విండోను ఎక్కువసేపు పట్టుకోడానికి ట్రై చేయండి."
"మీరు నేరుగా స్వైప్ చేశారని నిర్ధారించుకోండి, ఆపై పాజ్ చేయండి."
- "మీరు \'యాప్ల మధ్య మార్పు\' సంజ్ఞను పూర్తి చేశారు. మీరు మీ ఫోన్ను ఉపయోగించడానికి సిద్ధంగా ఉన్నారు!"
+ "మీరు సంజ్ఞలను ఎలా ఉపయోగించాలో నేర్చుకున్నారు. సంజ్ఞలను ఆఫ్ చేయడానికి, సెట్టింగ్లకు వెళ్లండి."
+ "మీరు \'యాప్ల మధ్య మార్పు\' సంజ్ఞను పూర్తి చేశారు."
"యాప్ల మధ్య మార్చడం కోసం స్వైప్ చేయండి"
- "మీ స్క్రీన్ కింది వైపు నుండి పైకి స్వైప్ చేసి, హోల్డ్ చేసి, తర్వాత రిలీజ్ చేయండి."
+ "యాప్ల మధ్య మారడానికి, మీ స్క్రీన్ కింది వైపు నుండి పైకి స్వైప్ చేసి, పట్టుకుని, తర్వాత వదలండి."
"అంతా సిద్ధంగా ఉంది"
- "తర్వాత"
- "పూర్తయింది"
+ "పూర్తయింది"
"సెట్టింగ్లు"
"మళ్లీ ట్రై చేయండి"
"పనితీరు బాగుంది!"
"ట్యుటోరియల్ %1$d/%2$d"
+ "అంతా సెట్ అయింది!"
+ "మొదటి స్క్రీన్కు వెళ్లడానికి పైకి స్వైప్ చేయండి"
+ "మీరు మీ ఫోన్ను ఉపయోగించడానికి సిద్ధంగా ఉన్నారు"
+ "సిస్టమ్ నావిగేషన్ సెట్టింగ్లు"
"షేర్ చేయండి"
"స్క్రీన్షాట్"
+ "స్ప్లిట్ చేయండి"
+ "స్క్రీన్ విభజనను ఉపయోగించడానికి మరొక యాప్ నొక్కండి"
+ "యాప్లో స్ప్లిట్-స్క్రీన్ పని చేయదు."
"ఈ చర్యను యాప్ గానీ, మీ సంస్థ గానీ అనుమతించవు"
"నావిగేషన్ ట్యుటోరియల్ను స్కిప్ చేయాలా?"
"%1$s యాప్లో మీరు తర్వాత కనుగొనవచ్చు"
"రద్దు చేయి"
"స్కిప్ చేయి"
+ "స్క్రీన్ను తిప్పండి"
+ "టాస్క్బార్ శిక్షణకు సంబంధించిన ప్యానెల్ కనిపించింది"
+ "టాస్క్బార్ శిక్షణకు సంబంధించిన ప్యానెల్ మూసివేయబడింది"
+ "యాప్లను స్విచ్ చేయడానికి టాస్క్బార్ను ఉపయోగించండి"
+ "ఒకేసారి రెండు యాప్లను ఉపయోగించడానికి పక్కకు లాగండి"
+ "టాస్క్బార్ను దాచడానికి తాకి, నొక్కి ఉంచండి"
+ "తర్వాత"
+ "వెనుకకు"
+ "మూసివేయండి"
+ "పూర్తయింది"
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index d7061089f2..220c9582dc 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -25,11 +25,12 @@
"การตั้งค่าการใช้แอป"
"ล้างทั้งหมด"
"แอปล่าสุด"
+
+
"%1$s %2$s"
"<1 นาที"
"วันนี้เหลืออีก %1$s"
"คำแนะนำเกี่ยวกับแอป"
- "แอปทั้งหมด"
"แอปที่คาดการณ์ไว้"
"ดูแอปแนะนำที่แถวล่างของหน้าจอหลัก"
"รับคำแนะนำเกี่ยวกับแอปในแถวรายการโปรดของหน้าจอหลัก"
@@ -76,9 +77,22 @@
"บทแนะนำ %1$d/%2$d"
"แชร์"
"ภาพหน้าจอ"
+ "แยก"
+ "แตะที่แอปอื่นเพื่อใช้แบ่งหน้าจอ"
+ "แอปไม่รองรับการแบ่งหน้าจอ"
"แอปหรือองค์กรของคุณไม่อนุญาตการดำเนินการนี้"
"ข้ามบทแนะนำการนำทางไหม"
"คุณดูบทแนะนำนี้ได้ภายหลังในแอป %1$s"
"ยกเลิก"
"ข้าม"
+ "หมุนหน้าจอ"
+ "แถบงาน Education ปรากฎขึ้น"
+ "ปิดแถบงาน Education แล้ว"
+ "ใช้แถบงานเพื่อเปลี่ยนแอป"
+ "ลากไปด้านข้างเพื่อใช้ 2 แอปพร้อมกัน"
+ "แตะค้างไว้เพื่อซ่อนแถบงาน"
+ "ถัดไป"
+ "กลับ"
+ "ปิด"
+ "เสร็จ"
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 5056481ccf..b0f1241e0c 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -25,11 +25,12 @@
"Mga setting ng paggamit ng app"
"I-clear lahat"
"Mga kamakailang app"
+
+
"%1$s, %2$s"
"< 1 min"
"%1$s na lang ngayon"
"Mga iminumungkahing app"
- "Lahat ng app"
"Iyong mga nahulaang app"
"Makakuha ng mga suhestyon sa app sa ibabang row ng iyong Home screen"
"Makakuha ng mga iminumungkahing app sa row ng mga paborito ng iyong Home screen"
@@ -76,9 +77,22 @@
"Tutorial %1$d/%2$d"
"Ibahagi"
"Screenshot"
+ "Split"
+ "Mag-tap ng ibang app para gamitin ang splitscreen"
+ "Hindi sinusuportahan ng app ang split-screen."
"Hindi pinapayagan ng app o ng iyong organisasyon ang pagkilos na ito"
"Laktawan ang tutorial sa pag-navigate?"
"Makikita mo ito sa %1$s app sa ibang pagkakataon"
"Kanselahin"
"Laktawan"
+ "I-rotate ang screen"
+ "Lumabas ang edukasyon sa taskbar"
+ "Sarado ang edukasyon sa taskbar"
+ "Gamitin ang taskbar para magpalipat-lipat sa mga app"
+ "I-drag sa gilid para makagamit ng dalawang app nang sabay"
+ "Pindutin nang matagal para itago ang taskbar"
+ "Susunod"
+ "Bumalik"
+ "Isara"
+ "Tapos na"
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index a22247b2de..08f467b56e 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -25,11 +25,12 @@
"Uygulama kullanım ayarları"
"Tümünü temizle"
"Son uygulamalar"
+
+
"%1$s, %2$s"
"< 1 dk."
"Bugün %1$s kaldı"
"Önerilen uygulamalar"
- "Tüm uygulamalar"
"Tahmin edilen uygulamalarınız"
"Ana ekranınızın alt satırında uygulama önerileri alın"
"Ana ekranınızın favoriler satırında uygulama önerileri alın"
@@ -76,9 +77,22 @@
"Eğitici %1$d/%2$d"
"Paylaş"
"Ekran görüntüsü"
+ "Böl"
+ "Bölünmüş ekran için başka bir uygulamaya dokunun"
+ "Uygulama bölünmüş ekranı desteklemiyor."
"Uygulamanız veya kuruluşunuz bu işleme izin vermiyor"
"Gezinme eğitici içeriği atlansın mı?"
"Bunu daha sonra %1$s uygulamasında bulabilirsiniz"
"İptal"
"Atla"
+ "Ekranı döndür"
+ "Görev çubuğu eğitimi görüntülendi"
+ "Görev çubuğu eğitimi kapatıldı"
+ "Görev çubuğundan uygulamalar arasında geçiş yapabilirsiniz"
+ "Tek seferde iki uygulamayı kullanmak için yana sürükleyin"
+ "Görev çubuğunu gizlemek için basılı tutun"
+ "İleri"
+ "Geri"
+ "Kapat"
+ "Bitti"
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index c63cec4c0a..3117c2dc3d 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -25,11 +25,12 @@
"Налаштування використання додатка"
"Очистити все"
"Нещодавні додатки"
+
+
"%1$s, %2$s"
"< 1 хв"
"Сьогодні залишилося %1$s"
"Рекомендовані додатки"
- "Усі додатки"
"Передбачені додатки"
"Рекомендовані додатки з\'являтимуться в нижньому рядку головного екрана"
"Рекомендовані додатки з\'являтимуться в рядку \"Вибране\" на головному екрані"
@@ -76,9 +77,22 @@
"Навчальний посібник %1$d/%2$d"
"Поділитися"
"Знімок екрана"
+ "Розділити"
+ "Щоб розділити екран, виберіть ще один додаток"
+ "Додаток не підтримує розділення екрана."
"Ця дія заборонена додатком або адміністратором організації"
"Пропустити посібник із навігації?"
"Ви знайдете його пізніше в додатку %1$s"
"Скасувати"
"Пропустити"
+ "Обернути екран"
+ "Панель завдань Education відкрито"
+ "Панель завдань Education закрито"
+ "Переходьте між додатками за допомогою панелі завдань"
+ "Перетягніть убік, щоб використовувати два додатки одночасно"
+ "Натисніть і втримуйте панель завдань, щоб сховати її"
+ "Далі"
+ "Назад"
+ "Закрити"
+ "Готово"
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 5ce95ad7c1..bad600678d 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -25,11 +25,12 @@
"ایپ کے استعمال کی ترتیبات"
"سبھی کو صاف کریں"
"حالیہ ایپس"
+
+
"%1$s،%2$s"
"< 1 منٹ"
"آج %1$s بچا ہے"
"ایپس کی تجاویز"
- "تمام ایپس"
"آپ کی پیشن گوئی کردہ ایپس"
"اپنی ہوم اسکرین کی نچلی قطار پر ایپ کی تجاویز حاصل کریں"
"اپنی ہوم اسکرین کی پسندیدہ قطار پر ایپ کی تجاویز حاصل کریں"
@@ -76,9 +77,22 @@
"ٹیوٹوریل %1$d/%2$d"
"اشتراک کریں"
"اسکرین شاٹ"
+ "اسپلٹ"
+ "اسپلٹ اسکرین کا استعمال کرنے کیلئے دوسری ایپ پر تھپتھپائیں"
+ "ایپ سپلٹ اسکرین کو سپورٹ نہیں کرتی۔"
"ایپ یا آپ کی تنظیم کی جانب سے اس کارروائی کی اجازت نہیں ہے"
"نیویگیشن کا ٹیوٹوریل نظر انداز کریں؟"
"آپ اسے بعد میں %1$s ایپ میں تلاش کر سکتے ہیں"
"منسوخ کریں"
"نظر انداز کریں"
+ "اسکرین کو گھمائیں"
+ "ٹاکس بار کا تعلیمی پینل ظاہر ہو گیا"
+ "ٹاسک بار کا تعلیمی پینل بند ہو گیا"
+ "ایپس کو سوئچ کرنے کیلئے ٹاسک بار کا استعمال کریں"
+ "ایک وقت میں دو ایپس استعمال کرنے کے لیے سائیڈ پر گھسیٹیں"
+ "ٹاسک بار کو کسی بھی وقت چھپانے کیلئے ٹچ کریں اور دبائے رکھیں"
+ "آگے"
+ "پیچھے"
+ "بند کریں"
+ "ہو گیا"
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 7272eddf51..e3b91f1355 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -25,11 +25,12 @@
"Ilovadan foydalanish sozlamalari"
"Hammasini tozalash"
"Yaqinda ishlatilgan ilovalar"
+
+
"%1$s, %2$s"
"< 1 daqiqa"
"Bugun %1$s qoldi"
"Tavsiya etiladigan ilovalar"
- "Barcha ilovalar"
"Taklif qilingan ilovalar"
"Tavsiya etiladigan ilovalar bosh ekran pastidagi qatorda chiqadi"
"Tavsiya etiladigan ilovalar bosh ekranning saralanganlar ruknida chiqadi"
@@ -76,9 +77,22 @@
"Darslik: %1$d/%2$d"
"Ulashish"
"Skrinshot"
+ "Ajratish"
+ "Ekranni ikkiga ajratish uchun boshqa ilovani bosing"
+ "Bu ilovada ekranni ikkiga ajratish ishlamaydi."
"Bu amal ilova yoki tashkilotingiz tomonidan taqiqlangan"
"Navigatsiya darsi yopilsinmi?"
"Bu darslar %1$s ilovasida chiqadi"
"Bekor qilish"
"Tashlab ketish"
+ "Ekranni burish"
+ "Taʼlim vazifalar paneli chiqdi"
+ "Taʼlim vazifalar paneli yopildi"
+ "Ilovalarni vazifalar panelida almashtirish mumkin"
+ "Bir vaqtda ikkita ilova ochish uchun birini yoniga torting"
+ "Vazifalar panelini ustiga bosib turib yashirish mumkin"
+ "Keyingisi"
+ "Orqaga"
+ "Yopish"
+ "Tayyor"
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index fc3ef3ffff..8bfb4a49e6 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -25,11 +25,12 @@
"Cài đặt mức sử dụng ứng dụng"
"Xóa tất cả"
"Ứng dụng gần đây"
+
+
"%1$s, %2$s"
"< 1 phút"
"Hôm nay còn %1$s"
"Các ứng dụng đề xuất"
- "Tất cả ứng dụng"
"Các ứng dụng gợi ý của bạn"
"Nhận các ứng dụng đề xuất ở cuối Màn hình chính"
"Nhận các ứng dụng đề xuất trên hàng mục ưa thích của Màn hình chính"
@@ -76,9 +77,22 @@
"Hướng dẫn %1$d/%2$d"
"Chia sẻ"
"Chụp ảnh màn hình"
+ "Chia đôi màn hình"
+ "Nhấn vào một ứng dụng khác để dùng màn hình chia đôi"
+ "Ứng dụng không hỗ trợ chia đôi màn hình."
"Ứng dụng hoặc tổ chức của bạn không cho phép thực hiện hành động này"
"Bỏ qua phần hướng dẫn thao tác?"
"Bạn có thể tìm lại phần hướng dẫn này trong ứng dụng %1$s"
"Hủy"
"Bỏ qua"
+ "Xoay màn hình"
+ "Đã hiện bảng hướng dẫn trên thanh tác vụ"
+ "Đã đóng bảng hướng dẫn trên thanh tác vụ"
+ "Dùng thanh tác vụ để chuyển đổi ứng dụng"
+ "Kéo sang bên để dùng hai ứng dụng cùng một lúc"
+ "Chạm và giữ để ẩn thanh tác vụ"
+ "Tiếp theo"
+ "Quay lại"
+ "Đóng"
+ "Xong"
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index fe5e553d98..ecea74fe25 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -25,11 +25,12 @@
"应用使用设置"
"全部清除"
"最近用过的应用"
+
+
"%1$s(%2$s)"
"不到 1 分钟"
"今天还可使用 %1$s"
"应用建议"
- "所有应用"
"您可能想要使用的应用"
"在主屏幕底部获取应用建议"
"在主屏幕的收藏行获取应用建议"
@@ -76,9 +77,22 @@
"教程 %1$d/%2$d"
"分享"
"屏幕截图"
+ "拆分"
+ "点按另一个应用即可使用分屏"
+ "应用不支持分屏。"
"该应用或您所在的单位不允许执行此操作"
"要跳过导航教程吗?"
"您之后可以在“%1$s”应用中找到此教程"
"取消"
"跳过"
+ "旋转屏幕"
+ "任务栏教程已显示"
+ "任务栏教程已关闭"
+ "使用任务栏切换应用"
+ "拖动到一侧,以便一次使用两个应用"
+ "轻触并按住即可隐藏任务栏"
+ "继续"
+ "返回"
+ "关闭"
+ "完成"
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index 5522a67f05..8945293f21 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -25,11 +25,12 @@
"應用程式使用情況設定"
"全部清除"
"最近使用的應用程式"
+
+
"%1$s,%2$s"
"少於 1 分鐘"
"今天剩餘時間:%1$s"
"應用程式建議"
- "所有應用程式"
"您的預測應用程式"
"在主畫面底部取得應用程式建議"
"在主畫面「我的最愛」列取得應用程式建議"
@@ -76,9 +77,22 @@
"教學課程 %1$d/%2$d"
"分享"
"螢幕截圖"
+ "分割"
+ "輕按其他應用程式以使用分割螢幕"
+ "應用程式不支援分割螢幕。"
"應用程式或您的機構不允許此操作"
"要略過手勢操作教學課程嗎?"
"您之後可以在「%1$s」應用程式找到這些說明"
"取消"
"略過"
+ "旋轉螢幕"
+ "顯示咗工作列教學"
+ "閂咗工作列教學"
+ "使用工作列即可切換應用程式"
+ "拖曳至一側即可同時使用兩個應用程式"
+ "按住即可隱藏工作列"
+ "繼續"
+ "返回"
+ "關閉"
+ "完成"
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index 20af2b4f88..27ff971ebf 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -25,32 +25,31 @@
"應用程式使用情況設定"
"全部清除"
"最近使用的應用程式"
+
+
"%1$s (%2$s)"
"< 1 分鐘"
"今天還能使用 %1$s"
"應用程式建議"
- "所有應用程式"
"系統預測你會使用的應用程式"
- "在主畫面的底部取得應用程式建議"
+ "在主畫面底部顯示應用程式建議"
"在主畫面的收藏列取得應用程式建議"
- "在主畫面上輕鬆存取最常使用的應用程式。應用程式建議會依據你的日常使用習慣而有所不同。系統會將底部列出的應用程式上移到主畫面。"
- "在主畫面上輕鬆存取最常使用的應用程式。系統會根據你的日常使用習慣提供不同的應用程式建議,並在主畫面顯示收藏列中的應用程式。"
- "在主畫面上輕鬆存取最常使用的應用程式。應用程式建議會根據日常安排有所不同。系統會將底部列出的應用程式移到新的資料夾。"
+ "你可以輕鬆地在主畫面上找到自己常用的應用程式。應用程式建議會依據你的日常使用習慣而有所不同。系統會將底部列出的應用程式上移到主畫面。"
+ "你可以輕鬆地在主畫面上找到自己常用的應用程式。系統會根據你的日常使用習慣提供不同的應用程式建議,並在主畫面顯示收藏列中的應用程式。"
+ "你可以輕鬆地在主畫面上找到自己常用的應用程式。應用程式建議會根據日常安排有所不同。系統會將底部列出的應用程式移到新的資料夾。"
"取得應用程式建議"
"不用了,謝謝"
"設定"
"最常使用的應用程式會顯示在這裡,顯示的項目會根據日常安排有所不同"
- "將應用程式從底部列向外拖曳,即可取得應用程式建議"
+ "將底部列中顯示的應用程式拖曳出來,即可取得應用程式建議"
"應用程式建議已新增到空白位置"
"應用程式建議功能已啟用"
"應用程式建議功能已停用"
"預測的應用程式:%1$s"
- "請從螢幕左側邊緣滑動。"
- "請從螢幕左側邊緣往中央滑動,然後放開手指。"
- "大功告成!現在請試著從螢幕右側邊緣滑動。"
- "請從螢幕右側邊緣滑動。"
- "請從螢幕右側邊緣往中央滑動,然後放開手指。"
- "你已完成「返回」手勢的教學課程。接著,一起來瞭解如何返回主畫面。"
+ "請從螢幕右側或左側邊緣滑動。"
+ "請從螢幕右側或左側邊緣往中央滑動,然後放開手指。"
+ "你已瞭解如何透過「由右向左滑動」手勢返回。接著,一起來瞭解如何切換應用程式。"
+ "你已完成「返回」手勢的教學課程。"
"滑動時,手的位置不要太接近螢幕底部。"
"如要變更「返回」手勢的敏感度,請前往「設定」"
"滑動即可返回"
@@ -58,27 +57,45 @@
"請從螢幕底部邊緣向上滑動。"
"放開手指前請勿停下來。"
"請向上滑動。"
- "你已完成「返回主畫面」手勢的教學課程。接著,一起來瞭解如何切換應用程式。"
+ "你已完成「返回主畫面」手勢的教學課程。接著,一起來瞭解如何返回上一個畫面。"
+ "你已完成「返回主畫面」手勢的教學課程。"
"使用滑動手勢返回主畫面"
"從螢幕底部向上滑動,即可返回主畫面。"
"請從螢幕底部邊緣向上滑動。"
"請按住視窗久一點,然後再放開。"
"請向上滑動,然後停住。"
- "你已完成「切換應用程式」手勢的教學課程。現在可以開始使用手機了!"
+ "你已瞭解如何使用手勢了。如要關閉手勢,請前往「設定」。"
+ "你已完成「切換應用程式」手勢的教學課程。"
"使用滑動手勢切換應用程式"
- "從螢幕底部向上滑動並按住,然後放開。"
+ "如要切換不同的應用程式,請從螢幕底部向上滑動並按住,然後放開手指。"
"大功告成"
- "繼續"
- "完成"
+ "完成"
"設定"
"重試"
"很好!"
"教學課程 %1$d/%2$d"
+ "設定完成!"
+ "向上滑動即可前往主畫面"
+ "你可以開始使用手機了"
+ "系統操作機制設定"
"分享"
- "擷取螢幕畫面"
+ "螢幕截圖"
+ "分割"
+ "輕觸另一個應用程式即可使用分割畫面"
+ "這個應用程式不支援分割畫面。"
"這個應用程式或貴機構不允許執行這個動作"
"要略過手勢操作教學課程嗎?"
"你之後可以在「%1$s」應用程式找到這些說明"
"取消"
"略過"
+ "旋轉螢幕"
+ "工作列教學課程已顯示"
+ "工作列教學課程已關閉"
+ "使用工作列即可切換應用程式"
+ "拖曳到一邊即可同時使用兩個應用程式"
+ "按住即可隱藏工作列"
+ "繼續"
+ "返回"
+ "關閉"
+ "完成"
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index 2ffaecf9c5..bb98d1b00b 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -25,11 +25,12 @@
"Izilungiselelo zokusetshenziswa kohlelo lokusebenza"
"Sula konke"
"Izinhlelo zokusebenza zakamuva"
+
+
"%1$s, %2$s"
"< 1 iminithi"
"%1$s esele namhlanje"
"Iziphakamiso zohlelo lokusebenza"
- "Zonke izinhlelo zokusebenza"
"Ama-app akho aqagelwe"
"Thola iziphakamiso ze-app emgqeni ongezansi wesikrini sakho sasekhaya"
"Thola iziphakamiso zohlelo lokusebenza kumugqa wezintandokazi Zesikrini sakho sasekhaya"
@@ -76,9 +77,22 @@
"Okokufundisa %1$d/%2$d"
"Yabelana"
"Isithombe-skrini"
+ "Hlukanisa"
+ "Thepha enye i-app ukuze usebenzise isikrini sokuhlukanisa"
+ "Uhlelo lokusebenza alusekeli isikrini esihlukanisiwe."
"Lesi senzo asivunyelwanga uhlelo lokusebenza noma inhlangano yakho"
"Yeqa isifundo sokuzulazula?"
"Lokhu ungakuthola kamuva ku-app ye-%1$s"
"Khansela"
"Yeqa"
+ "Zungezisa isikrini"
+ "Imfuno yebha yomsebenzi ivelile"
+ "Imfundo yebha yomsebenzi ivaliwe"
+ "Sebenzisa ibha yomsebenzi ukushintsha ama-app"
+ "Hudula ngaseceleni ukuze usebenzise ama-app amabili ngesikhathi esisodwa"
+ "Thinta futhi ubambe, bamba ukuze ufihle ibha yomsebenzi"
+ "Okulandelayo"
+ "Emuva"
+ "Vala"
+ "Kwenziwe"
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 167c7c3d0f..671a617f01 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -14,8 +14,6 @@
limitations under the License.
-->
- #FFFFFFFF
- #99000000
#fff
#39000000
@@ -26,6 +24,59 @@
#3cffffff
- #101010
- #E0E0E0
+ @color/overview_scrim_dark
+ #E0E0E0
+ #ffffff
+
+ #99000000
+ #EBffffff
+ #99000000
+
+
+ #FFFFFFFF
+
+ #f9f9f9
+ #A0C2F9
+ #6DA1FF
+
+ #3C4043
+ #FF000000
+ #B7F29F
+ #202124
+
+
+ #8AB4F8
+ #F28B82
+ #FDD663
+ #81C995
+ #3C4043
+
+
+ #f1f3f4
+ #e8eaed
+ #dadce0
+ #bdc1c6
+ #e8eaed
+ #dadce0
+ #dadce0
+
+
+ #dadce0
+ #e8eaed
+ #f8f9fa
+ #9aa0a6
+ #bdc1c6
+ #bdc1c6
+
+
+ #f1f3f4
+ #6e7175
+ #9a9a9a
+ #e8eaed
+ #80868b
+ #bdc1c6
+
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index d67b23be0a..31c0f5f2ab 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -30,6 +30,7 @@
determines how many thumbnails will be fetched in the background. -->
3
12
+ 20
200
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 6cc64e0aa7..5ea94e906e 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -16,7 +16,8 @@
48dp
- 32dp
+ 48dp
+ 32dp
2dp
@@ -26,24 +27,35 @@
22dp
4dp
2dp
+ 234dp
+ 8dp
48dp
16dp
50dp
16dp
+ 12dp
+ 4dp
+ - 0.7
48dp
- 28dp
- 8dp
+ 32dp
+ 36dp
+ 28dp
+ 19.37dp
+ 22dp
+ 19.1dp
+ 10dp
+ 8dp
16dp
- 77dp
- 90dp
- 54dp
- 42dp
- 90dp
- 110dp
+ 60dp
+ 52dp
+ 36dp
+ 16dp
+ 36dp
+ 38dp
2.25dp
@@ -51,7 +63,6 @@
1dp
5dp
- 16dp
70dp
10dp
@@ -108,17 +121,57 @@
136dp
- 40dp
- 16dp
24dp
- 18dp
+ 140dp
+ 72dp
+ 18dp
+ 80dp
+
+
+ 44dp
+ 100dp
+ 36dp
+ 32dp
+ 4dp
+ 26dp
+ 18dp
+ 126dp
+
+
+ 56dp
+ 100dp
+ 28dp
+ 20dp
+
+
+ 60dp
+ 100dp
+ 50dp
+ 100dp
+ 36dp
+
+
+ 32dp
+ 24dp
+ 8dp
+ 22dp
+ 8dp
+ 4dp
+ 36dp
+ 22dp
+
+
+ 44dp
+ 100dp
+ 218dp
40dp
24dp
32dp
- 52dp
24dp
+ 348dp
+ 10dp
8dp
@@ -148,13 +201,34 @@
80dp
+
+ 95dp
+
+ 0dp
+
+ 0dp
+
+
+ 40dp
+ 20dp
+ 20dp
+ 10dp
+
- 60dp
- 44dp
+ @*android:dimen/taskbar_frame_height
+ 48dp
48dp
54dp
-
- 8dp
16dp
16dp
+ 8dp
+ 44dp
+ 40dp
+ 42dp
+ 35dp
+ 24dp
+ 220dp
+ 6dp
+ 25dp
+ 4dp
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 4aee2a9ccb..6caed1cf7d 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -39,6 +39,9 @@
Recent apps
+
+ Task Closed
+
%1$s, %2$s
@@ -53,8 +56,6 @@
App suggestions
-
- All apps
Your predicted apps
@@ -191,6 +192,12 @@
Share
Screenshot
+
+ Split
+
+ Tap another app to use splitscreen
+
+ App does not support split-screen.
This action isn\'t allowed by the app or your organization
@@ -203,4 +210,30 @@
Cancel
Skip
+
+
+ Rotate screen
+
+
+
+ Taskbar education appeared
+
+ Taskbar education closed
+
+ Use the taskbar to switch apps
+
+ Drag to the side to use two apps at once
+
+ Touch & hold to hide the taskbar
+
+ Next
+
+ Back
+
+ Close
+
+ Done
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 9e91cd1f32..d969070b8a 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -115,7 +115,7 @@
- @android:color/transparent
- false
- true
- - #FFFFFFFF
+ - @android:color/transparent
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
deleted file mode 100644
index 9df9ab1682..0000000000
--- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.util.LauncherUIHelper.doLayout;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-
-import com.android.quickstep.fallback.FallbackRecentsView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.android.controller.ActivityController;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
-import org.robolectric.shadows.ShadowLooper;
-import org.robolectric.util.ReflectionHelpers;
-
-
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
-@org.junit.Ignore
-public class RecentsActivityTest {
-
- @Test
- public void testRecentsActivityCreates() {
- ActivityController controller =
- Robolectric.buildActivity(RecentsActivity.class);
-
- RecentsActivity launcher = controller.setup().get();
- doLayout(launcher);
-
- // TODO: Ensure that LauncherAppState is not created
- }
-
- @Test
- public void testRecents_showCurrentTask() {
- ActivityController controller =
- Robolectric.buildActivity(RecentsActivity.class);
-
- RecentsActivity activity = controller.setup().get();
- doLayout(activity);
-
- FallbackRecentsView frv = activity.getOverviewPanel();
-
- RunningTaskInfo placeholderTask = new RunningTaskInfo();
- placeholderTask.taskId = 22;
- frv.showCurrentTask(placeholderTask);
- doLayout(activity);
-
- ThumbnailData thumbnailData = new ThumbnailData();
- ReflectionHelpers.setField(thumbnailData, "thumbnail",
- Bitmap.createBitmap(300, 500, Config.ARGB_8888));
- frv.switchToScreenshot(thumbnailData, () -> { });
- ShadowLooper.idleMainLooper();
- }
-}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index ac307895fa..75b5f644b5 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -22,21 +22,25 @@ import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.NO_OFFSET;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
+import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
-import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
-import android.content.ServiceConnection;
+import android.graphics.Insets;
+import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.IBinder;
import android.view.View;
+import android.view.WindowInsets;
import android.window.SplashScreen;
import androidx.annotation.Nullable;
@@ -53,9 +57,10 @@ import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.taskbar.TaskbarManager;
-import com.android.launcher3.taskbar.TaskbarStateHandler;
import com.android.launcher3.uioverrides.RecentsViewStateController;
import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ObjectWrapper;
import com.android.launcher3.util.UiThreadHelper;
import com.android.quickstep.OverviewCommandHelper;
@@ -65,18 +70,24 @@ import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.util.LauncherUnfoldAnimationController;
+import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.SplitPlaceholderView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.unfold.UnfoldTransitionFactory;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.List;
import java.util.stream.Stream;
@@ -98,45 +109,61 @@ public abstract class BaseQuickstepLauncher extends Launcher
private OverviewActionsView mActionsView;
+ private TISBindHelper mTISBindHelper;
private @Nullable TaskbarManager mTaskbarManager;
private @Nullable OverviewCommandHelper mOverviewCommandHelper;
private @Nullable LauncherTaskbarUIController mTaskbarUIController;
- private final ServiceConnection mTisBinderConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager();
- mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
-
- mOverviewCommandHelper = ((TISBinder) iBinder).getOverviewCommandHelper();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName componentName) { }
- };
- private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
// Will be updated when dragging from taskbar.
private @Nullable DragOptions mNextWorkspaceDragOptions = null;
- private SplitPlaceholderView mSplitPlaceholderView;
+
+ private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+ private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
addMultiWindowModeChangedListener(mDepthController);
+ initUnfoldTransitionProgressProvider();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onResume();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onPause();
+ }
+
+ super.onPause();
}
@Override
public void onDestroy() {
mAppTransitionManager.onActivityDestroyed();
+ if (mUnfoldTransitionProgressProvider != null) {
+ mUnfoldTransitionProgressProvider.destroy();
+ }
SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
-
- unbindService(mTisBinderConnection);
+ mTISBindHelper.onDestroy();
if (mTaskbarManager != null) {
- mTaskbarManager.setLauncher(null);
+ mTaskbarManager.clearActivity(this);
}
+
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onDestroy();
+ }
+
super.onDestroy();
}
@@ -200,6 +227,17 @@ public abstract class BaseQuickstepLauncher extends Launcher
}
}
+ /**
+ * {@code LauncherOverlayCallbacks} scroll amount.
+ * Indicates transition progress to -1 screen.
+ * @param progress From 0 to 1.
+ */
+ @Override
+ public void onScrollChanged(float progress) {
+ super.onScrollChanged(progress);
+ mDepthController.onOverlayScrollChanged(progress);
+ }
+
@Override
public void startIntentSenderForResult(IntentSender intent, int requestCode,
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
@@ -261,32 +299,64 @@ public abstract class BaseQuickstepLauncher extends Launcher
SysUINavigationMode.INSTANCE.get(this).updateMode();
mActionsView = findViewById(R.id.overview_actions_view);
- mSplitPlaceholderView = findViewById(R.id.split_placeholder);
RecentsView overviewPanel = (RecentsView) getOverviewPanel();
- mSplitPlaceholderView.init(
- new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this))
- );
- overviewPanel.init(mActionsView, mSplitPlaceholderView);
+ SplitSelectStateController controller =
+ new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this),
+ getStateManager(), getDepthController());
+ overviewPanel.init(mActionsView, controller);
mActionsView.setDp(getDeviceProfile());
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
mAppTransitionManager = new QuickstepTransitionManager(this);
mAppTransitionManager.registerRemoteAnimations();
+ mAppTransitionManager.registerRemoteTransitions();
- bindService(new Intent(this, TouchInteractionService.class), mTisBinderConnection, 0);
+ mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
+ }
+ private void onTISConnected(TISBinder binder) {
+ mTaskbarManager = binder.getTaskbarManager();
+ mTaskbarManager.setActivity(this);
+ mOverviewCommandHelper = binder.getOverviewCommandHelper();
+ }
+
+ @Override
+ public void runOnBindToTouchInteractionService(Runnable r) {
+ mTISBindHelper.runOnBindToTouchInteractionService(r);
+ }
+
+ private void initUnfoldTransitionProgressProvider() {
+ final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
+ if (config.isEnabled()) {
+ mUnfoldTransitionProgressProvider =
+ UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
+ this,
+ config,
+ ProxyScreenStatusProvider.INSTANCE,
+ getSystemService(DeviceStateManager.class),
+ getSystemService(SensorManager.class),
+ getMainThreadHandler(),
+ getMainExecutor()
+ );
+
+ mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
+ this,
+ getWindowManager(),
+ mUnfoldTransitionProgressProvider
+ );
+ }
}
public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
mTaskbarUIController = taskbarUIController;
}
- public T getActionsView() {
- return (T) mActionsView;
+ public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
+ return mTaskbarUIController;
}
- public SplitPlaceholderView getSplitPlaceholderView() {
- return mSplitPlaceholderView;
+ public T getActionsView() {
+ return (T) mActionsView;
}
@Override
@@ -301,19 +371,15 @@ public abstract class BaseQuickstepLauncher extends Launcher
out.add(getDepthController());
out.add(new RecentsViewStateController(this));
out.add(new BackButtonAlphaHandler(this));
- out.add(getTaskbarStateHandler());
}
public DepthController getDepthController() {
return mDepthController;
}
- public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
- return mTaskbarUIController;
- }
-
- public TaskbarStateHandler getTaskbarStateHandler() {
- return mTaskbarStateHandler;
+ @Nullable
+ public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
+ return mUnfoldTransitionProgressProvider;
}
@Override
@@ -363,14 +429,6 @@ public abstract class BaseQuickstepLauncher extends Launcher
? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET};
}
- @Override
- public float getNormalTaskbarScale() {
- if (mTaskbarUIController != null) {
- return mTaskbarUIController.getTaskbarScaleOnHome();
- }
- return super.getNormalTaskbarScale();
- }
-
@Override
public void onDragLayerHierarchyChanged() {
onLauncherStateOrFocusChanged();
@@ -425,8 +483,8 @@ public abstract class BaseQuickstepLauncher extends Launcher
}
@Override
- public void finishBindingItems(int pageBoundFirst) {
- super.finishBindingItems(pageBoundFirst);
+ public void finishBindingItems(IntSet pagesBoundFirst) {
+ super.finishBindingItems(pagesBoundFirst);
// Instantiate and initialize WellbeingModel now that its loading won't interfere with
// populating workspace.
// TODO: Find a better place for this
@@ -496,4 +554,35 @@ public abstract class BaseQuickstepLauncher extends Launcher
public void setHintUserWillBeActive() {
addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
}
+
+ @Override
+ public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+ super.onDisplayInfoChanged(context, info, flags);
+ // When changing screens, force moving to rest state similar to StatefulActivity.onStop, as
+ // StatefulActivity isn't called consistently.
+ if ((flags & CHANGE_ACTIVE_SCREEN) != 0) {
+ getStateManager().moveToRestState();
+ }
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ if (mDepthController != null) {
+ mDepthController.dump(prefix, writer);
+ }
+ }
+
+ @Override
+ public void updateWindowInsets(WindowInsets.Builder updatedInsetsBuilder,
+ WindowInsets oldInsets) {
+ // Override the tappable insets to be 0 on the bottom for gesture nav (otherwise taskbar
+ // would count towards it). This is used for the bottom protection in All Apps for example.
+ if (SysUINavigationMode.getMode(this) == NO_BUTTON) {
+ Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
+ Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top,
+ oldTappableInsets.right, 0);
+ updatedInsetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 5fc79f078f..35151f1a68 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -17,11 +17,8 @@ package com.android.launcher3;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
import android.os.Build;
import android.os.CancellationSignal;
-import android.os.Handler;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.RemoteAnimationProvider;
@@ -78,11 +75,4 @@ public class LauncherInitListener extends ActivityInitListener {
mRemoteAnimationProvider = null;
super.unregister();
}
-
- @Override
- public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
- Context context, Handler handler, long duration) {
- mRemoteAnimationProvider = animProvider;
- super.registerAndStartActivity(intent, animProvider, context, handler, duration);
- }
}
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 3048ff843f..61a7c5c156 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -28,19 +28,24 @@ import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.Utilities.mapBoundToRange;
import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_BACK_SWIPE_HOME_ANIMATION;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAUNCH;
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
+import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
@@ -52,26 +57,31 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.CancellationSignal;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.util.Pair;
import android.util.Size;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
+import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -86,8 +96,12 @@ import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.ObjectWrapper;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.ScrimView;
@@ -96,7 +110,9 @@ import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.FloatingWidgetView;
@@ -144,21 +160,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
- private static final long APP_LAUNCH_DURATION = 450;
- // Use a shorter duration for x or y translation to create a curve effect
- private static final long APP_LAUNCH_CURVED_DURATION = 250;
+ private static final long APP_LAUNCH_DURATION = 500;
+
private static final long APP_LAUNCH_ALPHA_DURATION = 50;
private static final long APP_LAUNCH_ALPHA_START_DELAY = 25;
- // We scale the durations for the downward app launch animations (minus the scale animation).
- private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f;
- private static final long APP_LAUNCH_DOWN_DURATION =
- (long) (APP_LAUNCH_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
- private static final long APP_LAUNCH_DOWN_CURVED_DURATION =
- (long) (APP_LAUNCH_CURVED_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
- private static final long APP_LAUNCH_ALPHA_DOWN_DURATION =
- (long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
-
public static final int ANIMATION_NAV_FADE_IN_DURATION = 266;
public static final int ANIMATION_NAV_FADE_OUT_DURATION = 133;
public static final long ANIMATION_DELAY_NAV_FADE_IN =
@@ -168,12 +174,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
public static final Interpolator NAV_FADE_OUT_INTERPOLATOR =
new PathInterpolator(0.2f, 0f, 1f, 1f);
- private static final long CROP_DURATION = 375;
- private static final long RADIUS_DURATION = 375;
-
public static final int RECENTS_LAUNCH_DURATION = 336;
private static final int LAUNCHER_RESUME_START_DELAY = 100;
private static final int CLOSING_TRANSITION_DURATION_MS = 250;
+ public static final int SPLIT_LAUNCH_DURATION = 370;
+ public static final int SPLIT_DIVIDER_ANIM_DURATION = 100;
public static final int CONTENT_ALPHA_DURATION = 217;
protected static final int CONTENT_SCALE_DURATION = 350;
@@ -224,6 +229,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
// Will never be larger than MAX_NUM_TASKS
private LinkedHashMap> mTaskStartParams;
+ private final Interpolator mOpeningXInterpolator;
+ private final Interpolator mOpeningInterpolator;
+
public QuickstepTransitionManager(Context context) {
mLauncher = Launcher.cast(Launcher.getLauncher(context));
mDragLayer = mLauncher.getDragLayer();
@@ -250,6 +258,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(
mStartingWindowListener);
}
+
+ mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x);
+ mOpeningInterpolator = AnimationUtils.loadInterpolator(context,
+ R.interpolator.three_point_fast_out_extra_slow_in);
}
@Override
@@ -277,7 +289,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
- STATUS_BAR_TRANSITION_PRE_DELAY;
RemoteAnimationAdapterCompat adapterCompat =
- new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay);
+ new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay,
+ mLauncher.getIApplicationThread());
return new ActivityOptionsWrapper(
ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback);
}
@@ -356,7 +369,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
if (launcherClosing) {
// Delay animation by a frame to avoid jank.
Pair launcherContentAnimator =
- getLauncherContentAnimator(true /* isAppOpening */, startDelay);
+ getLauncherContentAnimator(true /* isAppOpening */, startDelay, false);
anim.play(launcherContentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -430,6 +443,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
4 - rotationChange);
}
}
+ if (mDeviceProfile.isTaskbarPresentInApps) {
+ // Animate to above the taskbar.
+ bounds.bottom -= target.contentInsets.bottom;
+ }
return bounds;
}
@@ -449,9 +466,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
* @param isAppOpening True when this is called when an app is opening.
* False when this is called when an app is closing.
* @param startDelay Start delay duration.
+ * @param skipAllAppsScale True if we want to avoid scaling All Apps
*/
private Pair getLauncherContentAnimator(boolean isAppOpening,
- int startDelay) {
+ int startDelay, boolean skipAllAppsScale) {
AnimatorSet launcherAnimator = new AnimatorSet();
Runnable endListener;
@@ -469,7 +487,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
final float startAlpha = appsView.getAlpha();
final float startScale = SCALE_PROPERTY.get(appsView);
appsView.setAlpha(alphas[0]);
- SCALE_PROPERTY.set(appsView, scales[0]);
ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
alpha.setDuration(CONTENT_ALPHA_DURATION);
@@ -481,12 +498,16 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
- ObjectAnimator scale = ObjectAnimator.ofFloat(appsView, SCALE_PROPERTY, scales);
- scale.setInterpolator(AGGRESSIVE_EASE);
- scale.setDuration(CONTENT_SCALE_DURATION);
+
+ if (!skipAllAppsScale) {
+ SCALE_PROPERTY.set(appsView, scales[0]);
+ ObjectAnimator scale = ObjectAnimator.ofFloat(appsView, SCALE_PROPERTY, scales);
+ scale.setInterpolator(AGGRESSIVE_EASE);
+ scale.setDuration(CONTENT_SCALE_DURATION);
+ launcherAnimator.play(scale);
+ }
launcherAnimator.play(alpha);
- launcherAnimator.play(scale);
endListener = () -> {
appsView.setAlpha(startAlpha);
@@ -514,7 +535,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
final boolean scrimEnabled = ENABLE_SCRIM_FOR_APP_LAUNCH.get();
if (scrimEnabled) {
- int scrimColor = ColorTokens.OverviewScrim.resolveColor(mLauncher);
+ boolean useTaskbarColor = mDeviceProfile.isTaskbarPresentInApps;
+ int scrimColor = useTaskbarColor
+ ? mLauncher.getResources().getColor(R.color.taskbar_background)
+ : ColorTokens.OverviewScrim.resolveColor(mLauncher);
int scrimColorTrans = ColorUtils.setAlphaComponent(scrimColor, 0);
int[] colors = isAppOpening
? new int[]{scrimColorTrans, scrimColor}
@@ -527,6 +551,30 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
colors);
scrim.setDuration(CONTENT_SCRIM_DURATION);
scrim.setInterpolator(DEACCEL_1_5);
+
+ if (useTaskbarColor) {
+ // Hide the taskbar background color since it would duplicate the scrim.
+ scrim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ LauncherTaskbarUIController taskbarUIController =
+ mLauncher.getTaskbarUIController();
+ if (taskbarUIController != null) {
+ taskbarUIController.forceHideBackground(true);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ LauncherTaskbarUIController taskbarUIController =
+ mLauncher.getTaskbarUIController();
+ if (taskbarUIController != null) {
+ taskbarUIController.forceHideBackground(false);
+ }
+ }
+ });
+ }
+
launcherAnimator.play(scrim);
}
}
@@ -619,10 +667,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile,
windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1],
hasSplashScreen, floatingView.isDifferentFromAppIcon());
- int left = (int) (prop.cropCenterXStart - prop.cropWidthStart / 2);
- int top = (int) (prop.cropCenterYStart - prop.cropHeightStart / 2);
- int right = (int) (left + prop.cropWidthStart);
- int bottom = (int) (top + prop.cropHeightStart);
+ int left = prop.cropCenterXStart - prop.cropWidthStart / 2;
+ int top = prop.cropCenterYStart - prop.cropHeightStart / 2;
+ int right = left + prop.cropWidthStart;
+ int bottom = top + prop.cropHeightStart;
// Set the crop here so we can calculate the corner radius below.
crop.set(left, top, right, bottom);
@@ -641,6 +689,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
if (v instanceof BubbleTextView) {
((BubbleTextView) v).setStayPressed(false);
}
+ LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
+ if (taskbarController != null) {
+ taskbarController.showEdu();
+ }
openingTargets.release();
}
});
@@ -649,31 +701,33 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
? Math.max(crop.width(), crop.height()) / 2f * IconShapeManager.getWindowTransitionRadius(mLauncher)
: 0f;
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
- ? 0 : getWindowCornerRadius(mLauncher.getResources());
+ ? 0 : getWindowCornerRadius(mLauncher);
final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius;
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
- FloatProp mDx = new FloatProp(0, prop.dX, 0, prop.xDuration, AGGRESSIVE_EASE);
- FloatProp mDy = new FloatProp(0, prop.dY, 0, prop.yDuration, AGGRESSIVE_EASE);
+ FloatProp mDx = new FloatProp(0, prop.dX, 0, APP_LAUNCH_DURATION,
+ mOpeningXInterpolator);
+ FloatProp mDy = new FloatProp(0, prop.dY, 0, APP_LAUNCH_DURATION,
+ mOpeningInterpolator);
FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale,
- prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, EXAGGERATED_EASE);
+ prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f,
- APP_LAUNCH_ALPHA_START_DELAY, prop.alphaDuration, LINEAR);
+ APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, LINEAR);
FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0,
- RADIUS_DURATION, EXAGGERATED_EASE);
+ APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0,
- APP_LAUNCH_DURATION, EXAGGERATED_EASE);
+ APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd,
- 0, CROP_DURATION, EXAGGERATED_EASE);
+ 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd,
- 0, CROP_DURATION, EXAGGERATED_EASE);
+ 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 0,
- CROP_DURATION, EXAGGERATED_EASE);
+ APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0,
- CROP_DURATION, EXAGGERATED_EASE);
+ APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
NAV_FADE_OUT_INTERPOLATOR);
@@ -810,7 +864,13 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
// Since we added a start delay, call update here to init the FloatingIconView properly.
listener.onUpdate(0, true /* initOnly */);
- animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets));
+ // If app targets are translucent, do not animate the background as it causes a visible
+ // flicker when it resets itself at the end of its animation.
+ if (appTargetsAreTranslucent) {
+ animatorSet.play(appAnimator);
+ } else {
+ animatorSet.playTogether(appAnimator, getBackgroundAnimator());
+ }
return animatorSet;
}
@@ -838,7 +898,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
- ? 0 : getWindowCornerRadius(mLauncher.getResources());
+ ? 0 : getWindowCornerRadius(mLauncher);
final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher,
v, widgetBackgroundBounds,
new Size(windowTargetBounds.width(), windowTargetBounds.height()),
@@ -875,22 +935,23 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */,
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
- 0 /* start */, RADIUS_DURATION, LINEAR);
- final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, RADIUS_DURATION, LINEAR);
+ 0 /* start */, APP_LAUNCH_DURATION, mOpeningInterpolator);
+ final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, APP_LAUNCH_DURATION,
+ mOpeningInterpolator);
// Window & widget background positioning bounds
final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(),
- windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_CURVED_DURATION,
- EXAGGERATED_EASE);
+ windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_DURATION,
+ mOpeningXInterpolator);
final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(),
windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION,
- EXAGGERATED_EASE);
+ mOpeningInterpolator);
final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(),
windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION,
- EXAGGERATED_EASE);
+ mOpeningInterpolator);
final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(),
windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION,
- EXAGGERATED_EASE);
+ mOpeningInterpolator);
final FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
NAV_FADE_OUT_INTERPOLATOR);
@@ -946,11 +1007,20 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
});
- animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets));
+ // If app targets are translucent, do not animate the background as it causes a visible
+ // flicker when it resets itself at the end of its animation.
+ if (appTargetsAreTranslucent) {
+ animatorSet.play(appAnimator);
+ } else {
+ animatorSet.playTogether(appAnimator, getBackgroundAnimator());
+ }
return animatorSet;
}
- private ObjectAnimator getBackgroundAnimator(RemoteAnimationTargetCompat[] appTargets) {
+ /**
+ * Returns animator that controls depth/blur of the background.
+ */
+ private ObjectAnimator getBackgroundAnimator() {
// When launching an app from overview that doesn't map to a task, we still want to just
// blur the wallpaper instead of the launcher surface as well
boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
@@ -1020,7 +1090,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
new RemoteAnimationAdapterCompat(
new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner,
false /* startAtFrontOfQueue */),
- CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
+ CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */,
+ mLauncher.getIApplicationThread()));
if (KEYGUARD_ANIMATION.get()) {
mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
@@ -1030,7 +1101,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
new LauncherAnimationRunner(
mHandler, mKeyguardGoingAwayRunner,
true /* startAtFrontOfQueue */),
- CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
+ CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */,
+ mLauncher.getIApplicationThread()));
}
new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
@@ -1049,8 +1121,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition(
new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
- false /* startAtFrontOfQueue */));
- mLauncherOpenTransition.addHomeOpenCheck();
+ false /* startAtFrontOfQueue */), mLauncher.getIApplicationThread());
+ mLauncherOpenTransition.addHomeOpenCheck(mLauncher.getComponentName());
SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
}
}
@@ -1091,7 +1163,16 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
- return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
+ for (RemoteAnimationTargetCompat target : targets) {
+ if (target.mode == mode && target.taskInfo != null
+ // Compare component name instead of task-id because transitions will promote
+ // the target up to the root task while getTaskId returns the leaf.
+ && target.taskInfo.topActivity != null
+ && target.taskInfo.topActivity.equals(mLauncher.getComponentName())) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -1111,7 +1192,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
- QuickStepContract.getWindowCornerRadius(mLauncher.getResources());
+ QuickStepContract.getWindowCornerRadius(mLauncher);
unlockAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -1141,10 +1222,181 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
/**
- * Animator that controls the transformations of the windows the targets that are closing.
+ * Returns view on launcher that corresponds to the closing app in the list of app targets
*/
- private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets) {
+ private @Nullable View findLauncherView(RemoteAnimationTargetCompat[] appTargets) {
+ for (RemoteAnimationTargetCompat appTarget : appTargets) {
+ if (appTarget.mode == MODE_CLOSING) {
+ View launcherView = findLauncherView(appTarget);
+ if (launcherView != null) {
+ return launcherView;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns view on launcher that corresponds to the {@param runningTaskTarget}.
+ */
+ private @Nullable View findLauncherView(RemoteAnimationTargetCompat runningTaskTarget) {
+ if (runningTaskTarget == null || runningTaskTarget.taskInfo == null) {
+ return null;
+ }
+
+ final ComponentName[] taskInfoActivities = new ComponentName[] {
+ runningTaskTarget.taskInfo.baseActivity,
+ runningTaskTarget.taskInfo.origActivity,
+ runningTaskTarget.taskInfo.realActivity,
+ runningTaskTarget.taskInfo.topActivity};
+
+ String packageName = null;
+ for (ComponentName component : taskInfoActivities) {
+ if (component != null && component.getPackageName() != null) {
+ packageName = component.getPackageName();
+ break;
+ }
+ }
+
+ if (packageName == null) {
+ return null;
+ }
+
+ // Find the associated item info for the launch cookie (if available), note that predicted
+ // apps actually have an id of -1, so use another default id here
+ final ArrayList launchCookies = runningTaskTarget.taskInfo.launchCookies == null
+ ? new ArrayList<>()
+ : runningTaskTarget.taskInfo.launchCookies;
+
+ int launchCookieItemId = NO_MATCHING_ID;
+ for (IBinder cookie : launchCookies) {
+ Integer itemId = ObjectWrapper.unwrap(cookie);
+ if (itemId != null) {
+ launchCookieItemId = itemId;
+ break;
+ }
+ }
+
+ return mLauncher.getFirstMatchForAppClose(launchCookieItemId,
+ packageName, UserHandle.of(runningTaskTarget.taskInfo.userId));
+ }
+
+ private @NonNull RectF getDefaultWindowTargetRect() {
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ final int halfIconSize = dp.iconSizePx / 2;
+ float primaryDimension = orientationHandler
+ .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
+ float secondaryDimension = orientationHandler
+ .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
+ final float targetX = primaryDimension / 2f;
+ final float targetY = secondaryDimension - dp.hotseatBarSizePx;
+ return new RectF(targetX - halfIconSize, targetY - halfIconSize,
+ targetX + halfIconSize, targetY + halfIconSize);
+ }
+
+ /**
+ * Closing animator that animates the window into its final location on the workspace.
+ */
+ private void getClosingWindowAnimators(AnimatorSet animation,
+ RemoteAnimationTargetCompat[] targets, View launcherView, PointF velocityPxPerS) {
+ FloatingIconView floatingIconView = null;
+ FloatingWidgetView floatingWidget = null;
+ RectF targetRect = new RectF();
+
+ RemoteAnimationTargetCompat runningTaskTarget = null;
+ boolean isTransluscent = false;
+ for (RemoteAnimationTargetCompat target : targets) {
+ if (target.mode == MODE_CLOSING) {
+ runningTaskTarget = target;
+ isTransluscent = runningTaskTarget.isTranslucent;
+ break;
+ }
+ }
+
+ // Get floating view and target rect.
+ if (launcherView instanceof LauncherAppWidgetHostView) {
+ Size windowSize = new Size(mDeviceProfile.availableWidthPx,
+ mDeviceProfile.availableHeightPx);
+ int fallbackBackgroundColor =
+ FloatingWidgetView.getDefaultBackgroundColor(mLauncher, runningTaskTarget);
+ floatingWidget = FloatingWidgetView.getFloatingWidgetView(mLauncher,
+ (LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
+ mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
+ isTransluscent, fallbackBackgroundColor);
+ } else if (launcherView != null) {
+ floatingIconView = getFloatingIconView(mLauncher, launcherView,
+ true /* hideOriginal */, targetRect, false /* isOpening */);
+ } else {
+ targetRect.set(getDefaultWindowTargetRect());
+ }
+
+ final RectF startRect = new RectF(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
+ RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mLauncher,
+ mDeviceProfile);
+
+ // Hook up floating views to the closing window animators.
+ final int rotationChange = getRotationChange(targets);
+ Rect windowTargetBounds = getWindowTargetBounds(targets, rotationChange);
+ if (floatingIconView != null) {
+ anim.addAnimatorListener(floatingIconView);
+ floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
+ floatingIconView.setFastFinishRunnable(anim::end);
+ FloatingIconView finalFloatingIconView = floatingIconView;
+
+ // We want the window alpha to be 0 once this threshold is met, so that the
+ // FolderIconView can be seen morphing into the icon shape.
+ final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
+
+ RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
+ windowTargetBounds) {
+ @Override
+ public void onUpdate(RectF currentRectF, float progress) {
+ finalFloatingIconView.update(1f, 255 /* fgAlpha */, currentRectF, progress,
+ windowAlphaThreshold, getCornerRadius(progress), false);
+
+ super.onUpdate(currentRectF, progress);
+ }
+ };
+ anim.addOnUpdateListener(runner);
+ } else if (floatingWidget != null) {
+ anim.addAnimatorListener(floatingWidget);
+ floatingWidget.setOnTargetChangeListener(anim::onTargetPositionChanged);
+ floatingWidget.setFastFinishRunnable(anim::end);
+
+ final float floatingWidgetAlpha = isTransluscent ? 0 : 1;
+ FloatingWidgetView finalFloatingWidget = floatingWidget;
+ RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
+ windowTargetBounds) {
+ @Override
+ public void onUpdate(RectF currentRectF, float progress) {
+ final float fallbackBackgroundAlpha =
+ 1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE);
+ final float foregroundAlpha =
+ mapBoundToRange(progress, 0.5f, 1, 0, 1, EXAGGERATED_EASE);
+ finalFloatingWidget.update(currentRectF, floatingWidgetAlpha, foregroundAlpha,
+ fallbackBackgroundAlpha, 1 - progress);
+
+ super.onUpdate(currentRectF, progress);
+ }
+ };
+ anim.addOnUpdateListener(runner);
+ }
+
+ // Use a fixed velocity to start the animation.
+ animation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ anim.start(mLauncher, velocityPxPerS);
+ }
+ });
+ }
+
+ /**
+ * Closing window animator that moves the window down and offscreen.
+ */
+ private Animator getFallbackClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets) {
final int rotationChange = getRotationChange(appTargets);
SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
Matrix matrix = new Matrix();
@@ -1153,7 +1405,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
int duration = CLOSING_TRANSITION_DURATION_MS;
float windowCornerRadius = mDeviceProfile.isMultiWindowMode
- ? 0 : getWindowCornerRadius(mLauncher.getResources());
+ ? 0 : getWindowCornerRadius(mLauncher);
float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
closingAnimator.setDuration(duration);
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
@@ -1283,7 +1535,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
LauncherAnimationRunner.AnimationResult result) {
if (mLauncher.isDestroyed()) {
AnimatorSet anim = new AnimatorSet();
- anim.play(getClosingWindowAnimators(appTargets, wallpaperTargets));
+ anim.play(getFallbackClosingWindowAnimators(appTargets));
result.setAnimation(anim, mLauncher.getApplicationContext());
return;
}
@@ -1310,9 +1562,39 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
if (anim == null) {
anim = new AnimatorSet();
- anim.play(mFromUnlock
- ? getUnlockWindowAnimator(appTargets, wallpaperTargets)
- : getClosingWindowAnimators(appTargets, wallpaperTargets));
+
+ final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
+ || launcherIsATargetWithMode(appTargets, MODE_OPENING);
+
+ View launcherView = findLauncherView(appTargets);
+ boolean playFallBackAnimation = (launcherView == null
+ && launcherIsForceInvisibleOrOpening)
+ || mLauncher.getWorkspace().isOverlayShown();
+
+ boolean playWorkspaceReveal = true;
+ boolean skipAllAppsScale = false;
+ if (mFromUnlock) {
+ anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
+ } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
+ && !playFallBackAnimation) {
+ // Use a fixed velocity to start the animation.
+ float velocityPxPerS = DynamicResource.provider(mLauncher)
+ .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
+ PointF velocity = new PointF(0, -velocityPxPerS);
+ getClosingWindowAnimators(anim, appTargets, launcherView, velocity);
+ if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
+ anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
+ true /* animateOverviewScrim */, launcherView).getAnimators());
+ // We play StaggeredWorkspaceAnim as a part of the closing window animation.
+ playWorkspaceReveal = false;
+ } else {
+ // Skip scaling all apps, otherwise FloatingIconView will get wrong
+ // layout bounds.
+ skipAllAppsScale = true;
+ }
+ } else {
+ anim.play(getFallbackClosingWindowAnimators(appTargets));
+ }
// Normally, we run the launcher content animation when we are transitioning
// home, but if home is already visible, then we don't want to animate the
@@ -1322,8 +1604,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
// targets list because it is already visible). In that case, we force
// invisibility on touch down, and only reset it after the animation to home
// is initialized.
- if (launcherIsATargetWithMode(appTargets, MODE_OPENING)
- || mLauncher.isForceInvisible()) {
+ if (launcherIsForceInvisibleOrOpening) {
addCujInstrumentation(
anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
// Only register the content animation for cancellation when state changes
@@ -1331,7 +1612,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
Pair contentAnimator =
- getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY);
+ getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
+ skipAllAppsScale);
anim.play(contentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -1340,7 +1622,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
});
} else {
- anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
+ if (playWorkspaceReveal) {
+ anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
+ }
}
}
}
@@ -1427,10 +1711,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
public final float dX;
public final float dY;
- public final long xDuration;
- public final long yDuration;
- public final long alphaDuration;
-
public final float initialAppIconScale;
public final float finalAppIconScale;
@@ -1462,14 +1742,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
dX = centerX - launcherIconBounds.centerX();
dY = centerY - launcherIconBounds.centerY();
- boolean useUpwardAnimation = launcherIconBounds.top > centerY
- || Math.abs(dY) < dp.cellHeightPx;
- xDuration = useUpwardAnimation ? APP_LAUNCH_CURVED_DURATION
- : APP_LAUNCH_DOWN_DURATION;
- yDuration = useUpwardAnimation ? APP_LAUNCH_DURATION
- : APP_LAUNCH_DOWN_CURVED_DURATION;
- alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION
- : APP_LAUNCH_ALPHA_DOWN_DURATION;
iconAlphaStart = hasSplashScreen && !hasDifferentAppIcon ? 0 : 1f;
final int windowIconSize = ResourceUtils.getDimenByName("starting_surface_icon_size",
@@ -1501,4 +1773,101 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
mTransitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color));
}
}
+
+ /**
+ * RectFSpringAnim update listener to be used for app to home animation.
+ */
+ private class SpringAnimRunner implements RectFSpringAnim.OnUpdateListener {
+ private final RemoteAnimationTargetCompat[] mAppTargets;
+ private final Matrix mMatrix = new Matrix();
+ private final Point mTmpPos = new Point();
+ private final Rect mCurrentRect = new Rect();
+ private final float mStartRadius;
+ private final float mEndRadius;
+ private final SurfaceTransactionApplier mSurfaceApplier;
+ private final Rect mWindowTargetBounds = new Rect();
+
+ private final Rect mTmpRect = new Rect();
+
+ SpringAnimRunner(RemoteAnimationTargetCompat[] appTargets, RectF targetRect,
+ Rect windowTargetBounds) {
+ mAppTargets = appTargets;
+ mStartRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
+ mEndRadius = Math.max(1, targetRect.width()) / 2f;
+ mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer);
+ mWindowTargetBounds.set(windowTargetBounds);
+ }
+
+ public float getCornerRadius(float progress) {
+ return Utilities.mapRange(progress, mStartRadius, mEndRadius);
+ }
+
+ @Override
+ public void onUpdate(RectF currentRectF, float progress) {
+ SurfaceParams[] params = new SurfaceParams[mAppTargets.length];
+ for (int i = mAppTargets.length - 1; i >= 0; i--) {
+ RemoteAnimationTargetCompat target = mAppTargets[i];
+ SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
+
+ if (target.localBounds != null) {
+ mTmpPos.set(target.localBounds.left, target.localBounds.top);
+ } else {
+ mTmpPos.set(target.position.x, target.position.y);
+ }
+
+ if (target.mode == MODE_CLOSING) {
+ currentRectF.round(mCurrentRect);
+
+ // 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 (mWindowTargetBounds.height() > mWindowTargetBounds.width()) {
+ scale = Math.min(1f, currentRectF.width() / mWindowTargetBounds.width());
+
+ int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale));
+ int croppedHeight = mWindowTargetBounds.height() - unscaledHeight;
+ mTmpRect.set(0, 0, mWindowTargetBounds.width(),
+ mWindowTargetBounds.height() - croppedHeight);
+ } else {
+ scale = Math.min(1f, currentRectF.height() / mWindowTargetBounds.height());
+
+ int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale));
+ int croppedWidth = mWindowTargetBounds.width() - unscaledWidth;
+ mTmpRect.set(0, 0, mWindowTargetBounds.width() - croppedWidth,
+ mWindowTargetBounds.height());
+ }
+
+ // Match size and position of currentRect.
+ mMatrix.setScale(scale, scale);
+ mMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top);
+
+ builder.withMatrix(mMatrix)
+ .withWindowCrop(mTmpRect)
+ .withAlpha(getWindowAlpha(progress))
+ .withCornerRadius(getCornerRadius(progress) / scale);
+ } else if (target.mode == MODE_OPENING) {
+ mMatrix.setTranslate(mTmpPos.x, mTmpPos.y);
+ builder.withMatrix(mMatrix)
+ .withAlpha(1f);
+ }
+ params[i] = builder.build();
+ }
+ mSurfaceApplier.scheduleApply(params);
+ }
+
+ protected float getWindowAlpha(float progress) {
+ // Alpha interpolates between [1, 0] between progress values [start, end]
+ final float start = 0f;
+ final float end = 0.85f;
+
+ if (progress <= start) {
+ return 1f;
+ }
+ if (progress >= end) {
+ return 0f;
+ }
+ return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 63a569a65c..1b0f967a1f 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -282,8 +282,7 @@ public class AppsDividerView extends View implements StateListener items) {
mPendingPredictedItems = null;
mPredictedApps.clear();
- items.stream()
+ mPredictedApps.addAll(items.stream()
.filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
- .map(itemInfo -> (WorkspaceItemInfo) itemInfo)
- .forEach(mPredictedApps::add);
+ .map(itemInfo -> (WorkspaceItemInfo) itemInfo).collect(Collectors.toList()));
applyPredictionApps();
}
@@ -249,8 +249,7 @@ public class PredictionRowView extends LinearLayout implements
@Override
public void setInsets(Rect insets, DeviceProfile grid) {
- int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
- + grid.cellLayoutPaddingLeftRightPx;
+ int leftRightPadding = grid.allAppsLeftRightPadding;
setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
}
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
deleted file mode 100644
index 3a7d821ed6..0000000000
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.hybridhotseat;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ActivityTracker;
-
-/**
- * Proxy activity to return user to home screen and show halfsheet education
- */
-public class HotseatEduActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent homeIntent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setPackage(getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- Launcher.ACTIVITY_TRACKER.registerCallback(new HotseatActivityTracker());
- startActivity(homeIntent);
- finish();
- }
-
- static class HotseatActivityTracker implements
- ActivityTracker.SchedulerCallback {
-
- @Override
- public boolean init(BaseActivity activity, boolean alreadyOnHome) {
- QuickstepLauncher launcher = (QuickstepLauncher) activity;
- if (launcher != null) {
- launcher.getHotseatPredictionController().showEdu();
- }
- return false;
- }
-
- }
-}
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index a6844e48b7..680012ce28 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -15,18 +15,22 @@
*/
package com.android.launcher3.hybridhotseat;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
- .LAUNCHER_HOTSEAT_EDU_ONLY_TIP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_ONLY_TIP;
import android.content.Intent;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.Gravity;
import android.view.View;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.FolderInfo;
@@ -47,6 +51,8 @@ import java.util.stream.IntStream;
*/
public class HotseatEduController {
+ private static final String TAG = "HotseatEduController";
+
public static final String SETTINGS_ACTION =
"android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
@@ -188,8 +194,12 @@ public class HotseatEduController {
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
mNewScreens = IntArray.wrap(pageId);
}
- for (int i = 0; i < mLauncher.getDeviceProfile().numShownHotseatIcons; i++) {
- View child = mHotseat.getChildAt(i, 0);
+ boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
+ int hotseatItemsNum = mLauncher.getDeviceProfile().numShownHotseatIcons;
+ for (int i = 0; i < hotseatItemsNum; i++) {
+ int x = isPortrait ? i : 0;
+ int y = isPortrait ? 0 : hotseatItemsNum - i - 1;
+ View child = mHotseat.getChildAt(x, y);
if (child == null || child.getTag() == null) continue;
ItemInfo tag = (ItemInfo) child.getTag();
if (tag.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) continue;
@@ -229,8 +239,7 @@ public class HotseatEduController {
R.string.hotseat_prediction_settings, null,
() -> mLauncher.startActivity(getSettingsIntent()));
} else {
- new ArrowTipView(mLauncher).show(
- mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
+ showHotseatArrowTip(true, mLauncher.getString(R.string.hotseat_tip_no_empty_slots));
}
}
@@ -251,15 +260,50 @@ public class HotseatEduController {
if (requiresMigration && canMigrateToFirstPage) {
showDialog();
} else {
- new ArrowTipView(mLauncher).show(mLauncher.getString(
+ if (showHotseatArrowTip(requiresMigration, mLauncher.getString(
requiresMigration ? R.string.hotseat_tip_no_empty_slots
- : R.string.hotseat_auto_enrolled),
- mHotseat.getTop());
- mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP);
+ : R.string.hotseat_auto_enrolled))) {
+ mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP);
+ }
finishOnboarding();
}
}
+ /**
+ * Finds a child suitable child in hotseat and shows arrow tip pointing at it.
+ *
+ * @param usePinned used to determine target view. If true, will use the first matching pinned
+ * item. Otherwise, will use the first predicted child
+ * @param message String to be shown inside the arrowView
+ * @return whether suitable child was found and tip was shown
+ */
+ private boolean showHotseatArrowTip(boolean usePinned, String message) {
+ int childCount = mHotseat.getShortcutsAndWidgets().getChildCount();
+ boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
+
+ BubbleTextView tipTargetView = null;
+ for (int i = childCount - 1; i > -1; i--) {
+ int x = isPortrait ? i : 0;
+ int y = isPortrait ? 0 : i;
+ View v = mHotseat.getShortcutsAndWidgets().getChildAt(x, y);
+ if (v instanceof BubbleTextView && v.getTag() instanceof WorkspaceItemInfo) {
+ ItemInfo info = (ItemInfo) v.getTag();
+ boolean isPinned = info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ if (isPinned == usePinned) {
+ tipTargetView = (BubbleTextView) v;
+ break;
+ }
+ }
+ }
+ if (tipTargetView == null) {
+ Log.e(TAG, "Unable to find suitable view for ArrowTip");
+ return false;
+ }
+ Rect bounds = Utilities.getViewBounds(tipTargetView);
+ new ArrowTipView(mLauncher).show(message, Gravity.END, bounds.centerX(), bounds.top);
+ return true;
+ }
+
void showDialog() {
if (mPredictedApps == null || mPredictedApps.isEmpty()) {
return;
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 14b0c5dd62..119ae907f7 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -28,17 +28,20 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
+import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.views.AbstractSlideInView;
@@ -77,6 +80,11 @@ public class HotseatEduDialog extends AbstractSlideInView implements I
mContent = this;
}
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ setTranslationShift(TRANSLATION_SHIFT_CLOSED);
+ }
@Override
protected void onFinishInflate() {
@@ -84,8 +92,9 @@ public class HotseatEduDialog extends AbstractSlideInView implements I
mHotseatWrapper = findViewById(R.id.hotseat_wrapper);
mSampleHotseat = findViewById(R.id.sample_prediction);
+ Context context = getContext();
DeviceProfile grid = mActivityContext.getDeviceProfile();
- Rect padding = grid.getHotseatLayoutPadding();
+ Rect padding = grid.getHotseatLayoutPadding(context);
mSampleHotseat.getLayoutParams().height = grid.cellHeightPx;
mSampleHotseat.setGridSize(grid.numShownHotseatIcons, 1);
@@ -97,6 +106,15 @@ public class HotseatEduDialog extends AbstractSlideInView implements I
mDismissBtn = findViewById(R.id.no_thanks);
mDismissBtn.setOnClickListener(this::onDismiss);
+ LinearLayout buttonContainer = findViewById(R.id.button_container);
+ int adjustedMarginEnd = ApiWrapper.getHotseatEndOffset(context)
+ - buttonContainer.getPaddingEnd();
+ if (InvariantDeviceProfile.INSTANCE.get(context)
+ .getDeviceProfile(context).isTaskbarPresent && adjustedMarginEnd > 0) {
+ ((LinearLayout.LayoutParams) buttonContainer.getLayoutParams()).setMarginEnd(
+ adjustedMarginEnd);
+ }
+
// update ui to reflect which migration method is going to be used
if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
((TextView) findViewById(R.id.hotseat_edu_content)).setText(
@@ -200,9 +218,9 @@ public class HotseatEduDialog extends AbstractSlideInView implements I
}
AbstractFloatingView.closeAllOpenViews(mActivityContext);
attachToContainer();
- mActivityContext.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_SEEN);
animateOpen();
populatePreview(predictions);
+ mActivityContext.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_SEEN);
}
/**
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 85e5ab0a9b..85d9f01735 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -55,8 +55,8 @@ import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.OnboardingPrefs;
-import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.Snackbar;
import java.util.ArrayList;
@@ -152,37 +152,14 @@ public class HotseatPredictionController implements DragController.DragListener,
*/
public void showEdu() {
mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
- if (mPredictedItems.isEmpty()) {
- // launcher has empty predictions set
- Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
- R.string.hotseat_prediction_settings, null,
- () -> mLauncher.startActivity(getSettingsIntent()));
- } else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) {
- showDiscoveryTip();
- } else {
- HotseatEduController eduController = new HotseatEduController(mLauncher);
- eduController.setPredictedApps(mPredictedItems.stream()
- .map(i -> (WorkspaceItemInfo) i)
- .collect(Collectors.toList()));
- eduController.showEdu();
- }
+ HotseatEduController eduController = new HotseatEduController(mLauncher);
+ eduController.setPredictedApps(mPredictedItems.stream()
+ .map(i -> (WorkspaceItemInfo) i)
+ .collect(Collectors.toList()));
+ eduController.showEdu();
}));
}
- /**
- * Shows educational tip for hotseat if user does not go through Tips app.
- */
- private void showDiscoveryTip() {
- if (getPredictedIcons().isEmpty()) {
- new ArrowTipView(mLauncher).show(
- mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
- } else {
- Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
- R.string.hotseat_prediction_settings, null,
- () -> mLauncher.startActivity(getSettingsIntent()));
- }
- }
-
/**
* Returns if hotseat client has predictions
*/
@@ -200,6 +177,7 @@ public class HotseatPredictionController implements DragController.DragListener,
}
int predictionIndex = 0;
+ int numViewsAnimated = 0;
ArrayList newItems = new ArrayList<>();
// make sure predicted icon removal and filling predictions don't step on each other
if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
@@ -233,7 +211,11 @@ public class HotseatPredictionController implements DragController.DragListener,
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
if (isPredictedIcon(child) && child.isEnabled()) {
PredictedAppIcon icon = (PredictedAppIcon) child;
- icon.applyFromWorkspaceItem(predictedItem);
+ boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
+ icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
+ if (animateIconChange) {
+ numViewsAnimated++;
+ }
icon.finishBinding(mPredictionLongClickListener);
} else {
newItems.add(predictedItem);
@@ -262,10 +244,6 @@ public class HotseatPredictionController implements DragController.DragListener,
} else {
removeOutlineDrawings();
}
-
- if (mLauncher.getTaskbarUIController() != null) {
- mLauncher.getTaskbarUIController().onHotseatUpdated();
- }
}
private void removeOutlineDrawings() {
@@ -496,6 +474,28 @@ public class HotseatPredictionController implements DragController.DragListener,
.log(LAUNCHER_HOTSEAT_RANKED);
}
+ /**
+ * Called when app/shortcut icon is removed by system. This is used to prune visible stale
+ * predictions while while waiting for AppAPrediction service to send new batch of predictions.
+ *
+ * @param matcher filter matching items that have been removed
+ */
+ public void onModelItemsRemoved(ItemInfoMatcher matcher) {
+ if (mPredictedItems.removeIf(matcher::matchesInfo)) {
+ fillGapsWithPrediction(true);
+ }
+ }
+
+ /**
+ * Called when user completes adding item requiring a config activity to the hotseat
+ */
+ public void onDeferredDrop(int cellX, int cellY) {
+ View child = mHotseat.getChildAt(cellX, cellY);
+ if (child instanceof PredictedAppIcon) {
+ removeIconWithoutNotify((PredictedAppIcon) child);
+ }
+ }
+
private class PinPrediction extends SystemShortcut {
private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
index 080633a653..56945ba0a6 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -16,33 +16,25 @@
package com.android.launcher3.hybridhotseat;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
+import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction;
+import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
-import android.app.prediction.AppTargetId;
-import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.Workspace;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.shortcuts.ShortcutKey;
import java.util.ArrayList;
-import java.util.Locale;
/**
* Model helper for app predictions in workspace
*/
public class HotseatPredictionModel {
- private static final String APP_LOCATION_HOTSEAT = "hotseat";
- private static final String APP_LOCATION_WORKSPACE = "workspace";
-
private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events";
private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items";
@@ -54,15 +46,15 @@ public class HotseatPredictionModel {
ArrayList events = new ArrayList<>();
ArrayList workspaceItems = dataModel.getAllWorkspaceItems();
for (ItemInfo item : workspaceItems) {
- AppTarget target = getAppTargetFromInfo(context, item);
- if (target != null && !isTrackedForPrediction(item)) continue;
- events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
+ AppTarget target = getAppTargetFromItemInfo(context, item);
+ if (target != null && !isTrackedForHotseatPrediction(item)) continue;
+ events.add(wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item));
}
ArrayList currentTargets = new ArrayList<>();
FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
if (hotseatItems != null) {
for (ItemInfo itemInfo : hotseatItems.items) {
- AppTarget target = getAppTargetFromInfo(context, itemInfo);
+ AppTarget target = getAppTargetFromItemInfo(context, itemInfo);
if (target != null) currentTargets.add(target);
}
}
@@ -70,56 +62,4 @@ public class HotseatPredictionModel {
bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
return bundle;
}
-
- /**
- * Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null
- * if item is not supported prediction
- */
- public static AppTarget getAppTargetFromInfo(Context context, ItemInfo info) {
- if (info == null) return null;
- if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
- && info instanceof LauncherAppWidgetInfo
- && ((LauncherAppWidgetInfo) info).providerName != null) {
- ComponentName cn = ((LauncherAppWidgetInfo) info).providerName;
- return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()),
- cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
- } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
- && info.getTargetComponent() != null) {
- ComponentName cn = info.getTargetComponent();
- return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
- cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
- } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
- && info instanceof WorkspaceItemInfo) {
- ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info);
- //TODO: switch to using full shortcut info
- return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()),
- shortcutKey.componentName.getPackageName(), shortcutKey.user).build();
- } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
- return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
- context.getPackageName(), info.user).build();
- }
- return null;
- }
-
- /**
- * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item
- * location using {@link ItemInfo}
- */
- public static AppTargetEvent wrapAppTargetWithLocation(
- AppTarget target, int action, ItemInfo info) {
- String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]",
- info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
- ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE,
- info.screenId, info.cellX, info.cellY, info.spanX, info.spanY);
- return new AppTargetEvent.Builder(target, action).setLaunchLocation(location).build();
- }
-
- /**
- * Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors
- */
- public static boolean isTrackedForPrediction(ItemInfo info) {
- return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT || (
- info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
- && info.screenId == Workspace.FIRST_SCREEN_ID);
- }
}
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index eed493d4cf..7c29c5b4c6 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -22,6 +22,7 @@ import static android.app.prediction.AppTargetEvent.ACTION_UNPIN;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
@@ -30,10 +31,13 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
+import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction;
+import static com.android.launcher3.model.PredictionHelper.isTrackedForWidgetPrediction;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.annotation.TargetApi;
@@ -61,7 +65,6 @@ import com.android.launcher3.logger.LauncherAtom.FolderContainer;
import com.android.launcher3.logger.LauncherAtom.HotseatContainer;
import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer;
import com.android.launcher3.logging.StatsLogManager.EventEnum;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
@@ -140,6 +143,9 @@ public class AppEventProducer implements StatsLogConsumer {
if (isTrackedForHotseatPrediction(mLastDragItem)) {
sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
}
+ if (isTrackedForWidgetPrediction(atomInfo)) {
+ sendEvent(atomInfo, ACTION_PIN, CONTAINER_WIDGETS_PREDICTION);
+ }
mLastDragItem = null;
} else if (event == LAUNCHER_ITEM_DROP_FOLDER_CREATED) {
if (isTrackedForHotseatPrediction(atomInfo)) {
@@ -157,10 +163,18 @@ public class AppEventProducer implements StatsLogConsumer {
if (mLastDragItem != null && isTrackedForHotseatPrediction(mLastDragItem)) {
sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
}
+ if (mLastDragItem != null && isTrackedForWidgetPrediction(mLastDragItem)) {
+ sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_WIDGETS_PREDICTION);
+ }
} else if (event == LAUNCHER_HOTSEAT_PREDICTION_PINNED) {
if (isTrackedForHotseatPrediction(atomInfo)) {
sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
}
+ } else if (event == LAUNCHER_ONRESUME) {
+ AppTarget target = new AppTarget.Builder(new AppTargetId("launcher:launcher"),
+ mContext.getPackageName(), Process.myUserHandle())
+ .build();
+ sendEvent(target, atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION);
}
}
@@ -257,9 +271,6 @@ public class AppEventProducer implements StatsLogConsumer {
case ALL_APPS_CONTAINER: {
return "all-apps";
}
- case SEARCH_RESULT_CONTAINER: {
- return "search-results";
- }
case PREDICTED_HOTSEAT_CONTAINER: {
return "predictions/hotseat";
}
@@ -279,6 +290,16 @@ public class AppEventProducer implements StatsLogConsumer {
}
return "folder";
}
+ case SEARCH_RESULT_CONTAINER:
+ return "search-results";
+ case EXTENDED_CONTAINERS: {
+ switch(ci.getExtendedContainers().getContainerCase()) {
+ case DEVICE_SEARCH_RESULT_CONTAINER:
+ case CORRECTED_DEVICE_SEARCH_RESULT_CONTAINER:
+ return "search-results";
+ }
+ }
+ default: // fall out
}
return "";
}
@@ -296,19 +317,4 @@ public class AppEventProducer implements StatsLogConsumer {
return TextUtils.isEmpty(componentNameString)
? null : ComponentName.unflattenFromString(componentNameString);
}
-
- /**
- * Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors
- */
- private static boolean isTrackedForHotseatPrediction(LauncherAtom.ItemInfo info) {
- ContainerInfo ci = info.getContainerInfo();
- switch (ci.getContainerCase()) {
- case HOTSEAT:
- return true;
- case WORKSPACE:
- return ci.getWorkspace().getPageIndex() == 0;
- default:
- return false;
- }
- }
}
diff --git a/quickstep/src/com/android/launcher3/model/PredictionHelper.java b/quickstep/src/com/android/launcher3/model/PredictionHelper.java
new file mode 100644
index 0000000000..738dd83cbc
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/PredictionHelper.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 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 static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.WORKSPACE;
+
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
+
+import java.util.Locale;
+
+/** Helper class with methods for converting launcher items to form usable by predictors */
+public final class PredictionHelper {
+ private static final String APP_LOCATION_HOTSEAT = "hotseat";
+ private static final String APP_LOCATION_WORKSPACE = "workspace";
+
+ /**
+ * Creates and returns an {@link AppTarget} object for an {@link ItemInfo}. Returns null
+ * if item type is not supported in predictions
+ */
+ @Nullable
+ public static AppTarget getAppTargetFromItemInfo(Context context, ItemInfo info) {
+ if (info == null) return null;
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+ && info instanceof LauncherAppWidgetInfo
+ && ((LauncherAppWidgetInfo) info).providerName != null) {
+ ComponentName cn = ((LauncherAppWidgetInfo) info).providerName;
+ return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()),
+ cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ && info.getTargetComponent() != null) {
+ ComponentName cn = info.getTargetComponent();
+ return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
+ cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ && info instanceof WorkspaceItemInfo) {
+ ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info);
+ //TODO: switch to using full shortcut info
+ return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()),
+ shortcutKey.componentName.getPackageName(), shortcutKey.user).build();
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
+ context.getPackageName(), info.user).build();
+ }
+ return null;
+ }
+
+ /**
+ * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item
+ * location using {@link ItemInfo}
+ */
+ public static AppTargetEvent wrapAppTargetWithItemLocation(
+ AppTarget target, int action, ItemInfo info) {
+ String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]",
+ info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE,
+ info.screenId, info.cellX, info.cellY, info.spanX, info.spanY);
+ return new AppTargetEvent.Builder(target, action).setLaunchLocation(location).build();
+ }
+
+ /**
+ * Helper method to determine if {@link ItemInfo} should be tracked and reported to hotseat
+ * predictors
+ */
+ public static boolean isTrackedForHotseatPrediction(ItemInfo info) {
+ return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT || (
+ info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
+ && info.screenId == Workspace.FIRST_SCREEN_ID);
+ }
+
+ /**
+ * Helper method to determine if {@link LauncherAtom.ItemInfo} should be tracked and reported to
+ * hotseat predictors
+ */
+ public static boolean isTrackedForHotseatPrediction(LauncherAtom.ItemInfo info) {
+ LauncherAtom.ContainerInfo ci = info.getContainerInfo();
+ switch (ci.getContainerCase()) {
+ case HOTSEAT:
+ return true;
+ case WORKSPACE:
+ return ci.getWorkspace().getPageIndex() == 0;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Helper method to determine if {@link ItemInfo} should be tracked and reported to widget
+ * predictors
+ */
+ public static boolean isTrackedForWidgetPrediction(ItemInfo info) {
+ return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+ && info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ }
+
+ /**
+ * Helper method to determine if {@link LauncherAtom.ItemInfo} should be tracked and reported
+ * to widget predictors
+ */
+ public static boolean isTrackedForWidgetPrediction(LauncherAtom.ItemInfo info) {
+ return info.getItemCase() == LauncherAtom.ItemInfo.ItemCase.WIDGET
+ && info.getContainerInfo().getContainerCase() == WORKSPACE;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 44bac1e8f2..8814a5657d 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -25,8 +25,14 @@ import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICA
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
+import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
+import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.Manifest;
+import static java.util.stream.Collectors.toCollection;
+
+import android.app.StatsManager;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
import android.app.prediction.AppPredictor;
@@ -39,8 +45,10 @@ import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
+import android.util.StatsEvent;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@@ -48,9 +56,9 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
+import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -59,11 +67,13 @@ import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.PersistedItemArray;
+import com.android.quickstep.logging.SettingsChangeLogger;
import com.android.quickstep.logging.StatsLogCompatManager;
+import com.android.systemui.shared.system.SysUiStatsLog;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -73,10 +83,11 @@ import java.util.stream.IntStream;
/**
* Model delegate which loads prediction items
*/
-public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener {
+public class QuickstepModelDelegate extends ModelDelegate {
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
+ private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
private static final boolean IS_DEBUG = false;
@@ -91,15 +102,18 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
private final InvariantDeviceProfile mIDP;
private final AppEventProducer mAppEventProducer;
+ private final StatsManager mStatsManager;
+ private final Context mContext;
protected boolean mActive = false;
public QuickstepModelDelegate(Context context) {
+ mContext = context;
mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
mIDP = InvariantDeviceProfile.INSTANCE.get(context);
- mIDP.addOnChangeListener(this);
StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
+ mStatsManager = context.getSystemService(StatsManager.class);
}
@Override
@@ -155,17 +169,82 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
}
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
for (ItemInfo info : itemsIdMap) {
- FolderInfo parent = info.container > 0
- ? (FolderInfo) itemsIdMap.get(info.container) : null;
+ FolderInfo parent = getContainer(info, itemsIdMap);
StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
}
additionalSnapshotEvents(instanceId);
prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
}
+
+ // Only register for launcher snapshot logging if this is the primary ModelDelegate
+ // instance, as there will be additional instances that may be destroyed at any time.
+ if (mIsPrimaryInstance) {
+ registerSnapshotLoggingCallback();
+ }
}
protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){}
+ /**
+ * Registers a callback to log launcher workspace layout using Statsd pulled atom.
+ */
+ protected void registerSnapshotLoggingCallback() {
+ if (mStatsManager == null) {
+ Log.d(TAG, "Failed to get StatsManager");
+ }
+
+ try {
+ mStatsManager.setPullAtomCallback(
+ SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT,
+ null /* PullAtomMetadata */,
+ MODEL_EXECUTOR,
+ (i, eventList) -> {
+ InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+ IntSparseArrayMap itemsIdMap;
+ synchronized (mDataModel) {
+ itemsIdMap = mDataModel.itemsIdMap.clone();
+ }
+
+ for (ItemInfo info : itemsIdMap) {
+ FolderInfo parent = getContainer(info, itemsIdMap);
+ LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
+ Log.d(TAG, itemInfo.toString());
+ StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
+ instanceId);
+ eventList.add(statsEvent);
+ }
+ Log.d(TAG,
+ String.format(
+ "Successfully logged %d workspace items with instanceId=%d",
+ itemsIdMap.size(), instanceId.getId()));
+ additionalSnapshotEvents(instanceId);
+ SettingsChangeLogger.INSTANCE.get(mContext).logSnapshot(instanceId);
+ return StatsManager.PULL_SUCCESS;
+ }
+ );
+ Log.d(TAG, "Successfully registered for launcher snapshot logging!");
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Failed to register launcher snapshot logging callback with StatsManager",
+ e);
+ }
+ }
+
+ private static FolderInfo getContainer(ItemInfo info, IntSparseArrayMap itemsIdMap) {
+ if (info.container > 0) {
+ ItemInfo containerInfo = itemsIdMap.get(info.container);
+
+ if (!(containerInfo instanceof FolderInfo)) {
+ Log.e(TAG, String.format(
+ "Item info: %s found with invalid container: %s",
+ info,
+ containerInfo));
+ } else {
+ return (FolderInfo) containerInfo;
+ }
+ }
+ return null;
+ }
+
@Override
public void validateData() {
super.validateData();
@@ -182,9 +261,10 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
super.destroy();
mActive = false;
StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
-
+ if (mIsPrimaryInstance) {
+ mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
+ }
destroyPredictors();
- mIDP.removeOnChangeListener(this);
}
private void destroyPredictors() {
@@ -225,6 +305,7 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
registerWidgetsPredictor(apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("widgets")
+ .setExtras(getBundleForWidgetsOnWorkspace(context, mDataModel))
.setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
.build()));
}
@@ -232,7 +313,7 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
private void registerPredictor(PredictorState state, AppPredictor predictor) {
state.predictor = predictor;
state.predictor.registerPredictionUpdates(
- Executors.MODEL_EXECUTOR, new AppPredictor.Callback() {
+ MODEL_EXECUTOR, new AppPredictor.Callback() {
@Keep
@Override
public void onTargetsAvailable(@NonNull List targets) {
@@ -253,7 +334,7 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
private void registerWidgetsPredictor(AppPredictor predictor) {
mWidgetsRecommendationState.predictor = predictor;
mWidgetsRecommendationState.predictor.registerPredictionUpdates(
- Executors.MODEL_EXECUTOR, new AppPredictor.Callback() {
+ MODEL_EXECUTOR, new AppPredictor.Callback() {
@Keep
@Override
public void onTargetsAvailable(@NonNull List targets) {
@@ -268,19 +349,44 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
mWidgetsRecommendationState.predictor.requestPredictionUpdate();
}
- @Override
- public void onIdpChanged(InvariantDeviceProfile profile) {
- // Reinitialize everything
- Executors.MODEL_EXECUTOR.execute(this::recreatePredictors);
- }
-
private void onAppTargetEvent(AppTargetEvent event, int client) {
- PredictorState state = client == CONTAINER_PREDICTION ? mAllAppsState : mHotseatState;
+ PredictorState state;
+ switch(client) {
+ case CONTAINER_PREDICTION:
+ state = mAllAppsState;
+ break;
+ case CONTAINER_WIDGETS_PREDICTION:
+ state = mWidgetsRecommendationState;
+ break;
+ case CONTAINER_HOTSEAT_PREDICTION:
+ default:
+ state = mHotseatState;
+ break;
+ }
if (state.predictor != null) {
state.predictor.notifyAppTargetEvent(event);
+ Log.d(TAG, "notifyAppTargetEvent action=" + event.getAction()
+ + " launchLocation=" + event.getLaunchLocation());
}
}
+ private Bundle getBundleForWidgetsOnWorkspace(Context context, BgDataModel dataModel) {
+ Bundle bundle = new Bundle();
+ ArrayList widgetEvents =
+ dataModel.getAllWorkspaceItems().stream()
+ .filter(PredictionHelper::isTrackedForWidgetPrediction)
+ .map(item -> {
+ AppTarget target = getAppTargetFromItemInfo(context, item);
+ if (target == null) return null;
+ return wrapAppTargetWithItemLocation(
+ target, AppTargetEvent.ACTION_PIN, item);
+ })
+ .filter(Objects::nonNull)
+ .collect(toCollection(ArrayList::new));
+ bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, widgetEvents);
+ return bundle;
+ }
+
static class PredictorState {
public final FixedContainerItems items;
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 154b78b1b9..e489cb3a71 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -377,7 +377,7 @@ public final class WellbeingModel extends BgObjectWithLooper {
/**
* Shortcut factory for generating wellbeing action
*/
- public static final SystemShortcut.Factory SHORTCUT_FACTORY =
+ public static final SystemShortcut.Factory SHORTCUT_FACTORY =
(activity, info) -> (info.getTargetComponent() == null) ? null : INSTANCE.get(activity)
.getShortcutForApp(
info.getTargetComponent().getPackageName(), info.user.getIdentifier(),
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 3d891e8748..4be83dc0b0 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -83,7 +83,7 @@ public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask {
}
} else {
Map widgetItems =
- allWidgets.values().stream().flatMap(List::stream)
+ allWidgets.values().stream().flatMap(List::stream).distinct()
.collect(Collectors.toMap(widget -> (ComponentKey) widget,
widget -> widget));
for (AppTarget app : mTargets) {
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index eb6788e463..a3ee817bfb 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -26,6 +26,7 @@ import android.animation.ObjectAnimator;
import android.os.IBinder;
import android.os.SystemProperties;
import android.util.FloatProperty;
+import android.view.AttachedSurfaceControl;
import android.view.CrossWindowBlurListeners;
import android.view.SurfaceControl;
import android.view.View;
@@ -45,6 +46,7 @@ import com.android.systemui.shared.system.WallpaperManagerCompat;
import app.lawnchair.preferences.BasePreferenceManager;
import app.lawnchair.preferences.PreferenceManager;
+import java.io.PrintWriter;
import java.util.function.Consumer;
/**
@@ -97,7 +99,11 @@ public class DepthController implements StateHandler,
public void onDraw() {
View view = mLauncher.getDragLayer();
ViewRootImpl viewRootImpl = view.getViewRootImpl();
- setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
+ boolean applied = setSurface(
+ viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
+ if (!applied) {
+ dispatchTransactionSurface(mDepth);
+ }
view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
}
};
@@ -110,6 +116,13 @@ public class DepthController implements StateHandler,
}
};
+ private final Runnable mOpaquenessListener = new Runnable() {
+ @Override
+ public void run() {
+ dispatchTransactionSurface(mDepth);
+ }
+ };
+
private final Launcher mLauncher;
/**
* Blur radius when completely zoomed out, in pixels.
@@ -118,19 +131,36 @@ public class DepthController implements StateHandler,
private boolean mCrossWindowBlursEnabled;
private WallpaperManagerCompat mWallpaperManager;
private SurfaceControl mSurface;
+ /**
+ * How visible the -1 overlay is, from 0 to 1.
+ */
+ private float mOverlayScrollProgress;
/**
* Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
* @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
*/
private float mDepth;
+ /**
+ * Last blur value, in pixels, that was applied.
+ * For debugging purposes.
+ */
+ private int mCurrentBlur;
/**
* If we're launching and app and should not be blurring the screen for performance reasons.
*/
private boolean mBlurDisabledForAppLaunch;
+ /**
+ * If we requested early wake-up offsets to SurfaceFlinger.
+ */
+ private boolean mInEarlyWakeUp;
// Workaround for animating the depth when multiwindow mode changes.
private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false;
+ // Hints that there is potentially content behind Launcher and that we shouldn't optimize by
+ // marking the launcher surface as opaque. Only used in certain Launcher states.
+ private boolean mHasContentBehindLauncher;
+
private View.OnAttachStateChangeListener mOnAttachListener;
private BasePreferenceManager.FloatPref mDrawerOpacity;
@@ -154,10 +184,7 @@ public class DepthController implements StateHandler,
if (windowToken != null) {
mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
}
- if (Utilities.ATLEAST_S) {
- CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
- mCrossWindowBlurListener);
- }
+ onAttached();
}
@Override
@@ -165,12 +192,12 @@ public class DepthController implements StateHandler,
if (Utilities.ATLEAST_S) {
CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener);
}
+ mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener);
}
};
mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
- if (mLauncher.getRootView().isAttachedToWindow() && Utilities.ATLEAST_S) {
- CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
- mCrossWindowBlurListener);
+ if (mLauncher.getRootView().isAttachedToWindow()) {
+ onAttached();
}
}
if (mDrawerOpacity == null) {
@@ -178,6 +205,18 @@ public class DepthController implements StateHandler,
}
}
+ private void onAttached() {
+ if (Utilities.ATLEAST_S) {
+ CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
+ mCrossWindowBlurListener);
+ }
+ mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
+ }
+
+ public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) {
+ mHasContentBehindLauncher = hasContentBehindLauncher;
+ }
+
/**
* Sets if the underlying activity is started or not
*/
@@ -193,20 +232,22 @@ public class DepthController implements StateHandler,
/**
* Sets the specified app target surface to apply the blur to.
+ * @return true when surface was valid and transaction was dispatched.
*/
- public void setSurface(SurfaceControl surface) {
+ public boolean setSurface(SurfaceControl surface) {
// Set launcher as the SurfaceControl when we don't need an external target anymore.
if (surface == null) {
ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
surface = viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null;
}
-
if (mSurface != surface) {
mSurface = surface;
if (surface != null) {
dispatchTransactionSurface(mDepth);
+ return true;
}
}
+ return false;
}
@Override
@@ -220,6 +261,8 @@ public class DepthController implements StateHandler,
setDepth(toDepth);
} else if (toState == LauncherState.OVERVIEW) {
dispatchTransactionSurface(mDepth);
+ } else if (toState == LauncherState.BACKGROUND_APP) {
+ mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
}
}
@@ -271,9 +314,19 @@ public class DepthController implements StateHandler,
if (Float.compare(mDepth, depthF) == 0 && !force) {
return;
}
- if (dispatchTransactionSurface(depthF)) {
- mDepth = depthF;
+ dispatchTransactionSurface(depthF);
+ mDepth = depthF;
+ }
+
+ public void onOverlayScrollChanged(float progress) {
+ // Round out the progress to dedupe frequent, non-perceptable updates
+ int progressI = (int) (progress * 256);
+ float progressF = Utilities.boundToRange(progressI / 256f, 0f, 1f);
+ if (Float.compare(mOverlayScrollProgress, progressF) == 0) {
+ return;
}
+ mOverlayScrollProgress = progressF;
+ dispatchTransactionSurface(mDepth);
}
private boolean dispatchTransactionSurface(float depth) {
@@ -282,23 +335,38 @@ public class DepthController implements StateHandler,
return false;
}
ensureDependencies();
+ depth = Math.max(depth, mOverlayScrollProgress);
IBinder windowToken = mLauncher.getRootView().getWindowToken();
if (windowToken != null) {
mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
}
if (supportsBlur) {
- // We cannot mark the window as opaque in overview because there will be an app window
- // below the launcher layer, and we need to draw it -- without blurs.
- boolean isOverview = mLauncher.isInState(LauncherState.OVERVIEW);
- boolean opaque = mLauncher.getScrimView().isFullyOpaque() && !isOverview;
+ boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
+ boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg;
- int blur = opaque || isOverview || !mCrossWindowBlursEnabled
- || mBlurDisabledForAppLaunch ? 0 : (int) (depth * mMaxBlurRadius);
- new SurfaceControl.Transaction()
- .setBackgroundBlurRadius(mSurface, blur)
- .setOpaque(mSurface, opaque)
- .apply();
+ mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch || hasOpaqueBg
+ ? 0 : (int) (depth * mMaxBlurRadius);
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
+ .setBackgroundBlurRadius(mSurface, mCurrentBlur)
+ .setOpaque(mSurface, isSurfaceOpaque);
+
+ // Set early wake-up flags when we know we're executing an expensive operation, this way
+ // SurfaceFlinger will adjust its internal offsets to avoid jank.
+ boolean wantsEarlyWakeUp = depth > 0 && depth < 1;
+ if (wantsEarlyWakeUp && !mInEarlyWakeUp) {
+ transaction.setEarlyWakeupStart();
+ mInEarlyWakeUp = true;
+ } else if (!wantsEarlyWakeUp && mInEarlyWakeUp) {
+ transaction.setEarlyWakeupEnd();
+ mInEarlyWakeUp = false;
+ }
+
+ AttachedSurfaceControl rootSurfaceControl =
+ mLauncher.getRootView().getRootSurfaceControl();
+ if (rootSurfaceControl != null) {
+ rootSurfaceControl.applyTransactionOnDraw(transaction);
+ }
}
return true;
}
@@ -319,4 +387,18 @@ public class DepthController implements StateHandler,
mwAnimation.setAutoCancel(true);
mwAnimation.start();
}
+
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + this.getClass().getSimpleName());
+ writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
+ writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
+ writer.println(prefix + "\tmSurface=" + mSurface);
+ writer.println(prefix + "\tmOverlayScrollProgress=" + mOverlayScrollProgress);
+ writer.println(prefix + "\tmDepth=" + mDepth);
+ writer.println(prefix + "\tmCurrentBlur=" + mCurrentBlur);
+ writer.println(prefix + "\tmBlurDisabledForAppLaunch=" + mBlurDisabledForAppLaunch);
+ writer.println(prefix + "\tmInEarlyWakeUp=" + mInEarlyWakeUp);
+ writer.println(prefix + "\tmIgnoreStateChangesDuringMultiWindowAnimation="
+ + mIgnoreStateChangesDuringMultiWindowAnimation);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
deleted file mode 100644
index 540f748313..0000000000
--- a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.taskbar;
-
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
-
-import android.annotation.DrawableRes;
-import android.view.View;
-import android.widget.ImageView;
-
-import com.android.launcher3.R;
-import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
-
-/**
- * Creates Buttons for Taskbar for 3 button nav.
- * Can add animations and state management for buttons in this class as things progress.
- */
-public class ButtonProvider {
-
- private final int mMarginLeftRight;
- private final TaskbarActivityContext mContext;
-
- public ButtonProvider(TaskbarActivityContext context) {
- mContext = context;
- mMarginLeftRight = context.getResources()
- .getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
- }
-
- public View getBack() {
- // Back button
- return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
- }
-
- public View getDown() {
- // Ime down button
- return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
- }
-
- public View getHome() {
- // Home button
- return getButtonForDrawable(R.drawable.ic_sysbar_home, BUTTON_HOME);
- }
-
- public View getRecents() {
- // Recents button
- return getButtonForDrawable(R.drawable.ic_sysbar_recent, BUTTON_RECENTS);
- }
-
- public View getImeSwitcher() {
- // IME Switcher Button
- return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH);
- }
-
- private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) {
- ImageView buttonView = new ImageView(mContext);
- buttonView.setImageResource(drawableId);
- buttonView.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
- buttonView.setPadding(mMarginLeftRight, 0, mMarginLeftRight, 0);
- buttonView.setOnClickListener(view -> mContext.onNavigationButtonClick(buttonType));
- return buttonView;
- }
-
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
new file mode 100644
index 0000000000..f1e67479f5
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
+import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
+
+import android.animation.Animator;
+
+import com.android.launcher3.statemanager.StateManager;
+import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * A data source which integrates with the fallback RecentsActivity instance (for 3P launchers).
+ */
+public class FallbackTaskbarUIController extends TaskbarUIController {
+
+ private final RecentsActivity mRecentsActivity;
+
+ private final StateManager.StateListener mStateListener =
+ new StateManager.StateListener() {
+ @Override
+ public void onStateTransitionStart(RecentsState toState) {
+ animateToRecentsState(toState);
+
+ // Handle tapping on live tile.
+ RecentsView recentsView = mRecentsActivity.getOverviewPanel();
+ recentsView.setTaskLaunchListener(toState == RecentsState.DEFAULT
+ ? (() -> animateToRecentsState(RecentsState.BACKGROUND_APP)) : null);
+ }
+ };
+
+ public FallbackTaskbarUIController(RecentsActivity recentsActivity) {
+ mRecentsActivity = recentsActivity;
+ }
+
+ @Override
+ protected void init(TaskbarControllers taskbarControllers) {
+ super.init(taskbarControllers);
+
+ mRecentsActivity.setTaskbarUIController(this);
+ mRecentsActivity.getStateManager().addStateListener(mStateListener);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mRecentsActivity.setTaskbarUIController(null);
+ mRecentsActivity.getStateManager().removeStateListener(mStateListener);
+ }
+
+ /**
+ * Creates an animation to animate the taskbar for the given state (but does not start it).
+ * Currently this animation just force stashes the taskbar in Overview.
+ */
+ public Animator createAnimToRecentsState(RecentsState toState, long duration) {
+ boolean forceStashed = toState.hasOverviewActions();
+ TaskbarStashController controller = mControllers.taskbarStashController;
+ // Set both FLAG_IN_STASHED_LAUNCHER_STATE and FLAG_IN_APP to ensure the state is respected.
+ // For all other states, just use the current stashed-in-app setting (e.g. if long clicked).
+ controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, forceStashed);
+ controller.updateStateForFlag(FLAG_IN_APP, !forceStashed);
+ return controller.applyStateWithoutStart(duration);
+ }
+
+ private void animateToRecentsState(RecentsState toState) {
+ Animator anim = createAnimToRecentsState(toState, TASKBAR_STASH_DURATION);
+ if (anim != null) {
+ anim.start();
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
deleted file mode 100644
index 287caab44b..0000000000
--- a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.taskbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.RelativeLayout;
-
-import com.android.launcher3.views.ActivityContext;
-
-public class ImeBarView extends RelativeLayout {
-
- private ButtonProvider mButtonProvider;
- private View mImeView;
-
- public ImeBarView(Context context) {
- this(context, null);
- }
-
- public ImeBarView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ImeBarView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public void init(ButtonProvider buttonProvider) {
- mButtonProvider = buttonProvider;
-
- ActivityContext context = getActivityContext();
- RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams(
- context.getDeviceProfile().iconSizePx,
- context.getDeviceProfile().iconSizePx
- );
- RelativeLayout.LayoutParams downParams = new RelativeLayout.LayoutParams(imeParams);
-
- imeParams.addRule(ALIGN_PARENT_END);
- imeParams.setMarginEnd(context.getDeviceProfile().iconSizePx);
- downParams.setMarginStart(context.getDeviceProfile().iconSizePx);
- downParams.addRule(ALIGN_PARENT_START);
-
- // Down Arrow
- View downView = mButtonProvider.getDown();
- downView.setLayoutParams(downParams);
- downView.setRotation(-90);
- addView(downView);
-
- // IME switcher button
- mImeView = mButtonProvider.getImeSwitcher();
- mImeView.setLayoutParams(imeParams);
- addView(mImeView);
- }
-
- public void setImeSwitcherVisibility(boolean show) {
- mImeView.setVisibility(show ? VISIBLE : GONE);
- }
-
- private T getActivityContext() {
- return ActivityContext.lookupContext(getContext());
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index c2d107c22d..5e8db69f2b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -15,13 +15,18 @@
*/
package com.android.launcher3.taskbar;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.view.MotionEvent;
+import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
+import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
-import androidx.annotation.Nullable;
+import android.animation.Animator;
+import android.annotation.ColorInt;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.TaskTransitionSpec;
+import android.view.WindowManagerGlobal;
+
+import androidx.annotation.NonNull;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
@@ -29,188 +34,116 @@ import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.OnboardingPrefs;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.RecentsAnimationCallbacks;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Stream;
/**
* A data source which integrates with a Launcher instance
- * TODO: Rename to have Launcher prefix
*/
-
public class LauncherTaskbarUIController extends TaskbarUIController {
+ private static final String TAG = "TaskbarUIController";
+
private final BaseQuickstepLauncher mLauncher;
- private final TaskbarStateHandler mTaskbarStateHandler;
- private final TaskbarAnimationController mTaskbarAnimationController;
- private final TaskbarHotseatController mHotseatController;
- private final TaskbarActivityContext mContext;
- final TaskbarDragLayer mTaskbarDragLayer;
- final TaskbarView mTaskbarView;
+ private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
+ this::onStashedInAppChanged;
- private @Nullable Animator mAnimator;
- private boolean mIsAnimatingToLauncher;
+ // Initialized in init.
+ private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
+ private TaskbarKeyguardController mKeyguardController;
+ private final TaskbarLauncherStateController
+ mTaskbarLauncherStateController = new TaskbarLauncherStateController();
- public LauncherTaskbarUIController(
- BaseQuickstepLauncher launcher, TaskbarActivityContext context) {
- mContext = context;
- mTaskbarDragLayer = context.getDragLayer();
- mTaskbarView = mTaskbarDragLayer.findViewById(R.id.taskbar_view);
+ private final DeviceProfile.OnDeviceProfileChangeListener mProfileChangeListener =
+ new DeviceProfile.OnDeviceProfileChangeListener() {
+ @Override
+ public void onDeviceProfileChanged(DeviceProfile dp) {
+ mControllers.taskbarViewController.onRotationChanged(
+ mLauncher.getDeviceProfile());
+ }
+ };
+ public LauncherTaskbarUIController(BaseQuickstepLauncher launcher) {
mLauncher = launcher;
- mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
- mTaskbarAnimationController = new TaskbarAnimationController(mLauncher,
- createTaskbarAnimationControllerCallbacks());
- mHotseatController = new TaskbarHotseatController(
- mLauncher, mTaskbarView::updateHotseatItems);
}
@Override
- protected void onCreate() {
- mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController);
- mTaskbarAnimationController.init();
- mHotseatController.init();
- setTaskbarViewVisible(!mLauncher.hasBeenResumed());
- alignRealHotseatWithTaskbar();
+ protected void init(TaskbarControllers taskbarControllers) {
+ super.init(taskbarControllers);
+
+ mTaskbarLauncherStateController.init(mControllers, mLauncher);
+ mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
+ .getOverrideBackgroundAlpha();
+
mLauncher.setTaskbarUIController(this);
+ mKeyguardController = taskbarControllers.taskbarKeyguardController;
+
+ onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */);
+
+ onStashedInAppChanged(mLauncher.getDeviceProfile());
+ mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
+ mLauncher.addOnDeviceProfileChangeListener(mProfileChangeListener);
}
@Override
protected void onDestroy() {
- if (mAnimator != null) {
- // End this first, in case it relies on properties that are about to be cleaned up.
- mAnimator.end();
- }
- mTaskbarStateHandler.setAnimationController(null);
- mTaskbarAnimationController.cleanup();
- mHotseatController.cleanup();
- setTaskbarViewVisible(true);
- mLauncher.getHotseat().setIconsAlpha(1f);
+ super.onDestroy();
+ onLauncherResumedOrPaused(false);
+ mTaskbarLauncherStateController.onDestroy();
+
+ mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
mLauncher.setTaskbarUIController(null);
+ mLauncher.removeOnDeviceProfileChangeListener(mProfileChangeListener);
+ updateTaskTransitionSpec(true);
}
@Override
protected boolean isTaskbarTouchable() {
- return !mIsAnimatingToLauncher;
- }
-
- private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
- return new TaskbarAnimationControllerCallbacks() {
- @Override
- public void updateTaskbarBackgroundAlpha(float alpha) {
- mTaskbarDragLayer.setTaskbarBackgroundAlpha(alpha);
- }
-
- @Override
- public void updateTaskbarVisibilityAlpha(float alpha) {
- mTaskbarView.setAlpha(alpha);
- }
-
- @Override
- public void updateImeBarVisibilityAlpha(float alpha) {
- mTaskbarDragLayer.updateImeBarVisibilityAlpha(alpha);
- }
-
- @Override
- public void updateTaskbarScale(float scale) {
- mTaskbarView.setScaleX(scale);
- mTaskbarView.setScaleY(scale);
- }
-
- @Override
- public void updateTaskbarTranslationY(float translationY) {
- if (translationY < 0) {
- // Resize to accommodate the max translation we'll reach.
- mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize
- + mLauncher.getHotseat().getTaskbarOffsetY());
- } else {
- mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize);
- }
- mTaskbarView.setTranslationY(translationY);
- }
- };
+ return !mTaskbarLauncherStateController.isAnimatingToLauncher();
}
/**
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
*/
public void onLauncherResumedOrPaused(boolean isResumed) {
- long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
- if (mAnimator != null) {
- mAnimator.cancel();
- }
- if (isResumed) {
- mAnimator = createAnimToLauncher(null, duration);
- } else {
- mAnimator = createAnimToApp(duration);
- }
- mAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimator = null;
+ onLauncherResumedOrPaused(isResumed, false /* fromInit */);
+ }
+
+ private void onLauncherResumedOrPaused(boolean isResumed, boolean fromInit) {
+ if (mKeyguardController.isScreenOff()) {
+ if (!isResumed) {
+ return;
+ } else {
+ // Resuming implicitly means device unlocked
+ mKeyguardController.setScreenOn();
}
- });
- mAnimator.start();
+ }
+
+ mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed);
+ mTaskbarLauncherStateController.applyState(
+ fromInit ? 0 : QuickstepTransitionManager.CONTENT_ALPHA_DURATION);
}
/**
- * Create Taskbar animation when going from an app to Launcher.
+ * Create Taskbar animation when going from an app to Launcher as part of recents transition.
* @param toState If known, the state we will end up in when reaching Launcher.
+ * @param callbacks callbacks to track the recents animation lifecycle. The state change is
+ * automatically reset once the recents animation finishes
*/
- public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) {
- PendingAnimation anim = new PendingAnimation(duration);
- anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration));
- if (toState != null) {
- mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
- }
-
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mIsAnimatingToLauncher = true;
- mTaskbarView.setHolesAllowedInLayout(true);
- mTaskbarView.updateHotseatItemsVisibility();
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mIsAnimatingToLauncher = false;
- setTaskbarViewVisible(false);
- }
- });
-
- return anim.buildAnim();
- }
-
- private Animator createAnimToApp(long duration) {
- PendingAnimation anim = new PendingAnimation(duration);
- anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration));
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mTaskbarView.updateHotseatItemsVisibility();
- setTaskbarViewVisible(true);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mTaskbarView.setHolesAllowedInLayout(false);
- }
- });
- return anim.buildAnim();
- }
-
- @Override
- protected void onImeVisible(TaskbarDragLayer containerView, boolean isVisible) {
- mTaskbarAnimationController.animateToVisibilityForIme(isVisible ? 0 : 1);
- }
-
- /**
- * Should be called when one or more items in the Hotseat have changed.
- */
- public void onHotseatUpdated() {
- mHotseatController.onHotseatUpdated();
+ public Animator createAnimToLauncher(@NonNull LauncherState toState,
+ @NonNull RecentsAnimationCallbacks callbacks, long duration) {
+ return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
}
/**
@@ -218,58 +151,99 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
* @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
*/
public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
- return mTaskbarView.isEventOverAnyItem(ev);
+ return mControllers.taskbarViewController.isEventOverAnyItem(ev)
+ || mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
}
public boolean isDraggingItem() {
- return mTaskbarView.isDraggingItem();
+ return mControllers.taskbarDragController.isDragging();
}
- /**
- * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
- */
@Override
- public void alignRealHotseatWithTaskbar() {
- Rect hotseatBounds = new Rect();
- DeviceProfile grid = mLauncher.getDeviceProfile();
- int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
- int taskbarOffset = mLauncher.getHotseat().getTaskbarOffsetY();
- int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset;
- int hotseatBottomDiff = taskbarOffset;
+ protected void onStashedInAppChanged() {
+ onStashedInAppChanged(mLauncher.getDeviceProfile());
+ }
- RectF hotseatBoundsF = mTaskbarView.getHotseatBounds();
- Utilities.scaleRectFAboutPivot(hotseatBoundsF, getTaskbarScaleOnHome(),
- mTaskbarView.getPivotX(), mTaskbarView.getPivotY());
- hotseatBoundsF.round(hotseatBounds);
- mLauncher.getHotseat().setPadding(hotseatBounds.left,
- hotseatBounds.top + hotseatTopDiff,
- mTaskbarView.getWidth() - hotseatBounds.right,
- mTaskbarView.getHeight() - hotseatBounds.bottom + hotseatBottomDiff);
+ private void onStashedInAppChanged(DeviceProfile deviceProfile) {
+ boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp();
+ deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps;
+ updateTaskTransitionSpec(taskbarStashedInApps);
+ }
+
+ private void updateTaskTransitionSpec(boolean taskbarIsHidden) {
+ try {
+ if (taskbarIsHidden) {
+ // Clear custom task transition settings when the taskbar is stashed
+ WindowManagerGlobal.getWindowManagerService().clearTaskTransitionSpec();
+ } else {
+ // Adjust task transition spec to account for taskbar being visible
+ @ColorInt int taskAnimationBackgroundColor =
+ mLauncher.getColor(R.color.taskbar_background);
+
+ TaskTransitionSpec customTaskAnimationSpec = new TaskTransitionSpec(
+ taskAnimationBackgroundColor,
+ Set.of(ITYPE_EXTRA_NAVIGATION_BAR)
+ );
+ WindowManagerGlobal.getWindowManagerService()
+ .setTaskTransitionSpec(customTaskAnimationSpec);
+ }
+ } catch (RemoteException e) {
+ // This shouldn't happen but if it does task animations won't look good until the
+ // taskbar stashing state is changed.
+ Log.e(TAG, "Failed to update task transition spec to account for new taskbar state",
+ e);
+ }
}
/**
- * Returns the ratio of the taskbar icon size on home vs in an app.
+ * Sets whether the background behind the taskbar/nav bar should be hidden.
*/
- public float getTaskbarScaleOnHome() {
- DeviceProfile inAppDp = mContext.getDeviceProfile();
- DeviceProfile onHomeDp = mLauncher.getDeviceProfile();
- return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
+ public void forceHideBackground(boolean forceHide) {
+ mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
}
- void setTaskbarViewVisible(boolean isVisible) {
- mTaskbarView.setIconsVisibility(isVisible);
- mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
+ @Override
+ public Stream getAppIconsForEdu() {
+ return Arrays.stream(mLauncher.getAppsView().getAppsStore().getApps());
}
/**
- * Contains methods that TaskbarAnimationController can call to interface with
- * TaskbarController.
+ * Starts the taskbar education flow, if the user hasn't seen it yet.
*/
- protected interface TaskbarAnimationControllerCallbacks {
- void updateTaskbarBackgroundAlpha(float alpha);
- void updateTaskbarVisibilityAlpha(float alpha);
- void updateImeBarVisibilityAlpha(float alpha);
- void updateTaskbarScale(float scale);
- void updateTaskbarTranslationY(float translationY);
+ public void showEdu() {
+ if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()
+ || Utilities.IS_RUNNING_IN_TEST_HARNESS
+ || mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN)) {
+ return;
+ }
+ mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
+
+ mControllers.taskbarEduController.showEdu();
+ }
+
+ /**
+ * Manually ends the taskbar education flow.
+ */
+ public void hideEdu() {
+ if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()) {
+ return;
+ }
+
+ mControllers.taskbarEduController.hideEdu();
+ }
+
+ @Override
+ public void onTaskbarIconLaunched(WorkspaceItemInfo item) {
+ InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+ mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
+ instanceId);
+ }
+
+ @Override
+ public void setSystemGestureInProgress(boolean inProgress) {
+ super.setSystemGestureInProgress(inProgress);
+ // Launcher's ScrimView will draw the background throughout the gesture. But once the
+ // gesture ends, start drawing taskbar's background again since launcher might stop drawing.
+ forceHideBackground(inProgress);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
new file mode 100644
index 0000000000..50637a12ce
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
+import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Region.Op;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.util.Property;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnHoverListener;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.AnimatedFloat;
+import com.android.systemui.shared.rotation.FloatingRotationButton;
+import com.android.systemui.shared.rotation.RotationButton;
+import com.android.systemui.shared.rotation.RotationButtonController;
+
+import java.util.ArrayList;
+import java.util.function.IntPredicate;
+
+/**
+ * Controller for managing nav bar buttons in taskbar
+ */
+public class NavbarButtonsViewController {
+
+ private final Rect mTempRect = new Rect();
+
+ private static final int FLAG_SWITCHER_SUPPORTED = 1 << 0;
+ private static final int FLAG_IME_VISIBLE = 1 << 1;
+ private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
+ private static final int FLAG_A11Y_VISIBLE = 1 << 3;
+ private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
+ private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
+ private static final int FLAG_KEYGUARD_OCCLUDED = 1 << 6;
+ private static final int FLAG_DISABLE_HOME = 1 << 7;
+ private static final int FLAG_DISABLE_RECENTS = 1 << 8;
+ private static final int FLAG_DISABLE_BACK = 1 << 9;
+ private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10;
+ private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11;
+
+ private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
+
+ private final ArrayList mPropertyHolders = new ArrayList<>();
+ private final ArrayList mAllButtons = new ArrayList<>();
+ private int mState;
+
+ private final TaskbarActivityContext mContext;
+ private final FrameLayout mNavButtonsView;
+ private final ViewGroup mNavButtonContainer;
+ // Used for IME+A11Y buttons
+ private final ViewGroup mEndContextualContainer;
+ private final ViewGroup mStartContextualContainer;
+ private final int mLightIconColor;
+ private final int mDarkIconColor;
+
+ private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
+ this::updateNavButtonTranslationY);
+ private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
+ this::updateNavButtonTranslationY);
+ // Only applies to mTaskbarNavButtonTranslationY
+ private final AnimatedFloat mNavButtonTranslationYMultiplier = new AnimatedFloat(
+ this::updateNavButtonTranslationY);
+ private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
+ this::updateNavButtonDarkIntensity);
+ private final AnimatedFloat mNavButtonDarkIntensityMultiplier = new AnimatedFloat(
+ this::updateNavButtonDarkIntensity);
+ private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
+
+ private final Rect mFloatingRotationButtonBounds = new Rect();
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+ private View mA11yButton;
+ private int mSysuiStateFlags;
+ private View mBackButton;
+ private FloatingRotationButton mFloatingRotationButton;
+
+ public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
+ mContext = context;
+ mNavButtonsView = navButtonsView;
+ mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
+ mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
+ mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
+
+ mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color);
+ mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color);
+ }
+
+ /**
+ * Initializes the controller
+ */
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
+ mNavButtonTranslationYMultiplier.value = 1;
+
+ boolean isThreeButtonNav = mContext.isThreeButtonNav();
+ // IME switcher
+ View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
+ isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
+ mControllers.navButtonController, R.id.ime_switcher);
+ mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
+ flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE)
+ && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
+
+ mPropertyHolders.add(new StatePropertyHolder(
+ mControllers.taskbarViewController.getTaskbarIconAlpha()
+ .getProperty(ALPHA_INDEX_KEYGUARD),
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
+ && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0,
+ MultiValueAlpha.VALUE, 1, 0));
+
+ mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
+ .getKeyguardBgTaskbar(),
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, AnimatedFloat.VALUE, 1, 0));
+
+ // Force nav buttons (specifically back button) to be visible during setup wizard.
+ boolean isInSetup = !mContext.isUserSetupComplete();
+ boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
+
+ // Make sure to remove nav bar buttons translation when notification shade is expanded or
+ // IME is showing (add separate translation for IME).
+ int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE;
+ mPropertyHolders.add(new StatePropertyHolder(mNavButtonTranslationYMultiplier,
+ flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
+ 0, 1));
+ // Center nav buttons in new height for IME.
+ float transForIme = (mContext.getDeviceProfile().taskbarSize
+ - mContext.getTaskbarHeightForIme()) / 2f;
+ // For gesture nav, nav buttons only show for IME anyway so keep them translated down.
+ float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
+ mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
+ flags -> (flags & FLAG_IME_VISIBLE) != 0, AnimatedFloat.VALUE, transForIme,
+ defaultButtonTransY));
+
+ if (alwaysShowButtons) {
+ initButtons(mNavButtonContainer, mEndContextualContainer,
+ mControllers.navButtonController);
+
+ if (isInSetup) {
+ // Since setup wizard only has back button enabled, it looks strange to be
+ // end-aligned, so start-align instead.
+ FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
+ mNavButtonContainer.getLayoutParams();
+ navButtonsLayoutParams.setMarginStart(navButtonsLayoutParams.getMarginEnd());
+ navButtonsLayoutParams.setMarginEnd(0);
+ navButtonsLayoutParams.gravity = Gravity.START;
+ mNavButtonContainer.requestLayout();
+
+ // TODO(b/210906568) Dark intensity is currently not propagated during setup, so set
+ // it based on dark theme for now.
+ int mode = mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
+ boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
+ mTaskbarNavButtonDarkIntensity.updateValue(isDarkTheme ? 0 : 1);
+ }
+
+ // Animate taskbar background when any of these flags are enabled
+ int flagsToShowBg = FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
+ | FLAG_NOTIFICATION_SHADE_EXPANDED;
+ mPropertyHolders.add(new StatePropertyHolder(
+ mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
+ flags -> (flags & flagsToShowBg) != 0, AnimatedFloat.VALUE, 1, 0));
+
+ // Rotation button
+ RotationButton rotationButton = new RotationButtonImpl(
+ addButton(mEndContextualContainer, R.id.rotate_suggestion,
+ R.layout.taskbar_contextual_button));
+ rotationButton.hide();
+ mControllers.rotationButtonController.setRotationButton(rotationButton, null);
+ } else {
+ mFloatingRotationButton = new FloatingRotationButton(mContext,
+ R.string.accessibility_rotate_button,
+ R.layout.rotate_suggestion,
+ R.id.rotate_suggestion,
+ R.dimen.floating_rotation_button_min_margin,
+ R.dimen.rounded_corner_content_padding,
+ R.dimen.floating_rotation_button_taskbar_left_margin,
+ R.dimen.floating_rotation_button_taskbar_bottom_margin,
+ R.dimen.floating_rotation_button_diameter,
+ R.dimen.key_button_ripple_max_width);
+ mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
+ mRotationButtonListener);
+
+ View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
+ mStartContextualContainer, mControllers.navButtonController, R.id.back);
+ imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90);
+ // Rotate when Ime visible
+ mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
+ flags -> (flags & FLAG_IME_VISIBLE) != 0));
+ }
+
+ applyState();
+ mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
+ }
+
+ private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
+ TaskbarNavButtonController navButtonController) {
+
+ mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
+ mNavButtonContainer, mControllers.navButtonController, R.id.back);
+ mPropertyHolders.add(new StatePropertyHolder(mBackButton,
+ flags -> {
+ // Show only if not disabled, and if not on the keyguard or otherwise only when
+ // the bouncer or a lockscreen app is showing above the keyguard
+ boolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
+ (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
+ (flags & FLAG_KEYGUARD_OCCLUDED) != 0;
+ return (flags & FLAG_DISABLE_BACK) == 0
+ && ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
+ }));
+ boolean isRtl = Utilities.isRtl(mContext.getResources());
+ mPropertyHolders.add(new StatePropertyHolder(
+ mBackButton, flags -> (flags & FLAG_IME_VISIBLE) != 0, View.ROTATION,
+ isRtl ? 90 : -90, 0));
+ // Translate back button to be at end/start of other buttons for keyguard
+ int navButtonSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.taskbar_nav_buttons_size);
+ mPropertyHolders.add(new StatePropertyHolder(
+ mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
+ || (flags & FLAG_KEYGUARD_VISIBLE) != 0,
+ VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
+
+
+ // home and recents buttons
+ View homeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
+ navButtonController, R.id.home);
+ mPropertyHolders.add(new StatePropertyHolder(homeButton,
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
+ (flags & FLAG_DISABLE_HOME) == 0));
+ View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
+ navContainer, navButtonController, R.id.recent_apps);
+ mPropertyHolders.add(new StatePropertyHolder(recentsButton,
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
+ (flags & FLAG_DISABLE_RECENTS) == 0));
+
+ // A11y button
+ mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
+ endContainer, navButtonController, R.id.accessibility_button,
+ R.layout.taskbar_contextual_button);
+ mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
+ flags -> (flags & FLAG_A11Y_VISIBLE) != 0
+ && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
+ }
+
+ private void parseSystemUiFlags(int sysUiStateFlags) {
+ mSysuiStateFlags = sysUiStateFlags;
+ boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
+ boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
+ boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+ boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
+ boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
+ boolean isBackDisabled = (sysUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
+ int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+ | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+ boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
+ boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
+
+ // TODO(b/202218289) we're getting IME as not visible on lockscreen from system
+ updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
+ updateStateForFlag(FLAG_SWITCHER_SUPPORTED, isImeSwitcherShowing);
+ updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
+ updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
+ updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
+ updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
+ updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
+ updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
+
+ if (mA11yButton != null) {
+ // Only used in 3 button
+ boolean a11yLongClickable =
+ (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+ mA11yButton.setLongClickable(a11yLongClickable);
+ }
+ }
+
+ public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
+ if (systemUiStateFlags == mSysuiStateFlags) {
+ return;
+ }
+ parseSystemUiFlags(systemUiStateFlags);
+ applyState();
+ if (skipAnim) {
+ mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
+ }
+ }
+
+ /**
+ * Should be called when we need to show back button for bouncer
+ */
+ public void setBackForBouncer(boolean isBouncerVisible) {
+ updateStateForFlag(FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE, isBouncerVisible);
+ applyState();
+ }
+
+ /**
+ * Slightly misnamed, but should be called when keyguard OR AOD is showing.
+ * We consider keyguardVisible when it's showing bouncer OR is occlucded by another app
+ */
+ public void setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded) {
+ updateStateForFlag(FLAG_KEYGUARD_VISIBLE, isKeyguardVisible || isKeyguardOccluded);
+ updateStateForFlag(FLAG_KEYGUARD_OCCLUDED, isKeyguardOccluded);
+ applyState();
+ }
+
+ /**
+ * Returns true if IME bar is visible
+ */
+ public boolean isImeVisible() {
+ return (mState & FLAG_IME_VISIBLE) != 0;
+ }
+
+ /**
+ * Returns true if the home button is disabled
+ */
+ public boolean isHomeDisabled() {
+ return (mState & FLAG_DISABLE_HOME) != 0;
+ }
+
+ /**
+ * Returns true if the recents (overview) button is disabled
+ */
+ public boolean isRecentsDisabled() {
+ return (mState & FLAG_DISABLE_RECENTS) != 0;
+ }
+
+ /**
+ * Adds the bounds corresponding to all visible buttons to provided region
+ */
+ public void addVisibleButtonsRegion(TaskbarDragLayer parent, Region outRegion) {
+ int count = mAllButtons.size();
+ for (int i = 0; i < count; i++) {
+ View button = mAllButtons.get(i);
+ if (button.getVisibility() == View.VISIBLE) {
+ parent.getDescendantRectRelativeToSelf(button, mTempRect);
+ outRegion.op(mTempRect, Op.UNION);
+ }
+ }
+ }
+
+ /** Use to set the translationY for the all nav+contextual buttons */
+ public AnimatedFloat getTaskbarNavButtonTranslationY() {
+ return mTaskbarNavButtonTranslationY;
+ }
+
+ /** Use to set the dark intensity for the all nav+contextual buttons */
+ public AnimatedFloat getTaskbarNavButtonDarkIntensity() {
+ return mTaskbarNavButtonDarkIntensity;
+ }
+
+ /** Use to determine whether to use the dark intensity requested by the underlying app */
+ public AnimatedFloat getNavButtonDarkIntensityMultiplier() {
+ return mNavButtonDarkIntensityMultiplier;
+ }
+
+ /**
+ * Does not call {@link #applyState()}. Don't forget to!
+ */
+ private void updateStateForFlag(int flag, boolean enabled) {
+ if (enabled) {
+ mState |= flag;
+ } else {
+ mState &= ~flag;
+ }
+ }
+
+ private void applyState() {
+ int count = mPropertyHolders.size();
+ for (int i = 0; i < count; i++) {
+ mPropertyHolders.get(i).setState(mState);
+ }
+ }
+
+ private void updateNavButtonTranslationY() {
+ float normalTranslationY = mTaskbarNavButtonTranslationY.value
+ * mNavButtonTranslationYMultiplier.value;
+ float otherTranslationY = mTaskbarNavButtonTranslationYForIme.value;
+ mNavButtonsView.setTranslationY(normalTranslationY + otherTranslationY);
+ }
+
+ private void updateNavButtonDarkIntensity() {
+ float darkIntensity = mTaskbarNavButtonDarkIntensity.value
+ * mNavButtonDarkIntensityMultiplier.value;
+ int iconColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightIconColor,
+ mDarkIconColor);
+ for (ImageView button : mAllButtons) {
+ button.setImageTintList(ColorStateList.valueOf(iconColor));
+ }
+ }
+
+ private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
+ ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
+ return addButton(drawableId, buttonType, parent, navButtonController, id,
+ R.layout.taskbar_nav_button);
+ }
+
+ private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
+ ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id,
+ @LayoutRes int layoutId) {
+ ImageView buttonView = addButton(parent, id, layoutId);
+ buttonView.setImageResource(drawableId);
+ buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
+ buttonView.setOnLongClickListener(view ->
+ navButtonController.onButtonLongClick(buttonType));
+ return buttonView;
+ }
+
+ private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId) {
+ ImageView buttonView = (ImageView) mContext.getLayoutInflater()
+ .inflate(layoutId, parent, false);
+ buttonView.setId(id);
+ parent.addView(buttonView);
+ mAllButtons.add(buttonView);
+ return buttonView;
+ }
+
+ public boolean isEventOverAnyItem(MotionEvent ev) {
+ return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
+ }
+
+ public void onConfigurationChanged(@Config int configChanges) {
+ if (mFloatingRotationButton != null) {
+ mFloatingRotationButton.onConfigurationChanged(configChanges);
+ }
+ }
+
+ public void onDestroy() {
+ mPropertyHolders.clear();
+ mControllers.rotationButtonController.unregisterListeners();
+ if (mFloatingRotationButton != null) {
+ mFloatingRotationButton.hide();
+ }
+ }
+
+ private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
+ @Override
+ public void onVisibilityChanged(boolean isVisible) {
+ if (isVisible) {
+ mFloatingRotationButton.getCurrentView()
+ .getBoundsOnScreen(mFloatingRotationButtonBounds);
+ } else {
+ mFloatingRotationButtonBounds.setEmpty();
+ }
+ }
+ }
+
+ private class RotationButtonImpl implements RotationButton {
+
+ private final ImageView mButton;
+ private AnimatedVectorDrawable mImageDrawable;
+
+ RotationButtonImpl(ImageView button) {
+ mButton = button;
+ }
+
+ @Override
+ public void setRotationButtonController(RotationButtonController rotationButtonController) {
+ // TODO(b/187754252) UI polish, different icons based on light/dark context, etc
+ mImageDrawable = (AnimatedVectorDrawable) mButton.getContext()
+ .getDrawable(rotationButtonController.getIconResId());
+ mButton.setImageDrawable(mImageDrawable);
+ mButton.setContentDescription(mButton.getResources()
+ .getString(R.string.accessibility_rotate_button));
+ mImageDrawable.setCallback(mButton);
+ }
+
+ @Override
+ public View getCurrentView() {
+ return mButton;
+ }
+
+ @Override
+ public boolean show() {
+ mButton.setVisibility(View.VISIBLE);
+ mState |= FLAG_ROTATION_BUTTON_VISIBLE;
+ applyState();
+ return true;
+ }
+
+ @Override
+ public boolean hide() {
+ mButton.setVisibility(View.GONE);
+ mState &= ~FLAG_ROTATION_BUTTON_VISIBLE;
+ applyState();
+ return true;
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mButton.getVisibility() == View.VISIBLE;
+ }
+
+ @Override
+ public void updateIcon(int lightIconColor, int darkIconColor) {
+ // TODO(b/187754252): UI Polish
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener onClickListener) {
+ mButton.setOnClickListener(onClickListener);
+ }
+
+ @Override
+ public void setOnHoverListener(OnHoverListener onHoverListener) {
+ mButton.setOnHoverListener(onHoverListener);
+ }
+
+ @Override
+ public AnimatedVectorDrawable getImageDrawable() {
+ return mImageDrawable;
+ }
+
+ @Override
+ public void setDarkIntensity(float darkIntensity) {
+ // TODO(b/187754252) UI polish
+ }
+
+ @Override
+ public boolean acceptRotationProposal() {
+ return mButton.isAttachedToWindow();
+ }
+ }
+
+ private static class StatePropertyHolder {
+
+ private final float mEnabledValue, mDisabledValue;
+ private final ObjectAnimator mAnimator;
+ private final IntPredicate mEnableCondition;
+
+ private boolean mIsEnabled = true;
+
+ StatePropertyHolder(View view, IntPredicate enableCondition) {
+ this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0);
+ mAnimator.addListener(new AlphaUpdateListener(view));
+ }
+
+ StatePropertyHolder(T target, IntPredicate enabledCondition,
+ Property property, float enabledValue, float disabledValue) {
+ mEnableCondition = enabledCondition;
+ mEnabledValue = enabledValue;
+ mDisabledValue = disabledValue;
+ mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue);
+ }
+
+ public void setState(int flags) {
+ boolean isEnabled = mEnableCondition.test(flags);
+ if (mIsEnabled != isEnabled) {
+ mIsEnabled = isEnabled;
+ mAnimator.cancel();
+ mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue);
+ mAnimator.start();
+ }
+ }
+
+ public void endAnimation() {
+ if (mAnimator.isRunning()) {
+ mAnimator.end();
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
new file mode 100644
index 0000000000..6db58397c3
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.ColorInt;
+import androidx.core.content.ContextCompat;
+
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+
+public class StashedHandleView extends View {
+
+ private static final long COLOR_CHANGE_DURATION = 120;
+
+ private final @ColorInt int mStashedHandleLightColor;
+ private final @ColorInt int mStashedHandleDarkColor;
+ private final Rect mSampledRegion = new Rect();
+ private final int[] mTmpArr = new int[2];
+
+ private @Nullable ObjectAnimator mColorChangeAnim;
+
+ public StashedHandleView(Context context) {
+ this(context, null);
+ }
+
+ public StashedHandleView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mStashedHandleLightColor = ContextCompat.getColor(context,
+ R.color.taskbar_stashed_handle_light_color);
+ mStashedHandleDarkColor = ContextCompat.getColor(context,
+ R.color.taskbar_stashed_handle_dark_color);
+ }
+
+ /**
+ * Updates mSampledRegion to be the location of the stashedHandleBounds relative to the screen.
+ * @see #getSampledRegion()
+ */
+ public void updateSampledRegion(Rect stashedHandleBounds) {
+ getLocationOnScreen(mTmpArr);
+ mSampledRegion.set(stashedHandleBounds);
+ mSampledRegion.offset(mTmpArr[0], mTmpArr[1]);
+ }
+
+ public Rect getSampledRegion() {
+ return mSampledRegion;
+ }
+
+ /**
+ * Updates the handle color.
+ * @param isRegionDark Whether the background behind the handle is dark, and thus the handle
+ * should be light (and vice versa).
+ * @param animate Whether to animate the change, or apply it immediately.
+ */
+ public void updateHandleColor(boolean isRegionDark, boolean animate) {
+ int newColor = isRegionDark ? mStashedHandleLightColor : mStashedHandleDarkColor;
+ if (mColorChangeAnim != null) {
+ mColorChangeAnim.cancel();
+ }
+ if (animate) {
+ mColorChangeAnim = ObjectAnimator.ofArgb(this,
+ LauncherAnimUtils.VIEW_BACKGROUND_COLOR, newColor);
+ mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mColorChangeAnim = null;
+ }
+ });
+ mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
+ mColorChangeAnim.start();
+ } else {
+ setBackgroundColor(newColor);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
new file mode 100644
index 0000000000..22ca63f963
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.RevealOutlineAnimation;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.AnimatedFloat;
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
+
+/**
+ * Handles properties/data collection, then passes the results to our stashed handle View to render.
+ */
+public class StashedHandleViewController {
+
+ public static final int ALPHA_INDEX_STASHED = 0;
+ public static final int ALPHA_INDEX_HOME_DISABLED = 1;
+ private static final int NUM_ALPHA_CHANNELS = 2;
+
+ /**
+ * The SharedPreferences key for whether the stashed handle region is dark.
+ */
+ private static final String SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY =
+ "stashed_handle_region_is_dark";
+
+ private final TaskbarActivityContext mActivity;
+ private final SharedPreferences mPrefs;
+ private final StashedHandleView mStashedHandleView;
+ private final int mStashedHandleWidth;
+ private final int mStashedHandleHeight;
+ private final RegionSamplingHelper mRegionSamplingHelper;
+ private final MultiValueAlpha mTaskbarStashedHandleAlpha;
+ private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat(
+ this::updateStashedHandleHintScale);
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+
+ // The bounds we want to clip to in the settled state when showing the stashed handle.
+ private final Rect mStashedHandleBounds = new Rect();
+ private float mStashedHandleRadius;
+
+ // When the reveal animation is cancelled, we can assume it's about to create a new animation,
+ // which should start off at the same point the cancelled one left off.
+ private float mStartProgressForNextRevealAnim;
+ private boolean mWasLastRevealAnimReversed;
+
+ public StashedHandleViewController(TaskbarActivityContext activity,
+ StashedHandleView stashedHandleView) {
+ mActivity = activity;
+ mPrefs = Utilities.getPrefs(mActivity);
+ mStashedHandleView = stashedHandleView;
+ mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, NUM_ALPHA_CHANNELS);
+ mTaskbarStashedHandleAlpha.setUpdateVisibility(true);
+ mStashedHandleView.updateHandleColor(
+ mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false),
+ false /* animate */);
+ final Resources resources = mActivity.getResources();
+ mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
+ mStashedHandleHeight = resources.getDimensionPixelSize(
+ R.dimen.taskbar_stashed_handle_height);
+ mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
+ new RegionSamplingHelper.SamplingCallback() {
+ @Override
+ public void onRegionDarknessChanged(boolean isRegionDark) {
+ mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */);
+ mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY,
+ isRegionDark).apply();
+ }
+
+ @Override
+ public Rect getSampledRegion(View sampledView) {
+ return mStashedHandleView.getSampledRegion();
+ }
+ }, Executors.UI_HELPER_EXECUTOR);
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+
+ mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_STASHED).setValue(0);
+ mTaskbarStashedHandleHintScale.updateValue(1f);
+
+ final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
+ mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ final int stashedCenterX = view.getWidth() / 2;
+ final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
+ mStashedHandleBounds.set(
+ stashedCenterX - mStashedHandleWidth / 2,
+ stashedCenterY - mStashedHandleHeight / 2,
+ stashedCenterX + mStashedHandleWidth / 2,
+ stashedCenterY + mStashedHandleHeight / 2);
+ mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
+ mStashedHandleRadius = view.getHeight() / 2f;
+ outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
+ }
+ });
+
+ mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> {
+ final int stashedCenterX = view.getWidth() / 2;
+ final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
+
+ view.setPivotX(stashedCenterX);
+ view.setPivotY(stashedCenterY);
+ });
+ }
+
+ public void onDestroy() {
+ mRegionSamplingHelper.stopAndDestroy();
+ }
+
+ public MultiValueAlpha getStashedHandleAlpha() {
+ return mTaskbarStashedHandleAlpha;
+ }
+
+ public AnimatedFloat getStashedHandleHintScale() {
+ return mTaskbarStashedHandleHintScale;
+ }
+
+ /**
+ * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle
+ * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
+ * morphs into the size of where the taskbar icons will be.
+ */
+ public Animator createRevealAnimToIsStashed(boolean isStashed) {
+ final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
+ mStashedHandleRadius, mStashedHandleRadius,
+ mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds);
+
+ boolean isReversed = !isStashed;
+ boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
+ mWasLastRevealAnimReversed = isReversed;
+ if (changingDirection) {
+ mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
+ }
+
+ ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
+ isReversed, mStartProgressForNextRevealAnim);
+ revealAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
+ }
+ });
+ return revealAnim;
+ }
+
+ public void onIsStashed(boolean isStashed) {
+ mRegionSamplingHelper.setWindowVisible(isStashed);
+ if (isStashed) {
+ mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
+ mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
+ } else {
+ mRegionSamplingHelper.stop();
+ }
+ }
+
+ protected void updateStashedHandleHintScale() {
+ mStashedHandleView.setScaleX(mTaskbarStashedHandleHintScale.value);
+ mStashedHandleView.setScaleY(mTaskbarStashedHandleHintScale.value);
+ }
+
+ /**
+ * Should be called when the home button is disabled, so we can hide this handle as well.
+ */
+ public void setIsHomeButtonDisabled(boolean homeDisabled) {
+ mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_HOME_DISABLED).setValue(
+ homeDisabled ? 0 : 1);
+ }
+
+ public boolean isStashedHandleVisible() {
+ return mStashedHandleView.getVisibility() == View.VISIBLE;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 4ba0ee0d41..5308a8f3e8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -17,29 +17,37 @@ package com.android.launcher3.taskbar;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
+import android.animation.AnimatorSet;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo.Config;
import android.content.pm.LauncherApps;
+import android.content.res.Resources;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
-import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.os.Process;
import android.os.SystemProperties;
+import android.provider.Settings;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.RoundedCorner;
import android.view.View;
import android.view.WindowManager;
+import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -47,30 +55,27 @@ import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.ViewCache;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
/**
* The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
@@ -88,59 +93,97 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
private final DeviceProfile mDeviceProfile;
private final LayoutInflater mLayoutInflater;
private final TaskbarDragLayer mDragLayer;
- private final TaskbarIconController mIconController;
- private final MyDragController mDragController;
+ private final TaskbarControllers mControllers;
private final WindowManager mWindowManager;
+ private final @Nullable RoundedCorner mLeftCorner, mRightCorner;
+ private final int mTaskbarHeightForIme;
private WindowManager.LayoutParams mWindowLayoutParams;
+ private boolean mIsFullscreen;
+ // The size we should return to when we call setTaskbarWindowFullscreen(false)
+ private int mLastRequestedNonFullscreenHeight;
private final SysUINavigationMode.Mode mNavMode;
- private final TaskbarNavButtonController mNavButtonController;
+ private final ViewCache mViewCache = new ViewCache();
private final boolean mIsSafeModeEnabled;
-
- @NonNull
- private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
-
- private final View.OnClickListener mOnTaskbarIconClickListener;
- private final View.OnLongClickListener mOnTaskbarIconLongClickListener;
+ private final boolean mIsUserSetupComplete;
+ private boolean mIsDestroyed = false;
+ // The flag to know if the window is excluded from magnification region computation.
+ private boolean mIsExcludeFromMagnificationRegion = false;
public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
- TaskbarNavButtonController buttonController) {
+ TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
+ unfoldTransitionProgressProvider) {
super(windowContext, Themes.getActivityThemeRes(windowContext));
mDeviceProfile = dp;
- mNavButtonController = buttonController;
+
mNavMode = SysUINavigationMode.getMode(windowContext);
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
+ mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(
+ Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
- mOnTaskbarIconLongClickListener =
- new TaskbarDragController(this)::startSystemDragOnLongClick;
- mOnTaskbarIconClickListener = this::onTaskbarIconClicked;
-
- float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
+ final Resources resources = getResources();
+ float taskbarIconSize = resources.getDimension(R.dimen.taskbar_icon_size);
+ mDeviceProfile.updateIconSize(1, resources);
float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
- mDeviceProfile.updateIconSize(iconScale, getResources());
+ mDeviceProfile.updateIconSize(iconScale, resources);
+
+ mTaskbarHeightForIme = resources.getDimensionPixelSize(R.dimen.taskbar_ime_size);
mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
- mDragLayer = (TaskbarDragLayer) mLayoutInflater
- .inflate(R.layout.taskbar, null, false);
- mIconController = new TaskbarIconController(this, mDragLayer);
- mDragController = new MyDragController(this);
+
+ // Inflate views.
+ mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(
+ R.layout.taskbar, null, false);
+ TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
+ TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
+ FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
+ StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
Display display = windowContext.getDisplay();
Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
? windowContext.getApplicationContext()
: windowContext.getApplicationContext().createDisplayContext(display);
mWindowManager = c.getSystemService(WindowManager.class);
+ mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+ mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
+
+ // Construct controllers.
+ mControllers = new TaskbarControllers(this,
+ new TaskbarDragController(this),
+ buttonController,
+ new NavbarButtonsViewController(this, navButtonsView),
+ new RotationButtonController(this,
+ c.getColor(R.color.taskbar_nav_icon_light_color),
+ c.getColor(R.color.taskbar_nav_icon_dark_color),
+ R.drawable.ic_sysbar_rotate_button_ccw_start_0,
+ R.drawable.ic_sysbar_rotate_button_ccw_start_90,
+ R.drawable.ic_sysbar_rotate_button_cw_start_0,
+ R.drawable.ic_sysbar_rotate_button_cw_start_90,
+ () -> getDisplay().getRotation()),
+ new TaskbarDragLayerController(this, mDragLayer),
+ new TaskbarViewController(this, taskbarView),
+ new TaskbarScrimViewController(this, taskbarScrimView),
+ new TaskbarUnfoldAnimationController(unfoldTransitionProgressProvider,
+ mWindowManager),
+ new TaskbarKeyguardController(this),
+ new StashedHandleViewController(this, stashedHandleView),
+ new TaskbarStashController(this),
+ new TaskbarEduController(this),
+ new TaskbarAutohideSuspendController(this),
+ new TaskbarPopupController());
}
- public void init() {
+ public void init(TaskbarSharedState sharedState) {
+ mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
mWindowLayoutParams = new WindowManager.LayoutParams(
MATCH_PARENT,
- mDeviceProfile.taskbarSize,
- TYPE_APPLICATION_OVERLAY,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ mLastRequestedNonFullscreenHeight,
+ TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
mWindowLayoutParams.setTitle(WINDOW_TITLE);
mWindowLayoutParams.packageName = getPackageName();
@@ -148,31 +191,40 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
mWindowLayoutParams.setFitInsetsTypes(0);
mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- mWindowLayoutParams.setSystemApplicationOverlay(true);
+ mWindowLayoutParams.privateFlags =
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
wmWrapper.setProvidesInsetsTypes(
mWindowLayoutParams,
new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
);
+ // Adjust the frame by the rounded corners (ie. leaving just the bar as the inset) when
+ // the IME is showing
+ mWindowLayoutParams.providedInternalImeInsets = Insets.of(0,
+ getDefaultTaskbarWindowHeight() - mTaskbarHeightForIme, 0, 0);
+
+ // Initialize controllers after all are constructed.
+ mControllers.init(sharedState);
+ updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
- mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener);
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
}
- /**
- * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
- */
- public void setTaskbarWindowHeight(int height) {
- if (mWindowLayoutParams.height == height) {
- return;
- }
- mWindowLayoutParams.height = height;
- mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+ public void onConfigurationChanged(@Config int configChanges) {
+ mControllers.onConfigurationChanged(configChanges);
}
- public boolean canShowNavButtons() {
- return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS;
+ public boolean isThreeButtonNav() {
+ return mNavMode == Mode.THREE_BUTTONS;
+ }
+
+ public int getLeftCornerRadius() {
+ return mLeftCorner == null ? 0 : mLeftCorner.getRadius();
+ }
+
+ public int getRightCornerRadius() {
+ return mRightCorner == null ? 0 : mRightCorner.getRadius();
}
@Override
@@ -192,59 +244,223 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
@Override
public Rect getFolderBoundingBox() {
- return mDragLayer.getFolderBoundingBox();
+ return mControllers.taskbarDragLayerController.getFolderBoundingBox();
}
@Override
- public DragController getDragController() {
- return mDragController;
+ public TaskbarDragController getDragController() {
+ return mControllers.taskbarDragController;
+ }
+
+ @Override
+ public ViewCache getViewCache() {
+ return mViewCache;
+ }
+
+ @Override
+ public boolean supportsIme() {
+ // Currently we don't support IME because we have FLAG_NOT_FOCUSABLE. We can remove that
+ // flag when opening a floating view that needs IME (such as Folder), but then that means
+ // Taskbar will be below IME and thus users can't click the back button.
+ return false;
+ }
+
+ @Override
+ public View.OnClickListener getItemOnClickListener() {
+ return this::onTaskbarIconClicked;
+ }
+
+ /**
+ * Change from hotseat/predicted hotseat to taskbar container.
+ */
+ @Override
+ public void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) {
+ if (!itemInfoBuilder.hasContainerInfo()) {
+ return;
+ }
+ LauncherAtom.ContainerInfo oldContainer = itemInfoBuilder.getContainerInfo();
+
+ if (oldContainer.hasPredictedHotseatContainer()) {
+ LauncherAtom.PredictedHotseatContainer predictedHotseat =
+ oldContainer.getPredictedHotseatContainer();
+ LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
+ LauncherAtom.TaskBarContainer.newBuilder();
+
+ if (predictedHotseat.hasIndex()) {
+ taskbarBuilder.setIndex(predictedHotseat.getIndex());
+ }
+ if (predictedHotseat.hasCardinality()) {
+ taskbarBuilder.setCardinality(predictedHotseat.getCardinality());
+ }
+
+ itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setTaskBarContainer(taskbarBuilder));
+ } else if (oldContainer.hasHotseat()) {
+ LauncherAtom.HotseatContainer hotseat = oldContainer.getHotseat();
+ LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
+ LauncherAtom.TaskBarContainer.newBuilder();
+
+ if (hotseat.hasIndex()) {
+ taskbarBuilder.setIndex(hotseat.getIndex());
+ }
+
+ itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setTaskBarContainer(taskbarBuilder));
+ } else if (oldContainer.hasFolder() && oldContainer.getFolder().hasHotseat()) {
+ LauncherAtom.FolderContainer.Builder folderBuilder = oldContainer.getFolder()
+ .toBuilder();
+ LauncherAtom.HotseatContainer hotseat = folderBuilder.getHotseat();
+ LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
+ LauncherAtom.TaskBarContainer.newBuilder();
+
+ if (hotseat.hasIndex()) {
+ taskbarBuilder.setIndex(hotseat.getIndex());
+ }
+
+ folderBuilder.setTaskbar(taskbarBuilder);
+ folderBuilder.clearHotseat();
+ itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setFolder(folderBuilder));
+ }
}
/**
* Sets a new data-source for this taskbar instance
*/
public void setUIController(@NonNull TaskbarUIController uiController) {
- mUIController.onDestroy();
- mUIController = uiController;
- mIconController.setUIController(mUIController);
- mUIController.onCreate();
+ mControllers.uiController.onDestroy();
+ mControllers.uiController = uiController;
+ mControllers.uiController.init(mControllers);
+ }
+
+ /**
+ * Sets the flag indicating setup UI is visible
+ */
+ public void setSetupUIVisible(boolean isVisible) {
+ mControllers.taskbarStashController.setSetupUIVisible(isVisible);
}
/**
* Called when this instance of taskbar is no longer needed
*/
public void onDestroy() {
+ mIsDestroyed = true;
setUIController(TaskbarUIController.DEFAULT);
- mIconController.onDestroy();
+ mControllers.onDestroy();
mWindowManager.removeViewImmediate(mDragLayer);
}
- void onNavigationButtonClick(@TaskbarButton int buttonType) {
- mNavButtonController.onButtonClick(buttonType);
+ public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) {
+ mControllers.navbarButtonsViewController.updateStateForSysuiFlags(systemUiStateFlags,
+ fromInit);
+ mControllers.taskbarViewController.setImeIsVisible(
+ mControllers.navbarButtonsViewController.isImeVisible());
+ int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+ | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+ onNotificationShadeExpandChanged((systemUiStateFlags & shadeExpandedFlags) != 0, fromInit);
+ mControllers.taskbarViewController.setRecentsButtonDisabled(
+ mControllers.navbarButtonsViewController.isRecentsDisabled());
+ mControllers.stashedHandleViewController.setIsHomeButtonDisabled(
+ mControllers.navbarButtonsViewController.isHomeDisabled());
+ mControllers.taskbarKeyguardController.updateStateForSysuiFlags(systemUiStateFlags);
+ mControllers.taskbarStashController.updateStateForSysuiFlags(systemUiStateFlags, fromInit);
+ mControllers.taskbarScrimViewController.updateStateForSysuiFlags(systemUiStateFlags,
+ fromInit);
+ mControllers.navButtonController.updateSysuiFlags(systemUiStateFlags);
}
/**
- * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
+ * Hides the taskbar icons and background when the notication shade is expanded.
*/
- public void setImeIsVisible(boolean isImeVisible) {
- mIconController.setImeIsVisible(isImeVisible);
+ private void onNotificationShadeExpandChanged(boolean isExpanded, boolean skipAnim) {
+ float alpha = isExpanded ? 0 : 1;
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(mControllers.taskbarViewController.getTaskbarIconAlpha().getProperty(
+ TaskbarViewController.ALPHA_INDEX_NOTIFICATION_EXPANDED).animateToValue(alpha));
+ if (!isThreeButtonNav()) {
+ anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar()
+ .animateToValue(alpha));
+ }
+ anim.start();
+ if (skipAnim) {
+ anim.end();
+ }
}
- /**
- * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
- * instantiating at all, which is what's responsible for sending sysui state flags over.
- *
- * @param vis IME visibility flag
- */
- public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
- mIconController.updateImeStatus(displayId, vis, showImeSwitcher);
+ public void onRotationProposal(int rotation, boolean isValid) {
+ mControllers.rotationButtonController.onRotationProposal(rotation, isValid);
+ }
+
+ public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
+ if (displayId != getDisplayId()) {
+ return;
+ }
+ mControllers.rotationButtonController.onDisable2FlagChanged(state2);
+ }
+
+ public void onSystemBarAttributesChanged(int displayId, int behavior) {
+ mControllers.rotationButtonController.onBehaviorChanged(displayId, behavior);
+ }
+
+ public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
+ if (!isUserSetupComplete()) {
+ return;
+ }
+ mControllers.navbarButtonsViewController.getTaskbarNavButtonDarkIntensity()
+ .updateValue(darkIntensity);
}
/**
* Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
*/
- protected void setTaskbarWindowFullscreen(boolean fullscreen) {
- setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : getDeviceProfile().taskbarSize);
+ public void setTaskbarWindowFullscreen(boolean fullscreen) {
+ mControllers.taskbarAutohideSuspendController.updateFlag(
+ TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN, fullscreen);
+ mIsFullscreen = fullscreen;
+ setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : mLastRequestedNonFullscreenHeight);
+ }
+
+ public boolean isTaskbarWindowFullscreen() {
+ return mIsFullscreen;
+ }
+
+ /**
+ * Updates the TaskbarContainer height (pass {@link #getDefaultTaskbarWindowHeight()} to reset).
+ */
+ public void setTaskbarWindowHeight(int height) {
+ if (mWindowLayoutParams.height == height || mIsDestroyed) {
+ return;
+ }
+ if (height == MATCH_PARENT) {
+ height = mDeviceProfile.heightPx;
+ } else {
+ mLastRequestedNonFullscreenHeight = height;
+ if (mIsFullscreen) {
+ // We still need to be fullscreen, so defer any change to our height until we call
+ // setTaskbarWindowFullscreen(false). For example, this could happen when dragging
+ // from the gesture region, as the drag will cancel the gesture and reset launcher's
+ // state, which in turn normally would reset the taskbar window height as well.
+ return;
+ }
+ }
+ mWindowLayoutParams.height = height;
+ mWindowLayoutParams.providedInternalImeInsets =
+ Insets.of(0, height - mTaskbarHeightForIme, 0, 0);
+ mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+ }
+
+ /**
+ * Returns the default height of the window, including the static corner radii above taskbar.
+ */
+ public int getDefaultTaskbarWindowHeight() {
+ return mDeviceProfile.taskbarSize + Math.max(getLeftCornerRadius(), getRightCornerRadius());
+ }
+
+ /**
+ * Returns the bottom insets taskbar provides to the IME when IME is visible.
+ */
+ public int getTaskbarHeightForIme() {
+ return mTaskbarHeightForIme;
}
protected void onTaskbarIconClicked(View view) {
@@ -260,10 +476,11 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
getDragLayer().post(() -> {
folder.animateOpen();
+ getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN);
folder.iterateOverItems((itemInfo, itemView) -> {
- itemView.setOnClickListener(mOnTaskbarIconClickListener);
- itemView.setOnLongClickListener(mOnTaskbarIconLongClickListener);
+ mControllers.taskbarViewController
+ .setClickAndLongClickListenersForIcon(itemView);
// To play haptic when dragging, like other Taskbar items do.
itemView.setHapticFeedbackEnabled(true);
return false;
@@ -271,7 +488,9 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
});
} else if (tag instanceof WorkspaceItemInfo) {
WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
- if (!(info.isDisabled() && ItemClickHandler.handleDisabledItemClicked(info, this))) {
+ if (info.isDisabled()) {
+ ItemClickHandler.handleDisabledItemClicked(info, this);
+ } else {
Intent intent = new Intent(info.getIntent())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
@@ -295,6 +514,8 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
getSystemService(LauncherApps.class).startMainActivity(
intent.getComponent(), info.user, intent.getSourceBounds(), null);
}
+
+ mControllers.uiController.onTaskbarIconLaunched(info);
} catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
.show();
@@ -308,26 +529,44 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
AbstractFloatingView.closeAllOpenViews(this);
}
- private static class MyDragController extends DragController {
- MyDragController(TaskbarActivityContext activity) {
- super(activity);
+ /**
+ * Called when we detect a long press in the nav region before passing the gesture slop.
+ * @return Whether taskbar handled the long press, and thus should cancel the gesture.
+ */
+ public boolean onLongPressToUnstashTaskbar() {
+ return mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
+ }
+
+ /**
+ * Called when we detect a motion down or up/cancel in the nav region while stashed.
+ * @param animateForward Whether to animate towards the unstashed hint state or back to stashed.
+ */
+ public void startTaskbarUnstashHint(boolean animateForward) {
+ mControllers.taskbarStashController.startUnstashHint(animateForward);
+ }
+
+ protected boolean isUserSetupComplete() {
+ return mIsUserSetupComplete;
+ }
+
+ /**
+ * Called when we determine the touchable region.
+ *
+ * @param exclude {@code true} then the magnification region computation will omit the window.
+ */
+ public void excludeFromMagnificationRegion(boolean exclude) {
+ if (mIsExcludeFromMagnificationRegion == exclude) {
+ return;
}
- @Override
- protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
- DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
- ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
- float dragViewScaleOnDrop, DragOptions options) {
- return null;
- }
-
- @Override
- protected void exitDrag() {
- }
-
- @Override
- protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
- return null;
+ mIsExcludeFromMagnificationRegion = exclude;
+ if (exclude) {
+ mWindowLayoutParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+ } else {
+ mWindowLayoutParams.privateFlags &=
+ ~WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
}
+ mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
deleted file mode 100644
index e20ddf88ce..0000000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import static com.android.launcher3.LauncherState.TASKBAR;
-
-import android.animation.Animator;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.taskbar.LauncherTaskbarUIController.TaskbarAnimationControllerCallbacks;
-import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.SystemUiProxy;
-import com.android.systemui.shared.system.QuickStepContract;
-
-/**
- * Works with TaskbarController to update the TaskbarView's visual properties based on factors such
- * as LauncherState, whether Launcher is in the foreground, etc.
- */
-public class TaskbarAnimationController {
-
- private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
-
- private final BaseQuickstepLauncher mLauncher;
- private final TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
-
- // Background alpha.
- private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
- this::onTaskbarBackgroundAlphaChanged);
-
- // Overall visibility.
- private final AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
- this::updateVisibilityAlpha);
- private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
- this::updateVisibilityAlphaForIme);
-
- // Scale.
- private final AnimatedFloat mTaskbarScaleForLauncherState = new AnimatedFloat(
- this::updateScale);
-
- // TranslationY.
- private final AnimatedFloat mTaskbarTranslationYForLauncherState = new AnimatedFloat(
- this::updateTranslationY);
-
- public TaskbarAnimationController(BaseQuickstepLauncher launcher,
- TaskbarAnimationControllerCallbacks taskbarCallbacks) {
- mLauncher = launcher;
- mTaskbarCallbacks = taskbarCallbacks;
- }
-
- protected void init() {
- mTaskbarBackgroundAlpha.updateValue(mLauncher.hasBeenResumed() ? 0f : 1f);
- boolean isVisibleForLauncherState = (mLauncher.getStateManager().getState()
- .getVisibleElements(mLauncher) & TASKBAR) != 0;
- mTaskbarVisibilityAlphaForLauncherState.updateValue(isVisibleForLauncherState ? 1f : 0f);
- boolean isImeVisible = (SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags()
- & QuickStepContract.SYSUI_STATE_IME_SHOWING) != 0;
- mTaskbarVisibilityAlphaForIme.updateValue(isImeVisible ? 0f : 1f);
-
- onTaskbarBackgroundAlphaChanged();
- updateVisibilityAlpha();
- }
-
- protected void cleanup() {
- setNavBarButtonAlpha(1f);
- }
-
- protected AnimatedFloat getTaskbarVisibilityForLauncherState() {
- return mTaskbarVisibilityAlphaForLauncherState;
- }
-
- protected AnimatedFloat getTaskbarScaleForLauncherState() {
- return mTaskbarScaleForLauncherState;
- }
-
- protected AnimatedFloat getTaskbarTranslationYForLauncherState() {
- return mTaskbarTranslationYForLauncherState;
- }
-
- protected Animator createAnimToBackgroundAlpha(float toAlpha, long duration) {
- return mTaskbarBackgroundAlpha.animateToValue(mTaskbarBackgroundAlpha.value, toAlpha)
- .setDuration(duration);
- }
-
- protected void animateToVisibilityForIme(float toAlpha) {
- mTaskbarVisibilityAlphaForIme.animateToValue(mTaskbarVisibilityAlphaForIme.value, toAlpha)
- .setDuration(IME_VISIBILITY_ALPHA_DURATION).start();
- }
-
- private void onTaskbarBackgroundAlphaChanged() {
- mTaskbarCallbacks.updateTaskbarBackgroundAlpha(mTaskbarBackgroundAlpha.value);
- updateVisibilityAlpha();
- updateScale();
- updateTranslationY();
- }
-
- private void updateVisibilityAlpha() {
- // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
- // assumption being that Taskbar should always be visible regardless of the current
- // LauncherState if Launcher is paused.
- float alphaDueToIme = mTaskbarVisibilityAlphaForIme.value;
- float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value,
- mTaskbarVisibilityAlphaForLauncherState.value);
- float taskbarAlpha = alphaDueToLauncher * alphaDueToIme;
- mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha);
-
- // Make the nav bar invisible if taskbar is visible.
- setNavBarButtonAlpha(1f - taskbarAlpha);
- }
-
- private void updateVisibilityAlphaForIme() {
- updateVisibilityAlpha();
- float taskbarAlphaDueToIme = mTaskbarVisibilityAlphaForIme.value;
- mTaskbarCallbacks.updateImeBarVisibilityAlpha(1f - taskbarAlphaDueToIme);
- }
-
- private void updateScale() {
- // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
- // assumption being that Taskbar should always be at scale 1f regardless of the current
- // LauncherState if Launcher is paused.
- float scale = mTaskbarScaleForLauncherState.value;
- scale = Utilities.mapRange(mTaskbarBackgroundAlpha.value, scale, 1f);
- mTaskbarCallbacks.updateTaskbarScale(scale);
- }
-
- private void updateTranslationY() {
- // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
- // assumption being that Taskbar should always be at translationY 0f regardless of the
- // current LauncherState if Launcher is paused.
- float translationY = mTaskbarTranslationYForLauncherState.value;
- translationY = Utilities.mapRange(mTaskbarBackgroundAlpha.value, translationY, 0f);
- mTaskbarCallbacks.updateTaskbarTranslationY(translationY);
- }
-
- private void setNavBarButtonAlpha(float navBarAlpha) {
- SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
new file mode 100644
index 0000000000..e42f83de6a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import androidx.annotation.IntDef;
+
+import com.android.quickstep.SystemUiProxy;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Normally Taskbar will auto-hide when entering immersive (fullscreen) apps. This controller allows
+ * us to suspend that behavior in certain cases (e.g. opening a Folder or dragging an icon).
+ */
+public class TaskbarAutohideSuspendController {
+
+ public static final int FLAG_AUTOHIDE_SUSPEND_FULLSCREEN = 1 << 0;
+ public static final int FLAG_AUTOHIDE_SUSPEND_DRAGGING = 1 << 1;
+ @IntDef(flag = true, value = {
+ FLAG_AUTOHIDE_SUSPEND_FULLSCREEN,
+ FLAG_AUTOHIDE_SUSPEND_DRAGGING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AutohideSuspendFlag {}
+
+ private final SystemUiProxy mSystemUiProxy;
+
+ private @AutohideSuspendFlag int mAutohideSuspendFlags = 0;
+
+ public TaskbarAutohideSuspendController(TaskbarActivityContext activity) {
+ mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
+ }
+
+ public void onDestroy() {
+ mSystemUiProxy.notifyTaskbarAutohideSuspend(false);
+ }
+
+ /**
+ * Adds or removes the given flag, then notifies system UI proxy whether to suspend auto-hide.
+ */
+ public void updateFlag(@AutohideSuspendFlag int flag, boolean enabled) {
+ if (enabled) {
+ mAutohideSuspendFlags |= flag;
+ } else {
+ mAutohideSuspendFlags &= ~flag;
+ }
+ mSystemUiProxy.notifyTaskbarAutohideSuspend(mAutohideSuspendFlags != 0);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
new file mode 100644
index 0000000000..c43fbf9b20
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.content.pm.ActivityInfo.Config;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.shared.rotation.RotationButtonController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Hosts various taskbar controllers to facilitate passing between one another.
+ */
+public class TaskbarControllers {
+
+ public final TaskbarActivityContext taskbarActivityContext;
+ public final TaskbarDragController taskbarDragController;
+ public final TaskbarNavButtonController navButtonController;
+ public final NavbarButtonsViewController navbarButtonsViewController;
+ public final RotationButtonController rotationButtonController;
+ public final TaskbarDragLayerController taskbarDragLayerController;
+ public final TaskbarScrimViewController taskbarScrimViewController;
+ public final TaskbarViewController taskbarViewController;
+ public final TaskbarUnfoldAnimationController taskbarUnfoldAnimationController;
+ public final TaskbarKeyguardController taskbarKeyguardController;
+ public final StashedHandleViewController stashedHandleViewController;
+ public final TaskbarStashController taskbarStashController;
+ public final TaskbarEduController taskbarEduController;
+ public final TaskbarAutohideSuspendController taskbarAutohideSuspendController;
+ public final TaskbarPopupController taskbarPopupController;
+
+ /** Do not store this controller, as it may change at runtime. */
+ @NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT;
+
+ private boolean mAreAllControllersInitialized;
+ private final List mPostInitCallbacks = new ArrayList<>();
+
+ public TaskbarControllers(TaskbarActivityContext taskbarActivityContext,
+ TaskbarDragController taskbarDragController,
+ TaskbarNavButtonController navButtonController,
+ NavbarButtonsViewController navbarButtonsViewController,
+ RotationButtonController rotationButtonController,
+ TaskbarDragLayerController taskbarDragLayerController,
+ TaskbarViewController taskbarViewController,
+ TaskbarScrimViewController taskbarScrimViewController,
+ TaskbarUnfoldAnimationController taskbarUnfoldAnimationController,
+ TaskbarKeyguardController taskbarKeyguardController,
+ StashedHandleViewController stashedHandleViewController,
+ TaskbarStashController taskbarStashController,
+ TaskbarEduController taskbarEduController,
+ TaskbarAutohideSuspendController taskbarAutoHideSuspendController,
+ TaskbarPopupController taskbarPopupController) {
+ this.taskbarActivityContext = taskbarActivityContext;
+ this.taskbarDragController = taskbarDragController;
+ this.navButtonController = navButtonController;
+ this.navbarButtonsViewController = navbarButtonsViewController;
+ this.rotationButtonController = rotationButtonController;
+ this.taskbarDragLayerController = taskbarDragLayerController;
+ this.taskbarViewController = taskbarViewController;
+ this.taskbarScrimViewController = taskbarScrimViewController;
+ this.taskbarUnfoldAnimationController = taskbarUnfoldAnimationController;
+ this.taskbarKeyguardController = taskbarKeyguardController;
+ this.stashedHandleViewController = stashedHandleViewController;
+ this.taskbarStashController = taskbarStashController;
+ this.taskbarEduController = taskbarEduController;
+ this.taskbarAutohideSuspendController = taskbarAutoHideSuspendController;
+ this.taskbarPopupController = taskbarPopupController;
+ }
+
+ /**
+ * Initializes all controllers. Note that controllers can now reference each other through this
+ * TaskbarControllers instance, but should be careful to only access things that were created
+ * in constructors for now, as some controllers may still be waiting for init().
+ */
+ public void init(TaskbarSharedState sharedState) {
+ mAreAllControllersInitialized = false;
+
+ taskbarDragController.init(this);
+ navbarButtonsViewController.init(this);
+ rotationButtonController.init();
+ taskbarDragLayerController.init(this);
+ taskbarViewController.init(this);
+ taskbarScrimViewController.init(this);
+ taskbarUnfoldAnimationController.init(this);
+ taskbarKeyguardController.init(navbarButtonsViewController);
+ stashedHandleViewController.init(this);
+ taskbarStashController.init(this, sharedState);
+ taskbarEduController.init(this);
+
+ mAreAllControllersInitialized = true;
+ for (Runnable postInitCallback : mPostInitCallbacks) {
+ postInitCallback.run();
+ }
+ mPostInitCallbacks.clear();
+ }
+
+ public void onConfigurationChanged(@Config int configChanges) {
+ navbarButtonsViewController.onConfigurationChanged(configChanges);
+ }
+
+ /**
+ * Cleans up all controllers.
+ */
+ public void onDestroy() {
+ navbarButtonsViewController.onDestroy();
+ uiController.onDestroy();
+ rotationButtonController.onDestroy();
+ taskbarDragLayerController.onDestroy();
+ taskbarKeyguardController.onDestroy();
+ taskbarUnfoldAnimationController.onDestroy();
+ taskbarViewController.onDestroy();
+ stashedHandleViewController.onDestroy();
+ taskbarAutohideSuspendController.onDestroy();
+ }
+
+ /**
+ * If all controllers are already initialized, runs the given callback immediately. Otherwise,
+ * queues it to run after calling init() on all controllers. This should likely be used in any
+ * case where one controller is telling another controller to do something inside init().
+ */
+ public void runAfterInit(Runnable callback) {
+ if (mAreAllControllersInitialized) {
+ callback.run();
+ } else {
+ mPostInitCallbacks.add(callback);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index ee44927ce4..b3a9f8db7c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -15,25 +15,42 @@
*/
package com.android.launcher3.taskbar;
-import static android.view.View.INVISIBLE;
-import static android.view.View.VISIBLE;
-
import android.content.ClipData;
import android.content.ClipDescription;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.view.DragEvent;
+import android.view.MotionEvent;
import android.view.View;
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragDriver;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ClipDescriptionCompat;
import com.android.systemui.shared.system.LauncherAppsCompat;
@@ -41,52 +58,213 @@ import com.android.systemui.shared.system.LauncherAppsCompat;
/**
* Handles long click on Taskbar items to start a system drag and drop operation.
*/
-public class TaskbarDragController {
+public class TaskbarDragController extends DragController {
- private final Context mContext;
private final int mDragIconSize;
+ private final int[] mTempXY = new int[2];
- public TaskbarDragController(Context context) {
- mContext = context;
- Resources resources = mContext.getResources();
+ // Initialized in init.
+ TaskbarControllers mControllers;
+
+ // Where the initial touch was relative to the dragged icon.
+ private int mRegistrationX;
+ private int mRegistrationY;
+
+ private boolean mIsSystemDragInProgress;
+
+ public TaskbarDragController(TaskbarActivityContext activity) {
+ super(activity);
+ Resources resources = mActivity.getResources();
mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
}
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ }
+
/**
* Attempts to start a system drag and drop operation for the given View, using its tag to
* generate the ClipDescription and Intent.
* @return Whether {@link View#startDragAndDrop} started successfully.
*/
- protected boolean startSystemDragOnLongClick(View view) {
+ protected boolean startDragOnLongClick(View view) {
if (!(view instanceof BubbleTextView)) {
return false;
}
BubbleTextView btv = (BubbleTextView) view;
- View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
+
+ mActivity.setTaskbarWindowFullscreen(true);
+ btv.post(() -> {
+ startInternalDrag(btv);
+ btv.getIcon().setIsDisabled(true);
+ mControllers.taskbarAutohideSuspendController.updateFlag(
+ TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, true);
+ });
+ return true;
+ }
+
+ private void startInternalDrag(BubbleTextView btv) {
+ float iconScale = btv.getIcon().getAnimatedScale();
+
+ // Clear the pressed state if necessary
+ btv.clearFocus();
+ btv.setPressed(false);
+ btv.clearPressedBackground();
+
+ final DragPreviewProvider previewProvider = new DragPreviewProvider(btv);
+ final Drawable drawable = previewProvider.createDrawable();
+ final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
+ int dragLayerX = mTempXY[0];
+ int dragLayerY = mTempXY[1];
+
+ Rect dragRect = new Rect();
+ btv.getSourceVisualDragBounds(dragRect);
+ dragLayerY += dragRect.top;
+
+ DragOptions dragOptions = new DragOptions();
+ dragOptions.preDragCondition = new DragOptions.PreDragCondition() {
+ private DragView mDragView;
+
+ @Override
+ public boolean shouldStartDrag(double distanceDragged) {
+ return mDragView != null && mDragView.isAnimationFinished();
+ }
+
+ @Override
+ public void onPreDragStart(DropTarget.DragObject dragObject) {
+ mDragView = dragObject.dragView;
+ }
+
+ @Override
+ public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
+ mDragView = null;
+ }
+ };
+ if (FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()) {
+ PopupContainerWithArrow popupContainer =
+ mControllers.taskbarPopupController.showForIcon(btv);
+ if (popupContainer != null) {
+ dragOptions.preDragCondition = popupContainer.createPreDragCondition();
+ }
+ }
+
+ startDrag(
+ drawable,
+ /* view = */ null,
+ /* originalView = */ btv,
+ dragLayerX,
+ dragLayerY,
+ (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
+ (WorkspaceItemInfo) btv.getTag(),
+ /* dragVisualizeOffset = */ null,
+ dragRect,
+ scale * iconScale,
+ scale,
+ dragOptions);
+ }
+
+ @Override
+ protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
+ DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
+ ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
+ float dragViewScaleOnDrop, DragOptions options) {
+ mOptions = options;
+
+ mRegistrationX = mMotionDown.x - dragLayerX;
+ mRegistrationY = mMotionDown.y - dragLayerY;
+
+ final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
+ final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
+
+ mLastDropTarget = null;
+
+ mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext());
+ mDragObject.originalView = originalView;
+ mDragObject.deferDragViewCleanupPostAnimation = false;
+
+ mIsInPreDrag = mOptions.preDragCondition != null
+ && !mOptions.preDragCondition.shouldStartDrag(0);
+
+ float scalePx = mDragIconSize - dragRegion.width();
+ final DragView dragView = mDragObject.dragView = new TaskbarDragView(
+ mActivity,
+ drawable,
+ mRegistrationX,
+ mRegistrationY,
+ initialDragViewScale,
+ dragViewScaleOnDrop,
+ scalePx);
+ dragView.setItemInfo(dragInfo);
+ mDragObject.dragComplete = false;
+
+ mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
+ mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
+
+ mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {});
+ if (!mOptions.isAccessibleDrag) {
+ mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
+ }
+
+ mDragObject.dragSource = source;
+ mDragObject.dragInfo = dragInfo;
+ mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
+
+ if (dragRegion != null) {
+ dragView.setDragRegion(new Rect(dragRegion));
+ }
+
+ dragView.show(mLastTouch.x, mLastTouch.y);
+ mDistanceSinceScroll = 0;
+
+ if (!mIsInPreDrag) {
+ callOnDragStart();
+ } else if (mOptions.preDragCondition != null) {
+ mOptions.preDragCondition.onPreDragStart(mDragObject);
+ }
+
+ handleMoveEvent(mLastTouch.x, mLastTouch.y);
+
+ return dragView;
+ }
+
+ @Override
+ protected void callOnDragStart() {
+ super.callOnDragStart();
+ // Pre-drag has ended, start the global system drag.
+ AbstractFloatingView.closeAllOpenViews(mActivity);
+ startSystemDrag((BubbleTextView) mDragObject.originalView);
+ }
+
+ private void startSystemDrag(BubbleTextView btv) {
+ View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(btv) {
+
@Override
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
shadowSize.set(mDragIconSize, mDragIconSize);
- // TODO: should be based on last touch point on the icon.
- shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y / 2);
+ // The registration point was taken before the icon scaled to mDragIconSize, so
+ // offset the registration to where the touch is on the new size.
+ int offsetX = (mDragIconSize - mDragObject.dragView.getDragRegionWidth()) / 2;
+ int offsetY = (mDragIconSize - mDragObject.dragView.getDragRegionHeight()) / 2;
+ shadowTouchPoint.set(mRegistrationX + offsetX, mRegistrationY + offsetY);
}
@Override
public void onDrawShadow(Canvas canvas) {
canvas.save();
- float scale = (float) mDragIconSize / btv.getIconSize();
+ float scale = mDragObject.dragView.getScaleX();
canvas.scale(scale, scale);
- btv.getIcon().draw(canvas);
+ mDragObject.dragView.draw(canvas);
canvas.restore();
}
};
- Object tag = view.getTag();
+ Object tag = btv.getTag();
ClipDescription clipDescription = null;
Intent intent = null;
if (tag instanceof WorkspaceItemInfo) {
WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
- LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ LauncherApps launcherApps = mActivity.getSystemService(LauncherApps.class);
clipDescription = new ClipDescription(item.title,
new String[] {
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
@@ -115,29 +293,97 @@ public class TaskbarDragController {
}
if (clipDescription != null && intent != null) {
+ // Need to share the same InstanceId between launcher3 and WM Shell (internal).
+ InstanceId internalInstanceId = new InstanceIdSequence(
+ com.android.launcher3.logging.InstanceId.INSTANCE_ID_MAX).newInstanceId();
+ com.android.launcher3.logging.InstanceId launcherInstanceId =
+ new com.android.launcher3.logging.InstanceId(internalInstanceId.getId());
+
+ intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId);
+
ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
- view.setOnDragListener(getDraggedViewDragListener());
- return view.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
- View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE);
+ if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE)) {
+ onSystemDragStarted();
+
+ mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
+ .withInstanceId(launcherInstanceId)
+ .log(StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
+ }
}
- return false;
}
- /**
- * Hide the original Taskbar item while it is being dragged.
- */
- private View.OnDragListener getDraggedViewDragListener() {
- return (view, dragEvent) -> {
+ private void onSystemDragStarted() {
+ mIsSystemDragInProgress = true;
+ mActivity.getDragLayer().setOnDragListener((view, dragEvent) -> {
switch (dragEvent.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
- view.setVisibility(INVISIBLE);
+ // Return true to tell system we are interested in events, so we get DRAG_ENDED.
return true;
case DragEvent.ACTION_DRAG_ENDED:
- view.setVisibility(VISIBLE);
- view.setOnDragListener(null);
+ mIsSystemDragInProgress = false;
+ maybeOnDragEnd();
return true;
}
return false;
- };
+ });
+ }
+
+ @Override
+ public boolean isDragging() {
+ return super.isDragging() || mIsSystemDragInProgress;
+ }
+
+ private void maybeOnDragEnd() {
+ if (!isDragging()) {
+ ((BubbleTextView) mDragObject.originalView).getIcon().setIsDisabled(false);
+ mControllers.taskbarAutohideSuspendController.updateFlag(
+ TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false);
+ }
+ }
+
+ @Override
+ protected void callOnDragEnd() {
+ super.callOnDragEnd();
+ maybeOnDragEnd();
+ }
+
+ @Override
+ protected float getX(MotionEvent ev) {
+ // We will resize to fill the screen while dragging, so use screen coordinates. This ensures
+ // we start at the correct position even though touch down is on the smaller DragLayer size.
+ return ev.getRawX();
+ }
+
+ @Override
+ protected float getY(MotionEvent ev) {
+ // We will resize to fill the screen while dragging, so use screen coordinates. This ensures
+ // we start at the correct position even though touch down is on the smaller DragLayer size.
+ return ev.getRawY();
+ }
+
+ @Override
+ protected Point getClampedDragLayerPos(float x, float y) {
+ // No need to clamp, as we will take up the entire screen.
+ mTmpPoint.set(Math.round(x), Math.round(y));
+ return mTmpPoint;
+ }
+
+ @Override
+ protected void exitDrag() {
+ if (mDragObject != null) {
+ mActivity.getDragLayer().removeView(mDragObject.dragView);
+ }
+ }
+
+ @Override
+ public void addDropTarget(DropTarget target) {
+ // No-op as Taskbar currently doesn't support any drop targets internally.
+ // Note: if we do add internal DropTargets, we'll still need to ignore Folder.
+ }
+
+ @Override
+ protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
+ return null;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 45ec911527..b42a60ceea 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -18,14 +18,17 @@ package com.android.launcher3.taskbar;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Rect;
+import android.graphics.Path;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.systemui.shared.system.ViewTreeObserverWrapper;
@@ -37,14 +40,16 @@ import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInset
*/
public class TaskbarDragLayer extends BaseDragLayer {
- private final int mFolderMargin;
private final Paint mTaskbarBackgroundPaint;
-
- private TaskbarIconController.Callbacks mControllerCallbacks;
- private TaskbarView mTaskbarView;
-
+ private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
+ // Initialized in init.
+ private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
+ private float mLeftCornerRadius, mRightCornerRadius;
+
+ private float mTaskbarBackgroundOffset;
+
public TaskbarDragLayer(@NonNull Context context) {
this(context, null);
}
@@ -61,25 +66,47 @@ public class TaskbarDragLayer extends BaseDragLayer {
public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, 1 /* alphaChannelCount */);
- mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
mTaskbarBackgroundPaint = new Paint();
mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
+ mTaskbarBackgroundPaint.setAlpha(0);
+ mTaskbarBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+ mTaskbarBackgroundPaint.setStyle(Paint.Style.FILL);
+
+ // Will be set in init(), but this ensures they are always non-null.
+ mInvertedLeftCornerPath = new Path();
+ mInvertedRightCornerPath = new Path();
+ }
+
+ public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
+ mControllerCallbacks = callbacks;
+
+ // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
+ // square, and then subtracting out a circle from the appropriate corner.
+ mLeftCornerRadius = mActivity.getLeftCornerRadius();
+ mRightCornerRadius = mActivity.getRightCornerRadius();
+ Path square = new Path();
+ square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
+ Path circle = new Path();
+ circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
+ mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
+ square.reset();
+ square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
+ circle.reset();
+ circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
+ mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
+
recreateControllers();
}
@Override
public void recreateControllers() {
- mControllers = new TouchController[0];
- }
-
- public void init(TaskbarIconController.Callbacks callbacks, TaskbarView taskbarView) {
- mControllerCallbacks = callbacks;
- mTaskbarView = taskbarView;
+ mControllers = new TouchController[] {mActivity.getDragController()};
}
private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
if (mControllerCallbacks != null) {
mControllerCallbacks.updateInsetsTouchability(insetsInfo);
+ mControllerCallbacks.updateContentInsets(insetsInfo.contentInsets);
}
}
@@ -108,12 +135,6 @@ public class TaskbarDragLayer extends BaseDragLayer {
return true;
}
- public void updateImeBarVisibilityAlpha(float alpha) {
- if (mControllerCallbacks != null) {
- mControllerCallbacks.updateImeBarVisibilityAlpha(alpha);
- }
- }
-
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
@@ -124,21 +145,25 @@ public class TaskbarDragLayer extends BaseDragLayer {
@Override
protected void dispatchDraw(Canvas canvas) {
- canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(),
- canvas.getHeight(), mTaskbarBackgroundPaint);
+ float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
+ * (1f - mTaskbarBackgroundOffset);
+ canvas.save();
+ canvas.translate(0, canvas.getHeight() - backgroundHeight);
+
+ // Draw the background behind taskbar content.
+ canvas.drawRect(0, 0, canvas.getWidth(), backgroundHeight, mTaskbarBackgroundPaint);
+
+ // Draw the inverted rounded corners above the taskbar.
+ canvas.translate(0, -mLeftCornerRadius);
+ canvas.drawPath(mInvertedLeftCornerPath, mTaskbarBackgroundPaint);
+ canvas.translate(0, mLeftCornerRadius);
+ canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
+ canvas.drawPath(mInvertedRightCornerPath, mTaskbarBackgroundPaint);
+
+ canvas.restore();
super.dispatchDraw(canvas);
}
- /**
- * @return Bounds (in our coordinates) where an opened Folder can display.
- */
- protected Rect getFolderBoundingBox() {
- Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
- boundingBox.inset(mFolderMargin, mFolderMargin);
- return boundingBox;
- }
-
-
/**
* Sets the alpha of the background color behind all the Taskbar contents.
* @param alpha 0 is fully transparent, 1 is fully opaque.
@@ -147,4 +172,19 @@ public class TaskbarDragLayer extends BaseDragLayer {
mTaskbarBackgroundPaint.setAlpha((int) (alpha * 255));
invalidate();
}
+
+ /**
+ * Sets the translation of the background color behind all the Taskbar contents.
+ * @param offset 0 is fully onscreen, 1 is fully offscreen.
+ */
+ protected void setTaskbarBackgroundOffset(float offset) {
+ mTaskbarBackgroundOffset = offset;
+ invalidate();
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
+ return super.dispatchTouchEvent(ev);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
new file mode 100644
index 0000000000..a918016cc6
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_CONTENT;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
+
+import android.content.res.Resources;
+import android.graphics.Rect;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.quickstep.AnimatedFloat;
+import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
+
+/**
+ * Handles properties/data collection, then passes the results to TaskbarDragLayer to render.
+ */
+public class TaskbarDragLayerController {
+
+ private final TaskbarActivityContext mActivity;
+ private final TaskbarDragLayer mTaskbarDragLayer;
+ private final int mFolderMargin;
+
+ // Alpha properties for taskbar background.
+ private final AnimatedFloat mBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
+ private final AnimatedFloat mBgNavbar = new AnimatedFloat(this::updateBackgroundAlpha);
+ private final AnimatedFloat mKeyguardBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
+ private final AnimatedFloat mNotificationShadeBgTaskbar = new AnimatedFloat(
+ this::updateBackgroundAlpha);
+ private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
+ // Used to hide our background color when someone else (e.g. ScrimView) is handling it.
+ private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
+
+ // Translation property for taskbar background.
+ private final AnimatedFloat mBgOffset = new AnimatedFloat(this::updateBackgroundOffset);
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+ private AnimatedFloat mNavButtonDarkIntensityMultiplier;
+
+ private float mLastSetBackgroundAlpha;
+
+ public TaskbarDragLayerController(TaskbarActivityContext activity,
+ TaskbarDragLayer taskbarDragLayer) {
+ mActivity = activity;
+ mTaskbarDragLayer = taskbarDragLayer;
+ final Resources resources = mTaskbarDragLayer.getResources();
+ mFolderMargin = resources.getDimensionPixelSize(R.dimen.taskbar_folder_margin);
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
+
+ mNavButtonDarkIntensityMultiplier = mControllers.navbarButtonsViewController
+ .getNavButtonDarkIntensityMultiplier();
+
+ mBgTaskbar.value = 1;
+ mKeyguardBgTaskbar.value = 1;
+ mNotificationShadeBgTaskbar.value = 1;
+ mImeBgTaskbar.value = 1;
+ mBgOverride.value = 1;
+ updateBackgroundAlpha();
+ }
+
+ public void onDestroy() {
+ mTaskbarDragLayer.onDestroy();
+ }
+
+ /**
+ * @return Bounds (in TaskbarDragLayer coordinates) where an opened Folder can display.
+ */
+ public Rect getFolderBoundingBox() {
+ Rect boundingBox = new Rect(0, 0, mTaskbarDragLayer.getWidth(),
+ mTaskbarDragLayer.getHeight() - mActivity.getDeviceProfile().taskbarSize);
+ boundingBox.inset(mFolderMargin, mFolderMargin);
+ return boundingBox;
+ }
+
+ public AnimatedFloat getTaskbarBackgroundAlpha() {
+ return mBgTaskbar;
+ }
+
+ public AnimatedFloat getNavbarBackgroundAlpha() {
+ return mBgNavbar;
+ }
+
+ public AnimatedFloat getKeyguardBgTaskbar() {
+ return mKeyguardBgTaskbar;
+ }
+
+ public AnimatedFloat getNotificationShadeBgTaskbar() {
+ return mNotificationShadeBgTaskbar;
+ }
+
+ public AnimatedFloat getImeBgTaskbar() {
+ return mImeBgTaskbar;
+ }
+
+ public AnimatedFloat getOverrideBackgroundAlpha() {
+ return mBgOverride;
+ }
+
+ public AnimatedFloat getTaskbarBackgroundOffset() {
+ return mBgOffset;
+ }
+
+ private void updateBackgroundAlpha() {
+ final float bgNavbar = mBgNavbar.value;
+ final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
+ * mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value;
+ mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
+ mTaskbarDragLayer.setTaskbarBackgroundAlpha(mLastSetBackgroundAlpha);
+
+ updateNavBarDarkIntensityMultiplier();
+ }
+
+ private void updateBackgroundOffset() {
+ mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value);
+
+ updateNavBarDarkIntensityMultiplier();
+ }
+
+ private void updateNavBarDarkIntensityMultiplier() {
+ // Zero out the app-requested dark intensity when we're drawing our own background.
+ float effectiveBgAlpha = mLastSetBackgroundAlpha * (1 - mBgOffset.value);
+ mNavButtonDarkIntensityMultiplier.updateValue(1 - effectiveBgAlpha);
+ }
+
+ /**
+ * Callbacks for {@link TaskbarDragLayer} to interact with its controller.
+ */
+ public class TaskbarDragLayerCallbacks {
+
+ /**
+ * Called to update the touchable insets.
+ * @see InsetsInfo#setTouchableInsets(int)
+ */
+ public void updateInsetsTouchability(InsetsInfo insetsInfo) {
+ insetsInfo.touchableRegion.setEmpty();
+ // Always have nav buttons be touchable
+ mControllers.navbarButtonsViewController.addVisibleButtonsRegion(
+ mTaskbarDragLayer, insetsInfo.touchableRegion);
+ boolean insetsIsTouchableRegion = true;
+
+ if (mTaskbarDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
+ // Let touches pass through us.
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ } else if (mControllers.navbarButtonsViewController.isImeVisible()) {
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ } else if (!mControllers.uiController.isTaskbarTouchable()) {
+ // Let touches pass through us.
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ } else if (mControllers.taskbarViewController.areIconsVisible()
+ || AbstractFloatingView.getOpenView(mActivity, TYPE_ALL) != null) {
+ // Taskbar has some touchable elements, take over the full taskbar area
+ insetsInfo.setTouchableInsets(mActivity.isTaskbarWindowFullscreen()
+ ? TOUCHABLE_INSETS_FRAME : TOUCHABLE_INSETS_CONTENT);
+ insetsIsTouchableRegion = false;
+ } else {
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ }
+ mActivity.excludeFromMagnificationRegion(insetsIsTouchableRegion);
+ }
+
+ /**
+ * Called to update the {@link InsetsInfo#contentInsets}. This is reported to apps but our
+ * internal launcher will ignore these insets.
+ */
+ public void updateContentInsets(Rect outContentInsets) {
+ int contentHeight = mControllers.taskbarStashController
+ .getContentHeightToReportToApps();
+ outContentInsets.top = mTaskbarDragLayer.getHeight() - contentHeight;
+ }
+
+ /**
+ * Called when a child is removed from TaskbarDragLayer.
+ */
+ public void onDragLayerViewRemoved() {
+ if (AbstractFloatingView.getAnyView(mActivity, TYPE_ALL) == null) {
+ mActivity.setTaskbarWindowFullscreen(false);
+ }
+ }
+
+ /**
+ * Returns how tall the background should be drawn at the bottom of the screen.
+ */
+ public int getTaskbarBackgroundHeight() {
+ return mActivity.getDeviceProfile().taskbarSize;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java
new file mode 100644
index 0000000000..cf28eff0c2
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.DragView;
+
+/**
+ * A DragView drawn/used by the Taskbar. Note that this is only for the internal drag-and-drop,
+ * while the pre-drag is still in progress (i.e. when the long press popup is still open). After
+ * that ends, we switch to a system drag and drop view instead.
+ */
+public class TaskbarDragView extends DragView {
+ public TaskbarDragView(TaskbarActivityContext launcher, Drawable drawable, int registrationX,
+ int registrationY, float initialScale, float scaleOnDrop, float finalScaleDps) {
+ super(launcher, drawable, registrationX, registrationY, initialScale, scaleOnDrop,
+ finalScaleDps);
+ }
+
+ @Override
+ public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
+ Runnable onAnimationEnd = () -> {
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ mActivity.getDragLayer().removeView(this);
+ };
+
+ duration = Math.max(duration,
+ getResources().getInteger(R.integer.config_dropAnimMinDuration));
+
+ animate()
+ .translationX(toTouchX - mRegistrationX)
+ .translationY(toTouchY - mRegistrationY)
+ .scaleX(mScaleOnDrop)
+ .scaleY(mScaleOnDrop)
+ .withEndAction(onAnimationEnd)
+ .setDuration(duration)
+ .start();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
new file mode 100644
index 0000000000..fd5c2ea6b2
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Handles the Taskbar Education flow. */
+public class TaskbarEduController {
+
+ private static final long WAVE_ANIM_DELAY = 250;
+ private static final long WAVE_ANIM_STAGGER = 50;
+ private static final long WAVE_ANIM_EACH_ICON_DURATION = 633;
+ private static final long WAVE_ANIM_SLOT_MACHINE_DURATION = 1085;
+ // The fraction of each icon's animation at which we reach the top point of the wave.
+ private static final float WAVE_ANIM_FRACTION_TOP = 0.4f;
+ // The fraction of each icon's animation at which we reach the bottom, before overshooting.
+ private static final float WAVE_ANIM_FRACTION_BOTTOM = 0.9f;
+ private static final TimeInterpolator WAVE_ANIM_TO_TOP_INTERPOLATOR = FAST_OUT_SLOW_IN;
+ private static final TimeInterpolator WAVE_ANIM_TO_BOTTOM_INTERPOLATOR = ACCEL_2;
+ private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_INTERPOLATOR = DEACCEL;
+ private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR = ACCEL_DEACCEL;
+ private static final float WAVE_ANIM_ICON_SCALE = 1.2f;
+ // How many icons to cycle through in the slot machine (+ the original icon at each end).
+ private static final int WAVE_ANIM_SLOT_MACHINE_NUM_ICONS = 3;
+
+ private final TaskbarActivityContext mActivity;
+ private final float mWaveAnimTranslationY;
+ private final float mWaveAnimTranslationYReturnOvershoot;
+
+ // Initialized in init.
+ TaskbarControllers mControllers;
+
+ private TaskbarEduView mTaskbarEduView;
+ private Animator mAnim;
+
+ public TaskbarEduController(TaskbarActivityContext activity) {
+ mActivity = activity;
+
+ final Resources resources = activity.getResources();
+ mWaveAnimTranslationY = resources.getDimension(R.dimen.taskbar_edu_wave_anim_trans_y);
+ mWaveAnimTranslationYReturnOvershoot = resources.getDimension(
+ R.dimen.taskbar_edu_wave_anim_trans_y_return_overshoot);
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ }
+
+ void showEdu() {
+ mActivity.setTaskbarWindowFullscreen(true);
+ mActivity.getDragLayer().post(() -> {
+ mTaskbarEduView = (TaskbarEduView) mActivity.getLayoutInflater().inflate(
+ R.layout.taskbar_edu, mActivity.getDragLayer(), false);
+ mTaskbarEduView.init(new TaskbarEduCallbacks());
+ mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
+ mTaskbarEduView.show();
+ startAnim(createWaveAnim());
+ });
+ }
+
+ void hideEdu() {
+ if (mTaskbarEduView != null) {
+ mTaskbarEduView.close(true /* animate */);
+ }
+ }
+
+ /**
+ * Starts the given animation, ending the previous animation first if it's still playing.
+ */
+ private void startAnim(Animator anim) {
+ if (mAnim != null) {
+ mAnim.end();
+ }
+ mAnim = anim;
+ mAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnim = null;
+ }
+ });
+ mAnim.start();
+ }
+
+ /**
+ * Creates a staggered "wave" animation where each icon translates and scales up in succession.
+ */
+ private Animator createWaveAnim() {
+ AnimatorSet waveAnim = new AnimatorSet();
+ View[] icons = mControllers.taskbarViewController.getIconViews();
+ for (int i = 0; i < icons.length; i++) {
+ View icon = icons[i];
+ AnimatorSet iconAnim = new AnimatorSet();
+
+ Keyframe[] scaleKeyframes = new Keyframe[] {
+ Keyframe.ofFloat(0, 1f),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, WAVE_ANIM_ICON_SCALE),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 1f),
+ Keyframe.ofFloat(1f, 1f)
+ };
+ scaleKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
+ scaleKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
+
+ Keyframe[] translationYKeyframes = new Keyframe[] {
+ Keyframe.ofFloat(0, 0f),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, -mWaveAnimTranslationY),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 0f),
+ // Half of the remaining fraction overshoots, then the other half returns to 0.
+ Keyframe.ofFloat(
+ WAVE_ANIM_FRACTION_BOTTOM + (1 - WAVE_ANIM_FRACTION_BOTTOM) / 2f,
+ mWaveAnimTranslationYReturnOvershoot),
+ Keyframe.ofFloat(1f, 0f)
+ };
+ translationYKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
+ translationYKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
+ translationYKeyframes[3].setInterpolator(WAVE_ANIM_OVERSHOOT_INTERPOLATOR);
+ translationYKeyframes[4].setInterpolator(WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR);
+
+ iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
+ PropertyValuesHolder.ofKeyframe(SCALE_PROPERTY, scaleKeyframes))
+ .setDuration(WAVE_ANIM_EACH_ICON_DURATION));
+ iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
+ PropertyValuesHolder.ofKeyframe(View.TRANSLATION_Y, translationYKeyframes))
+ .setDuration(WAVE_ANIM_EACH_ICON_DURATION));
+
+ if (icon instanceof PredictedAppIcon) {
+ // Play slot machine animation through random icons from AllAppsList.
+ PredictedAppIcon predictedAppIcon = (PredictedAppIcon) icon;
+ ItemInfo itemInfo = (ItemInfo) icon.getTag();
+ List iconsToAnimate = mControllers.uiController.getAppIconsForEdu()
+ .filter(appInfo -> !TextUtils.equals(appInfo.title, itemInfo.title))
+ .map(appInfo -> appInfo.bitmap)
+ .filter(bitmap -> !bitmap.isNullOrLowRes())
+ .collect(Collectors.toList());
+ // Pick n icons at random.
+ Collections.shuffle(iconsToAnimate);
+ if (iconsToAnimate.size() > WAVE_ANIM_SLOT_MACHINE_NUM_ICONS) {
+ iconsToAnimate = iconsToAnimate.subList(0, WAVE_ANIM_SLOT_MACHINE_NUM_ICONS);
+ }
+ Animator slotMachineAnim = predictedAppIcon.createSlotMachineAnim(iconsToAnimate);
+ if (slotMachineAnim != null) {
+ iconAnim.play(slotMachineAnim.setDuration(WAVE_ANIM_SLOT_MACHINE_DURATION));
+ }
+ }
+
+ iconAnim.setStartDelay(WAVE_ANIM_STAGGER * i);
+ waveAnim.play(iconAnim);
+ }
+ waveAnim.setStartDelay(WAVE_ANIM_DELAY);
+ return waveAnim;
+ }
+
+ /**
+ * Callbacks for {@link TaskbarEduView} to interact with its controller.
+ */
+ class TaskbarEduCallbacks {
+ void onPageChanged(int currentPage, int pageCount) {
+ if (currentPage == 0) {
+ mTaskbarEduView.updateStartButton(R.string.taskbar_edu_close,
+ v -> mTaskbarEduView.close(true /* animate */));
+ } else {
+ mTaskbarEduView.updateStartButton(R.string.taskbar_edu_previous,
+ v -> mTaskbarEduView.snapToPage(currentPage - 1));
+ }
+ if (currentPage == pageCount - 1) {
+ mTaskbarEduView.updateEndButton(R.string.taskbar_edu_done,
+ v -> mTaskbarEduView.close(true /* animate */));
+ } else {
+ mTaskbarEduView.updateEndButton(R.string.taskbar_edu_next,
+ v -> mTaskbarEduView.snapToPage(currentPage + 1));
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduPagedView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduPagedView.java
new file mode 100644
index 0000000000..5efcc4df4c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduPagedView.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_EDUCATION_DIALOG;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.taskbar.TaskbarEduController.TaskbarEduCallbacks;
+import com.android.launcher3.views.ActivityContext;
+
+/** Horizontal carousel of tutorial screens for Taskbar Edu. */
+public class TaskbarEduPagedView extends PagedView {
+
+ private TaskbarEduView mTaskbarEduView;
+ private TaskbarEduCallbacks mControllerCallbacks;
+
+ public TaskbarEduPagedView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
+ void setTaskbarEduView(TaskbarEduView taskbarEduView) {
+ mTaskbarEduView = taskbarEduView;
+ mPageIndicator = taskbarEduView.findViewById(R.id.content_page_indicator);
+ initParentViews(taskbarEduView);
+ }
+
+ void setControllerCallbacks(TaskbarEduCallbacks controllerCallbacks) {
+ mControllerCallbacks = controllerCallbacks;
+ mControllerCallbacks.onPageChanged(getCurrentPage(), getPageCount());
+ }
+
+ @Override
+ protected int getChildGap() {
+ return mTaskbarEduView.getPaddingLeft() + mTaskbarEduView.getPaddingRight();
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ if (mMaxScroll > 0) {
+ mPageIndicator.setScroll(l, mMaxScroll);
+ }
+ }
+
+ @Override
+ protected void notifyPageSwitchListener(int prevPage) {
+ super.notifyPageSwitchListener(prevPage);
+ mControllerCallbacks.onPageChanged(getCurrentPage(), getPageCount());
+ }
+
+ @Override
+ protected boolean canScroll(float absVScroll, float absHScroll) {
+ return AbstractFloatingView.getTopOpenViewWithType(
+ ActivityContext.lookupContext(getContext()),
+ TYPE_ALL & ~TYPE_TASKBAR_EDUCATION_DIALOG) == null;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
new file mode 100644
index 0000000000..8525427a64
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Pair;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.R;
+import com.android.launcher3.views.AbstractSlideInView;
+
+/** Education view about the Taskbar. */
+public class TaskbarEduView extends AbstractSlideInView
+ implements Insettable {
+
+ private static final int DEFAULT_OPEN_DURATION = 500;
+ private static final int DEFAULT_CLOSE_DURATION = 200;
+
+ private final Rect mInsets = new Rect();
+
+ private Button mStartButton;
+ private Button mEndButton;
+ private TaskbarEduPagedView mPagedView;
+
+ public TaskbarEduView(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public TaskbarEduView(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ protected void init(TaskbarEduController.TaskbarEduCallbacks callbacks) {
+ if (mPagedView != null) {
+ mPagedView.setControllerCallbacks(callbacks);
+ }
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ handleClose(animate, DEFAULT_CLOSE_DURATION);
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_TASKBAR_EDUCATION_DIALOG) != 0;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mContent = findViewById(R.id.edu_view);
+ mStartButton = findViewById(R.id.edu_start_button);
+ mEndButton = findViewById(R.id.edu_end_button);
+ mPagedView = findViewById(R.id.content);
+ mPagedView.setTaskbarEduView(this);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ mContent.setPadding(mContent.getPaddingStart(),
+ mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom);
+ }
+
+ @Override
+ protected void attachToContainer() {
+ if (mColorScrim != null) {
+ getPopupContainer().addView(mColorScrim, 0);
+ }
+ getPopupContainer().addView(this, 1);
+ }
+
+ /** Show the Education flow. */
+ public void show() {
+ attachToContainer();
+ animateOpen();
+ }
+
+ @Override
+ protected Pair getAccessibilityTarget() {
+ return Pair.create(mContent, mIsOpen ? getContext().getString(R.string.taskbar_edu_opened)
+ : getContext().getString(R.string.taskbar_edu_closed));
+ }
+
+ @Override
+ protected int getScrimColor(Context context) {
+ return context.getResources().getColor(R.color.widgets_picker_scrim);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int width = r - l;
+ int height = b - t;
+
+ // Lay out the content as center bottom aligned.
+ int contentWidth = mContent.getMeasuredWidth();
+ int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
+ mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
+ contentLeft + contentWidth, height);
+
+ setTranslationShift(mTranslationShift);
+ }
+
+ private void animateOpen() {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
+ mOpenCloseAnimator.setDuration(DEFAULT_OPEN_DURATION).start();
+ }
+
+ void snapToPage(int page) {
+ mPagedView.snapToPage(page);
+ }
+
+ void updateStartButton(int textResId, OnClickListener onClickListener) {
+ mStartButton.setText(textResId);
+ mStartButton.setOnClickListener(onClickListener);
+ }
+
+ void updateEndButton(int textResId, OnClickListener onClickListener) {
+ mEndButton.setText(textResId);
+ mEndButton.setOnClickListener(onClickListener);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
deleted file mode 100644
index 91cf7efab5..0000000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import android.view.View;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.DropTarget;
-import com.android.launcher3.Hotseat;
-import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.data.ItemInfo;
-
-import java.util.function.Consumer;
-
-/**
- * Works with TaskbarController to update the TaskbarView's Hotseat items.
- */
-public class TaskbarHotseatController {
-
- private final BaseQuickstepLauncher mLauncher;
- private final Hotseat mHotseat;
- private final Consumer mTaskbarCallbacks;
- private final int mNumHotseatIcons;
-
- private final DragController.DragListener mDragListener = new DragController.DragListener() {
- @Override
- public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { }
-
- @Override
- public void onDragEnd() {
- onHotseatUpdated();
- }
- };
-
- public TaskbarHotseatController(
- BaseQuickstepLauncher launcher, Consumer taskbarCallbacks) {
- mLauncher = launcher;
- mHotseat = mLauncher.getHotseat();
- mTaskbarCallbacks = taskbarCallbacks;
- mNumHotseatIcons = mLauncher.getDeviceProfile().numShownHotseatIcons;
- }
-
- protected void init() {
- mLauncher.getDragController().addDragListener(mDragListener);
- onHotseatUpdated();
- }
-
- protected void cleanup() {
- mLauncher.getDragController().removeDragListener(mDragListener);
- }
-
- /**
- * Called when any Hotseat item changes, and reports the new list of items to TaskbarController.
- */
- protected void onHotseatUpdated() {
- ShortcutAndWidgetContainer shortcutsAndWidgets = mHotseat.getShortcutsAndWidgets();
- ItemInfo[] hotseatItemInfos = new ItemInfo[mNumHotseatIcons];
- for (int i = 0; i < shortcutsAndWidgets.getChildCount(); i++) {
- View child = shortcutsAndWidgets.getChildAt(i);
- Object tag = shortcutsAndWidgets.getChildAt(i).getTag();
- if (tag instanceof ItemInfo) {
- ItemInfo itemInfo = (ItemInfo) tag;
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
- // Since the hotseat might be laid out vertically or horizontally, use whichever
- // index is higher.
- int index = Math.max(lp.cellX, lp.cellY);
- if (0 <= index && index < hotseatItemInfos.length) {
- hotseatItemInfos[index] = itemInfo;
- }
- }
- }
-
- mTaskbarCallbacks.accept(hotseatItemInfos);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
deleted file mode 100644
index 683a5b9fc4..0000000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
-
-import android.graphics.Rect;
-import android.inputmethodservice.InputMethodService;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
-
-/**
- * Controller for taskbar icon UI
- */
-public class TaskbarIconController {
-
- private final Rect mTempRect = new Rect();
-
- private final TaskbarActivityContext mActivity;
- private final TaskbarDragLayer mDragLayer;
-
- private final TaskbarView mTaskbarView;
- private final ImeBarView mImeBarView;
-
- @NonNull
- private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
-
- TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
- mActivity = activity;
- mDragLayer = dragLayer;
- mTaskbarView = mDragLayer.findViewById(R.id.taskbar_view);
- mImeBarView = mDragLayer.findViewById(R.id.ime_bar_view);
- }
-
- public void init(OnClickListener clickListener, OnLongClickListener longClickListener) {
- mDragLayer.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) ->
- mUIController.alignRealHotseatWithTaskbar());
-
- ButtonProvider buttonProvider = new ButtonProvider(mActivity);
- mImeBarView.init(buttonProvider);
- mTaskbarView.construct(clickListener, longClickListener, buttonProvider);
- mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
-
- mDragLayer.init(new Callbacks(), mTaskbarView);
- }
-
- public void onDestroy() {
- mDragLayer.onDestroy();
- }
-
- public void setUIController(@NonNull TaskbarUIController uiController) {
- mUIController = uiController;
- }
-
- /**
- * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
- * instantiating at all, which is what's responsible for sending sysui state flags over.
- *
- * @param vis IME visibility flag
- */
- public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
- if (displayId != mActivity.getDisplayId() || !mActivity.canShowNavButtons()) {
- return;
- }
-
- mImeBarView.setImeSwitcherVisibility(showImeSwitcher);
- setImeIsVisible((vis & InputMethodService.IME_VISIBLE) != 0);
- }
-
- /**
- * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
- */
- public void setImeIsVisible(boolean isImeVisible) {
- mTaskbarView.setTouchesEnabled(!isImeVisible);
- mUIController.onImeVisible(mDragLayer, isImeVisible);
- }
-
- /**
- * Callbacks for {@link TaskbarDragLayer} to interact with the icon controller
- */
- public class Callbacks {
-
- /**
- * Called to update the touchable insets
- */
- public void updateInsetsTouchability(InsetsInfo insetsInfo) {
- insetsInfo.touchableRegion.setEmpty();
- if (mDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
- // Let touches pass through us.
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- } else if (mImeBarView.getVisibility() == VISIBLE) {
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
- } else if (!mUIController.isTaskbarTouchable()) {
- // Let touches pass through us.
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- } else if (mTaskbarView.areIconsVisible()) {
- // Buttons are visible, take over the full taskbar area
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
- } else {
- if (mTaskbarView.mSystemButtonContainer.getVisibility() == VISIBLE) {
- mDragLayer.getDescendantRectRelativeToSelf(
- mTaskbarView.mSystemButtonContainer, mTempRect);
- insetsInfo.touchableRegion.set(mTempRect);
- }
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- }
-
- // TaskbarContainerView provides insets to other apps based on contentInsets. These
- // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
- // to show a floating view like Folder. Thus, we set the contentInsets to be where
- // mTaskbarView is, since its position never changes and insets rather than overlays.
- insetsInfo.contentInsets.left = mTaskbarView.getLeft();
- insetsInfo.contentInsets.top = mTaskbarView.getTop();
- insetsInfo.contentInsets.right = mDragLayer.getWidth() - mTaskbarView.getRight();
- insetsInfo.contentInsets.bottom = mDragLayer.getHeight() - mTaskbarView.getBottom();
- }
-
- public void onDragLayerViewRemoved() {
- int count = mDragLayer.getChildCount();
- // Ensure no other children present (like Folders, etc)
- for (int i = 0; i < count; i++) {
- View v = mDragLayer.getChildAt(i);
- if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))) {
- return;
- }
- }
- mActivity.setTaskbarWindowFullscreen(false);
- }
-
- public void updateImeBarVisibilityAlpha(float alpha) {
- if (!mActivity.canShowNavButtons()) {
- // TODO Remove sysui IME bar for gesture nav as well
- return;
- }
- mImeBarView.setAlpha(alpha);
- mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE);
- }
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
new file mode 100644
index 0000000000..5fc0695564
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
@@ -0,0 +1,98 @@
+package com.android.launcher3.taskbar;
+
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+
+import android.app.KeyguardManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+/**
+ * Controller for managing keyguard state for taskbar
+ */
+public class TaskbarKeyguardController {
+
+ private static final int KEYGUARD_SYSUI_FLAGS = SYSUI_STATE_BOUNCER_SHOWING |
+ SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_DEVICE_DOZING |
+ SYSUI_STATE_OVERVIEW_DISABLED | SYSUI_STATE_HOME_DISABLED |
+ SYSUI_STATE_BACK_DISABLED | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+
+ private final TaskbarActivityContext mContext;
+ private int mKeyguardSysuiFlags;
+ private boolean mBouncerShowing;
+ private NavbarButtonsViewController mNavbarButtonsViewController;
+ private final KeyguardManager mKeyguardManager;
+ private boolean mIsScreenOff;
+
+ private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mIsScreenOff = true;
+ }
+ };
+
+ public TaskbarKeyguardController(TaskbarActivityContext context) {
+ mContext = context;
+ mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+ }
+
+ public void init(NavbarButtonsViewController navbarButtonUIController) {
+ mNavbarButtonsViewController = navbarButtonUIController;
+ mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ }
+
+ public void updateStateForSysuiFlags(int systemUiStateFlags) {
+ boolean bouncerShowing = (systemUiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0;
+ boolean keyguardShowing = (systemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING)
+ != 0;
+ boolean keyguardOccluded =
+ (systemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0;
+ boolean dozing = (systemUiStateFlags & SYSUI_STATE_DEVICE_DOZING) != 0;
+
+ int interestingKeyguardFlags = systemUiStateFlags & KEYGUARD_SYSUI_FLAGS;
+ if (interestingKeyguardFlags == mKeyguardSysuiFlags) {
+ return;
+ }
+ mKeyguardSysuiFlags = interestingKeyguardFlags;
+
+ mBouncerShowing = bouncerShowing;
+
+ mNavbarButtonsViewController.setKeyguardVisible(keyguardShowing || dozing,
+ keyguardOccluded);
+ updateIconsForBouncer();
+ }
+
+ public boolean isScreenOff() {
+ return mIsScreenOff;
+ }
+
+ public void setScreenOn() {
+ mIsScreenOff = false;
+ }
+
+ /**
+ * Hides/shows taskbar when keyguard is up
+ */
+ private void updateIconsForBouncer() {
+ boolean disableBack = (mKeyguardSysuiFlags & SYSUI_STATE_BACK_DISABLED) != 0;
+ boolean disableRecent = (mKeyguardSysuiFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
+ boolean disableHome = (mKeyguardSysuiFlags & SYSUI_STATE_HOME_DISABLED) != 0;
+ boolean onlyBackEnabled = !disableBack && disableRecent && disableHome;
+
+ boolean showBackForBouncer = onlyBackEnabled &&
+ mKeyguardManager.isDeviceSecure() &&
+ mBouncerShowing;
+ mNavbarButtonsViewController.setBackForBouncer(showBackForBouncer);
+ }
+
+ public void onDestroy() {
+ mContext.unregisterReceiver(mScreenOffReceiver);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
new file mode 100644
index 0000000000..7a50d0bf63
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
+import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
+import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.util.HashMap;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate
+ * the task bar accordingly.
+ */
+ public class TaskbarLauncherStateController {
+
+ public static final int FLAG_RESUMED = 1 << 0;
+ public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1;
+ public static final int FLAG_TRANSITION_STATE_RUNNING = 1 << 2;
+
+ /** Equivalent to an int with all 1s for binary operation purposes */
+ private static final int FLAGS_ALL = ~0;
+
+ private final AnimatedFloat mIconAlignmentForResumedState =
+ new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
+ private final AnimatedFloat mIconAlignmentForGestureState =
+ new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
+ private final AnimatedFloat mIconAlignmentForLauncherState =
+ new AnimatedFloat(this::onIconAlignmentRatioChangedForStateTransition);
+
+ private TaskbarControllers mControllers;
+ private AnimatedFloat mTaskbarBackgroundAlpha;
+ private MultiValueAlpha.AlphaProperty mIconAlphaForHome;
+ private BaseQuickstepLauncher mLauncher;
+
+ private Integer mPrevState;
+ private int mState;
+ private LauncherState mLauncherState = LauncherState.NORMAL;
+
+ private boolean mIsAnimatingToLauncherViaGesture;
+ private boolean mIsAnimatingToLauncherViaResume;
+
+ private final StateManager.StateListener mStateListener =
+ new StateManager.StateListener() {
+
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {
+ if (toState != mLauncherState) {
+ // Treat FLAG_TRANSITION_STATE_RUNNING as a changed flag even if a previous
+ // state transition was already running, so we update the new target.
+ mPrevState &= ~FLAG_TRANSITION_STATE_RUNNING;
+ mLauncherState = toState;
+ }
+ updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, true);
+ applyState();
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ mLauncherState = finalState;
+ updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, false);
+ applyState();
+ }
+ };
+
+ public void init(TaskbarControllers controllers, BaseQuickstepLauncher launcher) {
+ mControllers = controllers;
+ mLauncher = launcher;
+
+ mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
+ .getTaskbarBackgroundAlpha();
+ MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
+ mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
+ mIconAlphaForHome.setConsumer(
+ (Consumer) alpha -> mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1));
+
+ mIconAlignmentForResumedState.finishAnimation();
+ onIconAlignmentRatioChangedForAppAndHomeTransition();
+
+ mLauncher.getStateManager().addStateListener(mStateListener);
+
+ // Initialize to the current launcher state
+ updateStateForFlag(FLAG_RESUMED, launcher.hasBeenResumed());
+ mLauncherState = launcher.getStateManager().getState();
+ applyState(0);
+ }
+
+ public void onDestroy() {
+ mIconAlignmentForResumedState.finishAnimation();
+ mIconAlignmentForGestureState.finishAnimation();
+ mIconAlignmentForLauncherState.finishAnimation();
+
+ mIconAlphaForHome.setConsumer(null);
+ mLauncher.getHotseat().setIconsAlpha(1f);
+ mLauncher.getStateManager().removeStateListener(mStateListener);
+ }
+
+ public Animator createAnimToLauncher(@NonNull LauncherState toState,
+ @NonNull RecentsAnimationCallbacks callbacks, long duration) {
+ // If going to overview, stash the task bar
+ // If going home, align the icons to hotseat
+ AnimatorSet animatorSet = new AnimatorSet();
+
+ // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
+ TaskbarStashController stashController = mControllers.taskbarStashController;
+ stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
+ toState.isTaskbarStashed(mLauncher));
+ stashController.updateStateForFlag(FLAG_IN_APP, false);
+
+ updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, true);
+ animatorSet.play(stashController.applyStateWithoutStart(duration));
+ animatorSet.play(applyState(duration, false));
+
+ TaskBarRecentsAnimationListener listener = new TaskBarRecentsAnimationListener(callbacks);
+ callbacks.addListener(listener);
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ recentsView.setTaskLaunchListener(() -> {
+ listener.endGestureStateOverride(true);
+ callbacks.removeListener(listener);
+ });
+ return animatorSet;
+ }
+
+ public boolean isAnimatingToLauncher() {
+ return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture;
+ }
+
+ /**
+ * Updates the proper flag to change the state of the task bar.
+ *
+ * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
+ *
+ * @param flag The flag to update.
+ * @param enabled Whether to enable the flag
+ */
+ public void updateStateForFlag(int flag, boolean enabled) {
+ if (enabled) {
+ mState |= flag;
+ } else {
+ mState &= ~flag;
+ }
+ }
+
+ private boolean hasAnyFlag(int flagMask) {
+ return hasAnyFlag(mState, flagMask);
+ }
+
+ private boolean hasAnyFlag(int flags, int flagMask) {
+ return (flags & flagMask) != 0;
+ }
+
+ public void applyState() {
+ applyState(TASKBAR_STASH_DURATION);
+ }
+
+ public void applyState(long duration) {
+ applyState(duration, true);
+ }
+
+ public Animator applyState(boolean start) {
+ return applyState(TASKBAR_STASH_DURATION, start);
+ }
+
+ public Animator applyState(long duration, boolean start) {
+ Animator animator = null;
+ if (mPrevState == null || mPrevState != mState) {
+ // If this is our initial state, treat all flags as changed.
+ int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState;
+ mPrevState = mState;
+ animator = onStateChangeApplied(changedFlags, duration, start);
+ }
+ return animator;
+ }
+
+ private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ if (hasAnyFlag(changedFlags, FLAG_RESUMED)) {
+ boolean isResumed = isResumed();
+ ObjectAnimator anim = mIconAlignmentForResumedState
+ .animateToValue(isResumed && goingToUnstashedLauncherState()
+ ? 1 : 0)
+ .setDuration(duration);
+
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsAnimatingToLauncherViaResume = false;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIsAnimatingToLauncherViaResume = isResumed;
+
+ TaskbarStashController stashController = mControllers.taskbarStashController;
+ stashController.updateStateForFlag(FLAG_IN_APP, !isResumed);
+ stashController.applyState(duration);
+ }
+ });
+ animatorSet.play(anim);
+ }
+
+ if (hasAnyFlag(changedFlags, FLAG_RECENTS_ANIMATION_RUNNING)) {
+ boolean isRecentsAnimationRunning = isRecentsAnimationRunning();
+ Animator animator = mIconAlignmentForGestureState
+ .animateToValue(isRecentsAnimationRunning && goingToUnstashedLauncherState()
+ ? 1 : 0);
+ if (isRecentsAnimationRunning) {
+ animator.setDuration(duration);
+ }
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsAnimatingToLauncherViaGesture = false;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIsAnimatingToLauncherViaGesture = isRecentsAnimationRunning();
+ }
+ });
+ animatorSet.play(animator);
+ }
+
+ if (hasAnyFlag(changedFlags, FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING)) {
+ boolean goingToLauncher = hasAnyFlag(FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING);
+ animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(goingToLauncher ? 0 : 1)
+ .setDuration(duration));
+ }
+
+ if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_RUNNING)) {
+ boolean committed = !hasAnyFlag(FLAG_TRANSITION_STATE_RUNNING);
+ playStateTransitionAnim(animatorSet, duration, committed);
+
+ if (committed && mLauncherState == LauncherState.QUICK_SWITCH) {
+ // We're about to be paused, set immediately to ensure seamless handoff.
+ updateStateForFlag(FLAG_RESUMED, false);
+ applyState(0 /* duration */);
+ }
+ }
+
+ if (start) {
+ animatorSet.start();
+ }
+ return animatorSet;
+ }
+
+ /** Returns whether we're going to a state where taskbar icons should align with launcher. */
+ private boolean goingToUnstashedLauncherState() {
+ return !mControllers.taskbarStashController.isInStashedLauncherState();
+ }
+
+ private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
+ boolean committed) {
+ boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
+ float toAlignment = mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) ? 1 : 0;
+
+ TaskbarStashController controller = mControllers.taskbarStashController;
+ controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
+ Animator stashAnimator = controller.applyStateWithoutStart(duration);
+ if (stashAnimator != null) {
+ stashAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (isInStashedState && committed) {
+ // Reset hotseat alpha to default
+ mLauncher.getHotseat().setIconsAlpha(1);
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mLauncher.getHotseat().getIconsAlpha() > 0) {
+ mIconAlphaForHome.setValue(mLauncher.getHotseat().getIconsAlpha());
+ }
+ }
+ });
+ animatorSet.play(stashAnimator);
+ }
+
+ animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
+ .setDuration(duration));
+ }
+
+ private boolean isResumed() {
+ return (mState & FLAG_RESUMED) != 0;
+ }
+
+ private boolean isRecentsAnimationRunning() {
+ return (mState & FLAG_RECENTS_ANIMATION_RUNNING) != 0;
+ }
+
+ private void onIconAlignmentRatioChangedForStateTransition() {
+ if (!isResumed()) {
+ return;
+ }
+ onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioForLauncherState);
+ }
+
+ private void onIconAlignmentRatioChangedForAppAndHomeTransition() {
+ onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioBetweenAppAndHome);
+ }
+
+ private void onIconAlignmentRatioChanged(Supplier alignmentSupplier) {
+ if (mControllers == null) {
+ return;
+ }
+ float alignment = alignmentSupplier.get();
+ mControllers.taskbarViewController.setLauncherIconAlignment(
+ alignment, mLauncher.getDeviceProfile());
+
+ // Switch taskbar and hotseat in last frame
+ setTaskbarViewVisible(alignment < 1);
+ }
+
+ private float getCurrentIconAlignmentRatioBetweenAppAndHome() {
+ return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
+ }
+
+ private float getCurrentIconAlignmentRatioForLauncherState() {
+ return mIconAlignmentForLauncherState.value;
+ }
+
+ private void setTaskbarViewVisible(boolean isVisible) {
+ mIconAlphaForHome.setValue(isVisible ? 1 : 0);
+ }
+
+ private final class TaskBarRecentsAnimationListener implements
+ RecentsAnimationCallbacks.RecentsAnimationListener {
+ private final RecentsAnimationCallbacks mCallbacks;
+
+ TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public void onRecentsAnimationCanceled(HashMap thumbnailDatas) {
+ endGestureStateOverride(true);
+ }
+
+ @Override
+ public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ endGestureStateOverride(!controller.getFinishTargetIsLauncher());
+ }
+
+ private void endGestureStateOverride(boolean finishedToApp) {
+ mCallbacks.removeListener(this);
+
+ // Update the resumed state immediately to ensure a seamless handoff
+ boolean launcherResumed = !finishedToApp;
+ updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, false);
+ updateStateForFlag(FLAG_RESUMED, launcherResumed);
+ applyState();
+ // Set this last because applyState() might also animate it.
+ mIconAlignmentForResumedState.cancelAnimation();
+ mIconAlignmentForResumedState.updateValue(launcherResumed ? 1 : 0);
+
+ TaskbarStashController controller = mControllers.taskbarStashController;
+ controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
+ controller.applyState();
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 8e2e624878..2b515702e5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -16,18 +16,24 @@
package com.android.launcher3.taskbar;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
-import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
import android.view.Display;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseQuickstepLauncher;
@@ -35,25 +41,47 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.quickstep.RecentsActivity;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
/**
- * Class to manager taskbar lifecycle
+ * Class to manage taskbar lifecycle
*/
public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
SysUINavigationMode.NavigationModeChangeListener {
+ private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
+ Settings.Secure.USER_SETUP_COMPLETE);
+
private final Context mContext;
private final DisplayController mDisplayController;
private final SysUINavigationMode mSysUINavigationMode;
private final TaskbarNavButtonController mNavButtonController;
+ private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
+ private final ComponentCallbacks mComponentCallbacks;
+ private final SimpleBroadcastReceiver mShutdownReceiver;
+
+ // The source for this provider is set when Launcher is available
+ private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
+ new ScopedUnfoldTransitionProgressProvider();
private TaskbarActivityContext mTaskbarActivityContext;
- private BaseQuickstepLauncher mLauncher;
+ private StatefulActivity mActivity;
+ /**
+ * Cache a copy here so we can initialize state whenever taskbar is recreated, since
+ * this class does not get re-initialized w/ new taskbars.
+ */
+ private final TaskbarSharedState mSharedState = new TaskbarSharedState();
private static final int CHANGE_FLAGS =
CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS;
@@ -66,12 +94,43 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
Display display =
service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
mContext = Utilities.ATLEAST_S
- ? service.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null)
+ ? service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
: null;
- mNavButtonController = new TaskbarNavButtonController(service);
+ mNavButtonController = new TaskbarNavButtonController(service,
+ SystemUiProxy.INSTANCE.get(mContext), new Handler());
+ mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
+ mComponentCallbacks = new ComponentCallbacks() {
+ private Configuration mOldConfig = mContext.getResources().getConfiguration();
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ int configDiff = mOldConfig.diff(newConfig);
+ int configsRequiringRecreate = ActivityInfo.CONFIG_ASSETS_PATHS
+ | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_UI_MODE;
+ if ((configDiff & configsRequiringRecreate) != 0) {
+ // Color has changed, recreate taskbar to reload background color & icons.
+ recreateTaskbar();
+ } else {
+ // Config change might be handled without re-creating the taskbar
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.onConfigurationChanged(configDiff);
+ }
+ }
+ mOldConfig = newConfig;
+ }
+
+ @Override
+ public void onLowMemory() { }
+ };
+ mShutdownReceiver = new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
mDisplayController.addChangeListener(this);
mSysUINavigationMode.addModeChangeListener(this);
+ SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
+ mUserSetupCompleteListener);
+ mContext.registerComponentCallbacks(mComponentCallbacks);
+ mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
+
recreateTaskbar();
}
@@ -103,61 +162,120 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
}
/**
- * Sets or clears a launcher to act as taskbar callback
+ * Sets a {@link StatefulActivity} to act as taskbar callback
*/
- public void setLauncher(@Nullable BaseQuickstepLauncher launcher) {
- mLauncher = launcher;
+ public void setActivity(@NonNull StatefulActivity activity) {
+ mActivity = activity;
+ mUnfoldProgressProvider.setSourceProvider(getUnfoldTransitionProgressProviderForActivity(
+ activity));
+
if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setUIController(mLauncher == null
- ? TaskbarUIController.DEFAULT
- : new LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
+ mTaskbarActivityContext.setUIController(
+ createTaskbarUIControllerForActivity(mActivity));
+ }
+ }
+
+ /**
+ * Returns an {@link UnfoldTransitionProgressProvider} to use while the given StatefulActivity
+ * is active.
+ */
+ private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
+ StatefulActivity activity) {
+ if (activity instanceof BaseQuickstepLauncher) {
+ return ((BaseQuickstepLauncher) activity).getUnfoldTransitionProgressProvider();
+ }
+ return null;
+ }
+
+ /**
+ * Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active.
+ */
+ private TaskbarUIController createTaskbarUIControllerForActivity(StatefulActivity activity) {
+ if (activity instanceof BaseQuickstepLauncher) {
+ return new LauncherTaskbarUIController((BaseQuickstepLauncher) activity);
+ }
+ if (activity instanceof RecentsActivity) {
+ return new FallbackTaskbarUIController((RecentsActivity) activity);
+ }
+ return TaskbarUIController.DEFAULT;
+ }
+
+ /**
+ * Clears a previously set {@link StatefulActivity}
+ */
+ public void clearActivity(@NonNull StatefulActivity activity) {
+ if (mActivity == activity) {
+ mActivity = null;
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
+ }
+ mUnfoldProgressProvider.setSourceProvider(null);
}
}
private void recreateTaskbar() {
destroyExistingTaskbar();
- if (!FeatureFlags.ENABLE_TASKBAR.get()) {
+
+ DeviceProfile dp =
+ mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
+
+ boolean isTaskBarEnabled =
+ FeatureFlags.ENABLE_TASKBAR.get() && dp != null && dp.isTaskbarPresent;
+
+ if (!isTaskBarEnabled) {
+ SystemUiProxy.INSTANCE.get(mContext)
+ .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
return;
}
- if (!mUserUnlocked) {
- return;
- }
- DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
- if (!dp.isTaskbarPresent) {
- return;
- }
- mTaskbarActivityContext = new TaskbarActivityContext(
- mContext, dp.copy(mContext), mNavButtonController);
- mTaskbarActivityContext.init();
- if (mLauncher != null) {
+
+ mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp.copy(mContext),
+ mNavButtonController, mUnfoldProgressProvider);
+
+ mTaskbarActivityContext.init(mSharedState);
+ if (mActivity != null) {
mTaskbarActivityContext.setUIController(
- new LauncherTaskbarUIController(mLauncher, mTaskbarActivityContext));
+ createTaskbarUIControllerForActivity(mActivity));
}
}
- /**
- * See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags}
- * @param systemUiStateFlags The latest SystemUiStateFlags
- */
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
- boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
+ mSharedState.sysuiStateFlags = systemUiStateFlags;
if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setImeIsVisible(isImeVisible);
+ mTaskbarActivityContext.updateSysuiStateFlags(systemUiStateFlags, false /* fromInit */);
}
}
/**
- * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
- * instantiating at all, which is what's responsible for sending sysui state flags over.
- *
- * @param vis IME visibility flag
- * @param backDisposition Used to determine back button behavior for software keyboard
- * See BACK_DISPOSITION_* constants in {@link InputMethodService}
+ * Sets the flag indicating setup UI is visible
*/
- public void updateImeStatus(int displayId, int vis, int backDisposition,
- boolean showImeSwitcher) {
+ public void setSetupUIVisible(boolean isVisible) {
+ mSharedState.setupUIVisible = isVisible;
if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.updateImeStatus(displayId, vis, showImeSwitcher);
+ mTaskbarActivityContext.setSetupUIVisible(isVisible);
+ }
+ }
+
+ public void onRotationProposal(int rotation, boolean isValid) {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.onRotationProposal(rotation, isValid);
+ }
+ }
+
+ public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.disableNavBarElements(displayId, state1, state2, animate);
+ }
+ }
+
+ public void onSystemBarAttributesChanged(int displayId, int behavior) {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.onSystemBarAttributesChanged(displayId, behavior);
+ }
+ }
+
+ public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.onNavButtonsDarkIntensityChanged(darkIntensity);
}
}
@@ -168,5 +286,13 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
destroyExistingTaskbar();
mDisplayController.removeChangeListener(this);
mSysUINavigationMode.removeModeChangeListener(this);
+ SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
+ mUserSetupCompleteListener);
+ mContext.unregisterComponentCallbacks(mComponentCallbacks);
+ mContext.unregisterReceiver(mShutdownReceiver);
+ }
+
+ public @Nullable TaskbarActivityContext getCurrentActivityContext() {
+ return mTaskbarActivityContext;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
new file mode 100644
index 0000000000..37a9b5e5b2
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.util.SparseArray;
+import android.view.View;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Launcher model Callbacks for rendering taskbar.
+ */
+public class TaskbarModelCallbacks implements
+ BgDataModel.Callbacks, LauncherBindableItemsContainer {
+
+ private final SparseArray mHotseatItems = new SparseArray<>();
+ private List mPredictedItems = Collections.emptyList();
+
+ private final TaskbarActivityContext mContext;
+ private final TaskbarView mContainer;
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+
+ private boolean mBindInProgress = false;
+
+ public TaskbarModelCallbacks(
+ TaskbarActivityContext context, TaskbarView container) {
+ mContext = context;
+ mContainer = container;
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ }
+
+ @Override
+ public void startBinding() {
+ mBindInProgress = true;
+ mHotseatItems.clear();
+ mPredictedItems = Collections.emptyList();
+ }
+
+ @Override
+ public void finishBindingItems(IntSet pagesBoundFirst) {
+ mBindInProgress = false;
+ commitItemsToUI();
+ }
+
+ @Override
+ public void bindAppsAdded(IntArray newScreens, ArrayList addNotAnimated,
+ ArrayList addAnimated) {
+ boolean add1 = handleItemsAdded(addNotAnimated);
+ boolean add2 = handleItemsAdded(addAnimated);
+ if (add1 || add2) {
+ commitItemsToUI();
+ }
+ }
+
+ @Override
+ public void bindItems(List shortcuts, boolean forceAnimateIcons) {
+ if (handleItemsAdded(shortcuts)) {
+ commitItemsToUI();
+ }
+ }
+
+ private boolean handleItemsAdded(List items) {
+ boolean modified = false;
+ for (ItemInfo item : items) {
+ if (item.container == Favorites.CONTAINER_HOTSEAT) {
+ mHotseatItems.put(item.screenId, item);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+
+ @Override
+ public void bindWorkspaceItemsChanged(List updated) {
+ updateWorkspaceItems(updated, mContext);
+ }
+
+ @Override
+ public void bindRestoreItemsChange(HashSet updates) {
+ updateRestoreItems(updates, mContext);
+ }
+
+ @Override
+ public void mapOverItems(ItemOperator op) {
+ final int itemCount = mContainer.getChildCount();
+ for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
+ View item = mContainer.getChildAt(itemIdx);
+ if (op.evaluate((ItemInfo) item.getTag(), item)) {
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) {
+ if (handleItemsRemoved(matcher)) {
+ commitItemsToUI();
+ }
+ }
+
+ private boolean handleItemsRemoved(ItemInfoMatcher matcher) {
+ boolean modified = false;
+ for (int i = mHotseatItems.size() - 1; i >= 0; i--) {
+ if (matcher.matchesInfo(mHotseatItems.valueAt(i))) {
+ modified = true;
+ mHotseatItems.removeAt(i);
+ }
+ }
+ return modified;
+ }
+
+ @Override
+ public void bindItemsModified(List items) {
+ boolean removed = handleItemsRemoved(ItemInfoMatcher.ofItems(items));
+ boolean added = handleItemsAdded(items);
+ if (removed || added) {
+ commitItemsToUI();
+ }
+ }
+
+ @Override
+ public void bindExtraContainerItems(FixedContainerItems item) {
+ if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ mPredictedItems = item.items;
+ commitItemsToUI();
+ }
+ }
+
+ private void commitItemsToUI() {
+ if (mBindInProgress) {
+ return;
+ }
+
+ ItemInfo[] hotseatItemInfos =
+ new ItemInfo[mContext.getDeviceProfile().numShownHotseatIcons];
+ int predictionSize = mPredictedItems.size();
+ int predictionNextIndex = 0;
+
+ boolean isHotseatEmpty = true;
+ for (int i = 0; i < hotseatItemInfos.length; i++) {
+ hotseatItemInfos[i] = mHotseatItems.get(i);
+ if (hotseatItemInfos[i] == null && predictionNextIndex < predictionSize) {
+ hotseatItemInfos[i] = mPredictedItems.get(predictionNextIndex);
+ hotseatItemInfos[i].screenId = i;
+ predictionNextIndex++;
+ }
+ if (hotseatItemInfos[i] != null) {
+ isHotseatEmpty = false;
+ }
+ }
+ mContainer.updateHotseatItems(hotseatItemInfos);
+
+ final boolean finalIsHotseatEmpty = isHotseatEmpty;
+ mControllers.runAfterInit(() -> {
+ mControllers.taskbarStashController.updateStateForFlag(
+ TaskbarStashController.FLAG_STASHED_IN_APP_EMPTY, finalIsHotseatEmpty);
+ mControllers.taskbarStashController.applyState();
+ });
+ }
+
+ @Override
+ public void bindDeepShortcutMap(HashMap deepShortcutMapCopy) {
+ mControllers.taskbarPopupController.setDeepShortcutMap(deepShortcutMapCopy);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 3b5afad691..d23336505a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -16,13 +16,18 @@
package com.android.launcher3.taskbar;
-import static android.view.Display.DEFAULT_DISPLAY;
-import android.content.Intent;
-import android.view.inputmethod.InputMethodManager;
+import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
+import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+
+import android.os.Bundle;
+import android.os.Handler;
import androidx.annotation.IntDef;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
@@ -34,17 +39,23 @@ import java.lang.annotation.RetentionPolicy;
* Controller for 3 button mode in the taskbar.
* Handles all the functionality of the various buttons, making/routing the right calls into
* launcher or sysui/system.
- *
- * TODO: Create callbacks to hook into UI layer since state will change for more context buttons/
- * assistant invocation.
*/
public class TaskbarNavButtonController {
+
+ /** Allow some time in between the long press for back and recents. */
+ static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200;
+ static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100;
+
+ private long mLastScreenPinLongPress;
+ private boolean mScreenPinned;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
BUTTON_BACK,
BUTTON_HOME,
BUTTON_RECENTS,
- BUTTON_IME_SWITCH
+ BUTTON_IME_SWITCH,
+ BUTTON_A11Y,
})
public @interface TaskbarButton {}
@@ -53,11 +64,22 @@ public class TaskbarNavButtonController {
static final int BUTTON_HOME = BUTTON_BACK << 1;
static final int BUTTON_RECENTS = BUTTON_HOME << 1;
static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1;
+ static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1;
+
+ private static final int SCREEN_UNPIN_COMBO = BUTTON_BACK | BUTTON_RECENTS;
+ private int mLongPressedButtons = 0;
private final TouchInteractionService mService;
+ private final SystemUiProxy mSystemUiProxy;
+ private final Handler mHandler;
- public TaskbarNavButtonController(TouchInteractionService service) {
+ private final Runnable mResetLongPress = this::resetScreenUnpin;
+
+ public TaskbarNavButtonController(TouchInteractionService service,
+ SystemUiProxy systemUiProxy, Handler handler) {
mService = service;
+ mSystemUiProxy = systemUiProxy;
+ mHandler = handler;
}
public void onButtonClick(@TaskbarButton int buttonType) {
@@ -69,32 +91,115 @@ public class TaskbarNavButtonController {
navigateHome();
break;
case BUTTON_RECENTS:
- navigateToOverview();;
+ navigateToOverview();
break;
case BUTTON_IME_SWITCH:
showIMESwitcher();
break;
+ case BUTTON_A11Y:
+ notifyA11yClick(false /* longClick */);
+ break;
}
}
+ public boolean onButtonLongClick(@TaskbarButton int buttonType) {
+ switch (buttonType) {
+ case BUTTON_HOME:
+ startAssistant();
+ return true;
+ case BUTTON_A11Y:
+ notifyA11yClick(true /* longClick */);
+ return true;
+ case BUTTON_BACK:
+ case BUTTON_RECENTS:
+ mLongPressedButtons |= buttonType;
+ return determineScreenUnpin();
+ case BUTTON_IME_SWITCH:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the user has long pressed back and recents buttons
+ * "together" (within {@link #SCREEN_PIN_LONG_PRESS_THRESHOLD})ms
+ * If so, then requests the system to turn off screen pinning.
+ *
+ * @return true if the long press is a valid user action in attempting to unpin an app
+ * Will always return {@code false} when screen pinning is not active.
+ * NOTE: Returning true does not mean that screen pinning has stopped
+ */
+ private boolean determineScreenUnpin() {
+ long timeNow = System.currentTimeMillis();
+ if (!mScreenPinned) {
+ return false;
+ }
+
+ if (mLastScreenPinLongPress == 0) {
+ // First button long press registered, just mark time and wait for second button press
+ mLastScreenPinLongPress = System.currentTimeMillis();
+ mHandler.postDelayed(mResetLongPress, SCREEN_PIN_LONG_PRESS_RESET);
+ return true;
+ }
+
+ if ((timeNow - mLastScreenPinLongPress) > SCREEN_PIN_LONG_PRESS_THRESHOLD) {
+ // Too long in-between presses, reset the clock
+ resetScreenUnpin();
+ return false;
+ }
+
+ if ((mLongPressedButtons & SCREEN_UNPIN_COMBO) == SCREEN_UNPIN_COMBO) {
+ // Hooray! They did it (finally...)
+ mSystemUiProxy.stopScreenPinning();
+ mHandler.removeCallbacks(mResetLongPress);
+ resetScreenUnpin();
+ }
+ return true;
+ }
+
+ private void resetScreenUnpin() {
+ mLongPressedButtons = 0;
+ mLastScreenPinLongPress = 0;
+ }
+
+ public void updateSysuiFlags(int sysuiFlags) {
+ mScreenPinned = (sysuiFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
+ }
+
private void navigateHome() {
- mService.startActivity(new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
}
private void navigateToOverview() {
- mService.getOverviewCommandHelper()
- .addCommand(OverviewCommandHelper.TYPE_SHOW);
+ if (mScreenPinned) {
+ return;
+ }
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
+ mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE);
}
private void executeBack() {
- SystemUiProxy.INSTANCE.getNoCreate().onBackPressed();
+ mSystemUiProxy.onBackPressed();
}
private void showIMESwitcher() {
- mService.getSystemService(InputMethodManager.class)
- .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
- DEFAULT_DISPLAY);
+ mSystemUiProxy.onImeSwitcherPressed();
+ }
+
+ private void notifyA11yClick(boolean longClick) {
+ if (longClick) {
+ mSystemUiProxy.notifyAccessibilityButtonLongClicked();
+ } else {
+ mSystemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId());
+ }
+ }
+
+ private void startAssistant() {
+ if (mScreenPinned) {
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
+ mSystemUiProxy.startAssistant(args);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
new file mode 100644
index 0000000000..952f597f72
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Implements interfaces required to show and allow interacting with a PopupContainerWithArrow.
+ */
+public class TaskbarPopupController {
+
+ private static final SystemShortcut.Factory
+ APP_INFO = SystemShortcut.AppInfo::new;
+
+ private final PopupDataProvider mPopupDataProvider;
+
+ public TaskbarPopupController() {
+ // TODO (b/198438631): add notifications dots change listener
+ mPopupDataProvider = new PopupDataProvider(packageUserKey -> {});
+ }
+
+ @NonNull
+ public PopupDataProvider getPopupDataProvider() {
+ return mPopupDataProvider;
+ }
+
+ public void setDeepShortcutMap(HashMap deepShortcutMapCopy) {
+ mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
+ }
+
+ /**
+ * Shows the notifications and deep shortcuts associated with a Taskbar {@param icon}.
+ * @return the container if shown or null.
+ */
+ public PopupContainerWithArrow showForIcon(BubbleTextView icon) {
+ TaskbarActivityContext context = ActivityContext.lookupContext(icon.getContext());
+ if (PopupContainerWithArrow.getOpen(context) != null) {
+ // There is already an items container open, so don't open this one.
+ icon.clearFocus();
+ return null;
+ }
+ ItemInfo item = (ItemInfo) icon.getTag();
+ if (!PopupContainerWithArrow.canShow(icon, item)) {
+ return null;
+ }
+
+ final PopupContainerWithArrow container =
+ (PopupContainerWithArrow) context.getLayoutInflater().inflate(
+ R.layout.popup_container, context.getDragLayer(), false);
+ // TODO (b/198438631): configure for taskbar/context
+
+ container.populateAndShow(icon,
+ mPopupDataProvider.getShortcutCountForItem(item),
+ mPopupDataProvider.getNotificationKeysForItem(item),
+ // TODO (b/198438631): add support for INSTALL shortcut factory
+ Stream.of(APP_INFO)
+ .map(s -> s.getShortcut(context, item))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ container.requestFocus();
+ return container;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java
new file mode 100644
index 0000000000..94a3307bc3
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * View that handles scrimming the taskbar and the inverted corners it draws. The scrim is used
+ * when bubbles is expanded.
+ */
+public class TaskbarScrimView extends View {
+ private final Paint mTaskbarScrimPaint;
+ private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
+
+ private boolean mShowScrim;
+ private float mLeftCornerRadius, mRightCornerRadius;
+ private float mBackgroundHeight;
+
+ public TaskbarScrimView(Context context) {
+ this(context, null);
+ }
+
+ public TaskbarScrimView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskbarScrimView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TaskbarScrimView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mTaskbarScrimPaint = new Paint();
+ mTaskbarScrimPaint.setColor(getResources().getColor(android.R.color.system_neutral1_1000));
+ mTaskbarScrimPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+ mTaskbarScrimPaint.setStyle(Paint.Style.FILL);
+
+ mInvertedLeftCornerPath = new Path();
+ mInvertedRightCornerPath = new Path();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mShowScrim) {
+ canvas.save();
+ canvas.translate(0, canvas.getHeight() - mBackgroundHeight);
+
+ // Scrim the taskbar itself.
+ canvas.drawRect(0, 0, canvas.getWidth(), mBackgroundHeight, mTaskbarScrimPaint);
+
+ // Scrim the inverted rounded corners above the taskbar.
+ canvas.translate(0, -mLeftCornerRadius);
+ canvas.drawPath(mInvertedLeftCornerPath, mTaskbarScrimPaint);
+ canvas.translate(0, mLeftCornerRadius);
+ canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
+ canvas.drawPath(mInvertedRightCornerPath, mTaskbarScrimPaint);
+
+ canvas.restore();
+ }
+ }
+
+ /**
+ * Sets the height of the taskbar background.
+ * @param height the height of the background.
+ */
+ protected void setBackgroundHeight(float height) {
+ mBackgroundHeight = height;
+ if (mShowScrim) {
+ invalidate();
+ }
+ }
+
+ /**
+ * Sets the alpha of the taskbar scrim.
+ * @param alpha the alpha of the scrim.
+ */
+ protected void setScrimAlpha(float alpha) {
+ mShowScrim = alpha > 0f;
+ mTaskbarScrimPaint.setAlpha((int) (alpha * 255));
+ invalidate();
+ }
+
+ /**
+ * Sets the radius of the left and right corners above the taskbar.
+ * @param leftCornerRadius the radius of the left corner.
+ * @param rightCornerRadius the radius of the right corner.
+ */
+ protected void setCornerSizes(float leftCornerRadius, float rightCornerRadius) {
+ mLeftCornerRadius = leftCornerRadius;
+ mRightCornerRadius = rightCornerRadius;
+
+ Path square = new Path();
+ square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
+ Path circle = new Path();
+ circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
+ mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
+ square.reset();
+ square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
+ circle.reset();
+ circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
+ mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
+
+ if (mShowScrim) {
+ invalidate();
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
new file mode 100644
index 0000000000..4b4ee4423c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
+
+import android.animation.ObjectAnimator;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SystemUiProxy;
+
+/**
+ * Handles properties/data collection, and passes the results to {@link TaskbarScrimView} to render.
+ */
+public class TaskbarScrimViewController {
+
+ private static final float SCRIM_ALPHA = 0.6f;
+
+ private static final Interpolator SCRIM_ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+ private static final Interpolator SCRIM_ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+
+ private final TaskbarActivityContext mActivity;
+ private final TaskbarScrimView mScrimView;
+
+ // Alpha property for the scrim.
+ private final AnimatedFloat mScrimAlpha = new AnimatedFloat(this::updateScrimAlpha);
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+
+ public TaskbarScrimViewController(TaskbarActivityContext activity, TaskbarScrimView scrimView) {
+ mActivity = activity;
+ mScrimView = scrimView;
+ mScrimView.setCornerSizes(mActivity.getLeftCornerRadius(),
+ mActivity.getRightCornerRadius());
+ mScrimView.setBackgroundHeight(mActivity.getDeviceProfile().taskbarSize);
+ }
+
+ /**
+ * Initializes the controller
+ */
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ }
+
+ /**
+ * Updates the scrim state based on the flags.
+ */
+ public void updateStateForSysuiFlags(int stateFlags, boolean skipAnim) {
+ final boolean bubblesExpanded = (stateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+ final boolean manageMenuExpanded =
+ (stateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
+ final boolean showScrim = !mControllers.navbarButtonsViewController.isImeVisible()
+ && bubblesExpanded && mControllers.taskbarStashController.isInAppAndNotStashed();
+ final float scrimAlpha = manageMenuExpanded
+ // When manage menu shows there's the first scrim and second scrim so figure out
+ // what the total transparency would be.
+ ? (SCRIM_ALPHA + (SCRIM_ALPHA * (1 - SCRIM_ALPHA)))
+ : showScrim ? SCRIM_ALPHA : 0;
+ showScrim(showScrim, scrimAlpha, skipAnim);
+ }
+
+ private void showScrim(boolean showScrim, float alpha, boolean skipAnim) {
+ mScrimView.setOnClickListener(showScrim ? (view) -> onClick() : null);
+ mScrimView.setClickable(showScrim);
+ ObjectAnimator anim = mScrimAlpha.animateToValue(showScrim ? alpha : 0);
+ anim.setInterpolator(showScrim ? SCRIM_ALPHA_IN : SCRIM_ALPHA_OUT);
+ anim.start();
+ if (skipAnim) {
+ anim.end();
+ }
+ }
+
+ private void updateScrimAlpha() {
+ mScrimView.setScrimAlpha(mScrimAlpha.value);
+ }
+
+ private void onClick() {
+ SystemUiProxy.INSTANCE.get(mActivity).onBackPressed();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
new file mode 100644
index 0000000000..23beef0a8a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+/**
+ * State shared across different taskbar instance
+ */
+public class TaskbarSharedState {
+
+ public int sysuiStateFlags;
+
+ public boolean setupUIVisible = false;
+
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
new file mode 100644
index 0000000000..7f7585000c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static android.view.HapticFeedbackConstants.LONG_PRESS;
+
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.annotation.Nullable;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SystemUiProxy;
+
+import java.util.function.IntPredicate;
+
+/**
+ * Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
+ * create a cohesive animation between stashed/unstashed states.
+ */
+public class TaskbarStashController {
+
+ public static final int FLAG_IN_APP = 1 << 0;
+ public static final int FLAG_STASHED_IN_APP_MANUAL = 1 << 1; // long press, persisted
+ public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning
+ public static final int FLAG_STASHED_IN_APP_EMPTY = 1 << 3; // no hotseat icons
+ public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
+ public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
+ public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
+
+ // If we're in an app and any of these flags are enabled, taskbar should be stashed.
+ private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
+ | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
+ | FLAG_STASHED_IN_APP_IME;
+
+ // If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
+ // height. This way the reported insets are consistent even during transitions out of the app.
+ // Currently any flag that causes us to stash in an app is included, except for IME since that
+ // covers the underlying app anyway and thus the app shouldn't change insets.
+ private static final int FLAGS_REPORT_STASHED_INSETS_TO_APP = FLAGS_STASHED_IN_APP
+ & ~FLAG_STASHED_IN_APP_IME;
+
+ /**
+ * How long to stash/unstash when manually invoked via long press.
+ */
+ public static final long TASKBAR_STASH_DURATION = 300;
+
+ /**
+ * How long to stash/unstash when keyboard is appearing/disappearing.
+ */
+ private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
+
+ /**
+ * The scale TaskbarView animates to when being stashed.
+ */
+ private static final float STASHED_TASKBAR_SCALE = 0.5f;
+
+ /**
+ * How long the hint animation plays, starting on motion down.
+ */
+ private static final long TASKBAR_HINT_STASH_DURATION =
+ ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
+
+ /**
+ * The scale that TaskbarView animates to when hinting towards the stashed state.
+ */
+ private static final float STASHED_TASKBAR_HINT_SCALE = 0.9f;
+
+ /**
+ * The scale that the stashed handle animates to when hinting towards the unstashed state.
+ */
+ private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
+
+ /**
+ * The SharedPreferences key for whether user has manually stashed the taskbar.
+ */
+ private static final String SHARED_PREFS_STASHED_KEY = "taskbar_is_stashed";
+
+ /**
+ * Whether taskbar should be stashed out of the box.
+ */
+ private static final boolean DEFAULT_STASHED_PREF = false;
+
+ private final TaskbarActivityContext mActivity;
+ private final SharedPreferences mPrefs;
+ private final int mStashedHeight;
+ private final int mUnstashedHeight;
+ private final SystemUiProxy mSystemUiProxy;
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+ // Taskbar background properties.
+ private AnimatedFloat mTaskbarBackgroundOffset;
+ private AnimatedFloat mTaskbarImeBgAlpha;
+ // TaskbarView icon properties.
+ private AlphaProperty mIconAlphaForStash;
+ private AnimatedFloat mIconScaleForStash;
+ private AnimatedFloat mIconTranslationYForStash;
+ // Stashed handle properties.
+ private AlphaProperty mTaskbarStashedHandleAlpha;
+ private AnimatedFloat mTaskbarStashedHandleHintScale;
+
+ /** Whether we are currently visually stashed (might change based on launcher state). */
+ private boolean mIsStashed = false;
+ private int mState;
+
+ private @Nullable AnimatorSet mAnimator;
+ private boolean mIsSystemGestureInProgress;
+ private boolean mIsImeShowing;
+
+ // Evaluate whether the handle should be stashed
+ private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
+ flags -> {
+ boolean inApp = hasAnyFlag(flags, FLAG_IN_APP);
+ boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
+ boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
+ return (inApp && stashedInApp) || (!inApp && stashedLauncherState);
+ });
+
+ public TaskbarStashController(TaskbarActivityContext activity) {
+ mActivity = activity;
+ mPrefs = Utilities.getPrefs(mActivity);
+ final Resources resources = mActivity.getResources();
+ mStashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
+ mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
+ mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize;
+ }
+
+ public void init(TaskbarControllers controllers, TaskbarSharedState sharedState) {
+ mControllers = controllers;
+
+ TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
+ mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
+ mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
+
+ TaskbarViewController taskbarViewController = controllers.taskbarViewController;
+ mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty(
+ TaskbarViewController.ALPHA_INDEX_STASH);
+ mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash();
+ mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash();
+
+ StashedHandleViewController stashedHandleController =
+ controllers.stashedHandleViewController;
+ mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha().getProperty(
+ StashedHandleViewController.ALPHA_INDEX_STASHED);
+ mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
+
+ boolean isManuallyStashedInApp = supportsManualStashing()
+ && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
+ boolean isInSetup = !mActivity.isUserSetupComplete() || sharedState.setupUIVisible;
+ updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
+ // TODO(b/204384193): Temporarily disable SUW specific logic
+ // updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
+ if (isInSetup) {
+ // Update the in-app state to ensure isStashed() reflects right state during SUW
+ updateStateForFlag(FLAG_IN_APP, true);
+ }
+ applyState();
+
+ notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
+ }
+
+ /**
+ * Returns whether the taskbar can visually stash into a handle based on the current device
+ * state.
+ */
+ private boolean supportsVisualStashing() {
+ return !mActivity.isThreeButtonNav();
+ }
+
+ /**
+ * Returns whether the user can manually stash the taskbar based on the current device state.
+ */
+ private boolean supportsManualStashing() {
+ return supportsVisualStashing()
+ && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || supportsStashingForTests());
+ }
+
+ private boolean supportsStashingForTests() {
+ // TODO: enable this for tests that specifically check stash/unstash behavior.
+ return false;
+ }
+
+ /**
+ * Sets the flag indicating setup UI is visible
+ */
+ protected void setSetupUIVisible(boolean isVisible) {
+ updateStateForFlag(FLAG_STASHED_IN_APP_SETUP,
+ isVisible || !mActivity.isUserSetupComplete());
+ applyState();
+ }
+
+ /**
+ * Returns whether the taskbar is currently visually stashed.
+ */
+ public boolean isStashed() {
+ return mIsStashed;
+ }
+
+ /**
+ * Returns whether the taskbar should be stashed in apps (e.g. user long pressed to stash).
+ */
+ public boolean isStashedInApp() {
+ return hasAnyFlag(FLAGS_STASHED_IN_APP);
+ }
+
+ /**
+ * Returns whether the taskbar should be stashed in the current LauncherState.
+ */
+ public boolean isInStashedLauncherState() {
+ return hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing();
+ }
+
+ private boolean hasAnyFlag(int flagMask) {
+ return hasAnyFlag(mState, flagMask);
+ }
+
+ private boolean hasAnyFlag(int flags, int flagMask) {
+ return (flags & flagMask) != 0;
+ }
+
+
+ /**
+ * Returns whether the taskbar is currently visible and in an app.
+ */
+ public boolean isInAppAndNotStashed() {
+ return !mIsStashed && (mState & FLAG_IN_APP) != 0;
+ }
+
+ /**
+ * Returns the height that taskbar will inset when inside apps.
+ */
+ public int getContentHeightToReportToApps() {
+ if (hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
+ boolean isAnimating = mAnimator != null && mAnimator.isStarted();
+ return mControllers.stashedHandleViewController.isStashedHandleVisible() || isAnimating
+ ? mStashedHeight : 0;
+ }
+ return mUnstashedHeight;
+ }
+
+ public int getStashedHeight() {
+ return mStashedHeight;
+ }
+
+ /**
+ * Should be called when long pressing the nav region when taskbar is present.
+ * @return Whether taskbar was stashed and now is unstashed.
+ */
+ public boolean onLongPressToUnstashTaskbar() {
+ if (!isStashed()) {
+ // We only listen for long press on the nav region to unstash the taskbar. To stash the
+ // taskbar, we use an OnLongClickListener on TaskbarView instead.
+ return false;
+ }
+ if (updateAndAnimateIsManuallyStashedInApp(false)) {
+ mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Updates whether we should stash the taskbar when in apps, and animates to the changed state.
+ * @return Whether we started an animation to either be newly stashed or unstashed.
+ */
+ public boolean updateAndAnimateIsManuallyStashedInApp(boolean isManuallyStashedInApp) {
+ if (!supportsManualStashing()) {
+ return false;
+ }
+ if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL) != isManuallyStashedInApp) {
+ mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_KEY, isManuallyStashedInApp).apply();
+ updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
+ applyState();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Create a stash animation and save to {@link #mAnimator}.
+ * @param isStashed whether it's a stash animation or an unstash animation
+ * @param duration duration of the animation
+ * @param startDelay how many milliseconds to delay the animation after starting it.
+ */
+ private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay) {
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ mAnimator = new AnimatorSet();
+
+ if (!supportsVisualStashing()) {
+ // Just hide/show the icons and background instead of stashing into a handle.
+ mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1)
+ .setDuration(duration));
+ mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
+ hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
+ mAnimator.setStartDelay(startDelay);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator = null;
+ }
+ });
+ return;
+ }
+
+ AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
+ // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
+ AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
+ AnimatorSet secondHalfAnimatorSet = new AnimatorSet();
+
+ final float firstHalfDurationScale;
+ final float secondHalfDurationScale;
+
+ if (isStashed) {
+ firstHalfDurationScale = 0.75f;
+ secondHalfDurationScale = 0.5f;
+ final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
+
+ fullLengthAnimatorSet.playTogether(
+ mTaskbarBackgroundOffset.animateToValue(1),
+ mIconTranslationYForStash.animateToValue(stashTranslation)
+ );
+ firstHalfAnimatorSet.playTogether(
+ mIconAlphaForStash.animateToValue(0),
+ mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE)
+ );
+ secondHalfAnimatorSet.playTogether(
+ mTaskbarStashedHandleAlpha.animateToValue(1)
+ );
+ } else {
+ firstHalfDurationScale = 0.5f;
+ secondHalfDurationScale = 0.75f;
+
+ fullLengthAnimatorSet.playTogether(
+ mTaskbarBackgroundOffset.animateToValue(0),
+ mIconScaleForStash.animateToValue(1),
+ mIconTranslationYForStash.animateToValue(0)
+ );
+ firstHalfAnimatorSet.playTogether(
+ mTaskbarStashedHandleAlpha.animateToValue(0)
+ );
+ secondHalfAnimatorSet.playTogether(
+ mIconAlphaForStash.animateToValue(1)
+ );
+ }
+
+ fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
+ .createRevealAnimToIsStashed(isStashed));
+ // Return the stashed handle to its default scale in case it was changed as part of the
+ // feedforward hint. Note that the reveal animation above also visually scales it.
+ fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
+
+ fullLengthAnimatorSet.setDuration(duration);
+ firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale));
+ secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
+ secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
+
+ mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
+ secondHalfAnimatorSet);
+ mAnimator.setStartDelay(startDelay);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIsStashed = isStashed;
+ onIsStashed(mIsStashed);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator = null;
+ }
+ });
+ }
+
+ /**
+ * Creates and starts a partial stash animation, hinting at the new state that will trigger when
+ * long press is detected.
+ * @param animateForward Whether we are going towards the new stashed state or returning to the
+ * unstashed state.
+ */
+ public void startStashHint(boolean animateForward) {
+ if (isStashed() || !supportsManualStashing()) {
+ // Already stashed, no need to hint in that direction.
+ return;
+ }
+ mIconScaleForStash.animateToValue(
+ animateForward ? STASHED_TASKBAR_HINT_SCALE : 1)
+ .setDuration(TASKBAR_HINT_STASH_DURATION).start();
+ }
+
+ /**
+ * Creates and starts a partial unstash animation, hinting at the new state that will trigger
+ * when long press is detected.
+ * @param animateForward Whether we are going towards the new unstashed state or returning to
+ * the stashed state.
+ */
+ public void startUnstashHint(boolean animateForward) {
+ if (!isStashed()) {
+ // Already unstashed, no need to hint in that direction.
+ return;
+ }
+ mTaskbarStashedHandleHintScale.animateToValue(
+ animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
+ .setDuration(TASKBAR_HINT_STASH_DURATION).start();
+ }
+
+ private void onIsStashed(boolean isStashed) {
+ mControllers.stashedHandleViewController.onIsStashed(isStashed);
+ }
+
+ public void applyState() {
+ applyState(TASKBAR_STASH_DURATION);
+ }
+
+ public void applyState(long duration) {
+ mStatePropertyHolder.setState(mState, duration, true);
+ }
+
+ public void applyState(long duration, long startDelay) {
+ mStatePropertyHolder.setState(mState, duration, startDelay, true);
+ }
+
+ public Animator applyStateWithoutStart() {
+ return applyStateWithoutStart(TASKBAR_STASH_DURATION);
+ }
+
+ public Animator applyStateWithoutStart(long duration) {
+ return mStatePropertyHolder.setState(mState, duration, false);
+ }
+
+ /**
+ * Should be called when a system gesture starts and settles, so we can defer updating
+ * FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
+ */
+ public void setSystemGestureInProgress(boolean inProgress) {
+ mIsSystemGestureInProgress = inProgress;
+ // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
+ if (!mIsSystemGestureInProgress) {
+ updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+ applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
+ }
+ }
+
+ /**
+ * When hiding the IME, delay the unstash animation to align with the end of the transition.
+ */
+ private long getTaskbarStashStartDelayForIme() {
+ if (mIsImeShowing) {
+ // Only delay when IME is exiting, not entering.
+ return 0;
+ }
+ // This duration is based on input_method_extract_exit.xml.
+ long imeExitDuration = mControllers.taskbarActivityContext.getResources()
+ .getInteger(android.R.integer.config_shortAnimTime);
+ return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME;
+ }
+
+ /** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
+ public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
+ long animDuration = TASKBAR_STASH_DURATION;
+ long startDelay = 0;
+
+ updateStateForFlag(FLAG_STASHED_IN_APP_PINNED,
+ hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
+
+ // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
+ mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
+ if (!mIsSystemGestureInProgress) {
+ updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+ animDuration = TASKBAR_STASH_DURATION_FOR_IME;
+ startDelay = getTaskbarStashStartDelayForIme();
+ }
+
+ applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
+ }
+
+ /**
+ * Updates the proper flag to indicate whether the task bar should be stashed.
+ *
+ * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
+ *
+ * @param flag The flag to update.
+ * @param enabled Whether to enable the flag: True will cause the task bar to be stashed /
+ * unstashed.
+ */
+ public void updateStateForFlag(int flag, boolean enabled) {
+ if (enabled) {
+ mState |= flag;
+ } else {
+ mState &= ~flag;
+ }
+ }
+
+ /**
+ * Called after updateStateForFlag() and applyState() have been called.
+ * @param changedFlags The flags that have changed.
+ */
+ private void onStateChangeApplied(int changedFlags) {
+ if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP)) {
+ mControllers.uiController.onStashedInAppChanged();
+ }
+ if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP | FLAG_IN_APP)) {
+ notifyStashChange(/* visible */ hasAnyFlag(FLAG_IN_APP),
+ /* stashed */ isStashedInApp());
+ }
+ if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_MANUAL)) {
+ if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL)) {
+ mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_HIDE);
+ } else {
+ mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_SHOW);
+ }
+ }
+ }
+
+ private void notifyStashChange(boolean visible, boolean stashed) {
+ mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
+ mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
+ }
+
+ private class StatePropertyHolder {
+ private final IntPredicate mStashCondition;
+
+ private boolean mIsStashed;
+ private int mPrevFlags;
+
+ StatePropertyHolder(IntPredicate stashCondition) {
+ mStashCondition = stashCondition;
+ }
+
+ /**
+ * @see #setState(int, long, long, boolean) with a default startDelay = 0.
+ */
+ public Animator setState(int flags, long duration, boolean start) {
+ return setState(flags, duration, 0 /* startDelay */, start);
+ }
+
+ /**
+ * Applies the latest state, potentially calling onStateChangeApplied() and creating a new
+ * animation (stored in mAnimator) which is started if {@param start} is true.
+ * @param flags The latest flags to apply (see the top of this file).
+ * @param duration The length of the animation.
+ * @param startDelay How long to delay the animation after calling start().
+ * @param start Whether to start mAnimator immediately.
+ * @return mAnimator if mIsStashed changed, else null.
+ */
+ public Animator setState(int flags, long duration, long startDelay, boolean start) {
+ int changedFlags = mPrevFlags ^ flags;
+ if (mPrevFlags != flags) {
+ onStateChangeApplied(changedFlags);
+ mPrevFlags = flags;
+ }
+ boolean isStashed = mStashCondition.test(flags);
+ if (mIsStashed != isStashed) {
+ mIsStashed = isStashed;
+
+ // This sets mAnimator.
+ createAnimToIsStashed(mIsStashed, duration, startDelay);
+ if (start) {
+ mAnimator.start();
+ }
+ return mAnimator;
+ }
+ return null;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
deleted file mode 100644
index a701aae088..0000000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import static com.android.launcher3.LauncherState.TASKBAR;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.quickstep.AnimatedFloat;
-
-/**
- * StateHandler to animate Taskbar according to Launcher's state machine. Does nothing if Taskbar
- * isn't present (i.e. {@link #setAnimationController} is never called).
- */
-public class TaskbarStateHandler implements StateManager.StateHandler {
-
- private final BaseQuickstepLauncher mLauncher;
-
- // Contains Taskbar-related methods and fields we should aniamte. If null, don't do anything.
- private @Nullable TaskbarAnimationController mAnimationController = null;
-
- public TaskbarStateHandler(BaseQuickstepLauncher launcher) {
- mLauncher = launcher;
- }
-
- public void setAnimationController(TaskbarAnimationController callbacks) {
- mAnimationController = callbacks;
- }
-
- @Override
- public void setState(LauncherState state) {
- setState(state, PropertySetter.NO_ANIM_PROPERTY_SETTER);
- }
-
- @Override
- public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
- PendingAnimation animation) {
- setState(toState, animation);
- }
-
- private void setState(LauncherState toState, PropertySetter setter) {
- if (mAnimationController == null) {
- return;
- }
-
- boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
- setter.setFloat(mAnimationController.getTaskbarVisibilityForLauncherState(),
- AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
- setter.setFloat(mAnimationController.getTaskbarScaleForLauncherState(),
- AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher), LINEAR);
- setter.setFloat(mAnimationController.getTaskbarTranslationYForLauncherState(),
- AnimatedFloat.VALUE, toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 50adeadbfc..abad9060ab 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -15,6 +15,15 @@
*/
package com.android.launcher3.taskbar;
+import android.view.View;
+
+import androidx.annotation.CallSuper;
+
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+
+import java.util.stream.Stream;
+
/**
* Base class for providing different taskbar UI
*/
@@ -22,20 +31,40 @@ public class TaskbarUIController {
public static final TaskbarUIController DEFAULT = new TaskbarUIController();
- /**
- * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
- */
- public void alignRealHotseatWithTaskbar() { }
+ // Initialized in init.
+ protected TaskbarControllers mControllers;
- protected void onCreate() { }
+ @CallSuper
+ protected void init(TaskbarControllers taskbarControllers) {
+ mControllers = taskbarControllers;
+ }
- protected void onDestroy() { }
+ @CallSuper
+ protected void onDestroy() {
+ mControllers = null;
+ }
protected boolean isTaskbarTouchable() {
return true;
}
- protected void onImeVisible(TaskbarDragLayer container, boolean isVisible) {
- container.updateImeBarVisibilityAlpha(isVisible ? 1 : 0);
+ protected void onStashedInAppChanged() { }
+
+ public Stream getAppIconsForEdu() {
+ return Stream.empty();
+ }
+
+ public void onTaskbarIconLaunched(WorkspaceItemInfo item) { }
+
+ public View getRootView() {
+ return mControllers.taskbarActivityContext.getDragLayer();
+ }
+
+ /**
+ * Called when swiping from the bottom nav region in fully gestural mode.
+ * @param inProgress True if the animation started, false if we just settled on an end target.
+ */
+ public void setSystemGestureInProgress(boolean inProgress) {
+ mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
new file mode 100644
index 0000000000..c785186446
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.quickstep.util.LauncherViewsMoveFromCenterTranslationApplier;
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
+
+/**
+ * Controls animation of taskbar icons when unfolding foldable devices
+ */
+public class TaskbarUnfoldAnimationController {
+
+ private final ScopedUnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+ private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimator;
+ private final TransitionListener mTransitionListener = new TransitionListener();
+ private TaskbarViewController mTaskbarViewController;
+
+ public TaskbarUnfoldAnimationController(ScopedUnfoldTransitionProgressProvider
+ unfoldTransitionProgressProvider, WindowManager windowManager) {
+ mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider;
+ mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
+ new LauncherViewsMoveFromCenterTranslationApplier());
+ }
+
+ /**
+ * Initializes the controller
+ * @param taskbarControllers references to all other taskbar controllers
+ */
+ public void init(TaskbarControllers taskbarControllers) {
+ mTaskbarViewController = taskbarControllers.taskbarViewController;
+ mTaskbarViewController.addOneTimePreDrawListener(() ->
+ mUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
+ mUnfoldTransitionProgressProvider.addCallback(mTransitionListener);
+ }
+
+ /**
+ * Destroys the controller
+ */
+ public void onDestroy() {
+ mUnfoldTransitionProgressProvider.setReadyToHandleTransition(false);
+ mUnfoldTransitionProgressProvider.removeCallback(mTransitionListener);
+ }
+
+ private class TransitionListener implements TransitionProgressListener {
+
+ @Override
+ public void onTransitionStarted() {
+ mMoveFromCenterAnimator.updateDisplayProperties();
+ View[] icons = mTaskbarViewController.getIconViews();
+ for (View icon : icons) {
+ // TODO(b/193794563) we should re-register views if they are re-bound/re-inflated
+ // during the animation
+ mMoveFromCenterAnimator.registerViewForAnimation(icon);
+ }
+
+ mMoveFromCenterAnimator.onTransitionStarted();
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ mMoveFromCenterAnimator.onTransitionFinished();
+ mMoveFromCenterAnimator.clearRegisteredViews();
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ mMoveFromCenterAnimator.onTransitionProgress(progress);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index c6573a639c..59393d7b5b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,76 +15,54 @@
*/
package com.android.launcher3.taskbar;
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.util.AttributeSet;
-import android.view.DragEvent;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.LinearLayout;
+import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.views.ActivityContext;
/**
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
*/
-public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable {
+public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable {
- private final int mIconTouchSize;
- private final boolean mIsRtl;
- private final int mTouchSlop;
- private final RectF mTempDelegateBounds = new RectF();
- private final RectF mDelegateSlopBounds = new RectF();
private final int[] mTempOutLocation = new int[2];
+ private final Rect mIconLayoutBounds = new Rect();
+ private final int mIconTouchSize;
private final int mItemMarginLeftRight;
+ private final int mItemPadding;
private final TaskbarActivityContext mActivityContext;
- // Initialized in TaskbarController constructor.
+ // Initialized in init.
+ private TaskbarViewController.TaskbarViewCallbacks mControllerCallbacks;
private View.OnClickListener mIconClickListener;
private View.OnLongClickListener mIconLongClickListener;
- LinearLayout mSystemButtonContainer;
- LinearLayout mHotseatIconsContainer;
-
- // Delegate touches to the closest view if within mIconTouchSize.
- private boolean mDelegateTargeted;
- private View mDelegateView;
// Prevents dispatching touches to children if true
private boolean mTouchEnabled = true;
- private boolean mIsDraggingItem;
// Only non-null when the corresponding Folder is open.
private @Nullable FolderIcon mLeaveBehindFolderIcon;
- /** Provider of buttons added to taskbar in 3 button nav */
- private ButtonProvider mButtonProvider;
-
- private boolean mDisableRelayout;
- private boolean mAreHolesAllowed;
-
public TaskbarView(@NonNull Context context) {
this(context, null);
}
@@ -105,85 +83,80 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
Resources resources = getResources();
mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
- mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
- mIsRtl = Utilities.isRtl(resources);
- mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+ int actualIconSize = mActivityContext.getDeviceProfile().iconSizePx;
+
+ // We layout the icons to be of mIconTouchSize in width and height
+ mItemMarginLeftRight = actualMargin - (mIconTouchSize - actualIconSize) / 2;
+ mItemPadding = (mIconTouchSize - actualIconSize) / 2;
+
+ // Needed to draw folder leave-behind when opening one.
+ setWillNotDraw(false);
}
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSystemButtonContainer = findViewById(R.id.system_button_layout);
- mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout);
+ protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) {
+ mControllerCallbacks = callbacks;
+ mIconClickListener = mControllerCallbacks.getIconOnClickListener();
+ mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
+
+ setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener());
}
- protected void construct(OnClickListener clickListener, OnLongClickListener longClickListener,
- ButtonProvider buttonProvider) {
- mIconClickListener = clickListener;
- mIconLongClickListener = longClickListener;
- mButtonProvider = buttonProvider;
-
- if (mActivityContext.canShowNavButtons()) {
- createNavButtons();
- } else {
- mSystemButtonContainer.setVisibility(GONE);
- }
-
- int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons;
- updateHotseatItems(new ItemInfo[numHotseatIcons]);
- }
-
- /**
- * Enables/disables empty icons in taskbar so that the layout matches with Launcher
- */
- public void setHolesAllowedInLayout(boolean areHolesAllowed) {
- if (mAreHolesAllowed != areHolesAllowed) {
- mAreHolesAllowed = areHolesAllowed;
- updateHotseatItemsVisibility();
- // TODO: Add animation
- }
- }
-
- private void setHolesAllowedInLayoutNoAnimation(boolean areHolesAllowed) {
- if (mAreHolesAllowed != areHolesAllowed) {
- mAreHolesAllowed = areHolesAllowed;
- updateHotseatItemsVisibility();
- onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
- makeMeasureSpec(getMeasuredHeight(), EXACTLY));
- onLayout(false, getLeft(), getTop(), getRight(), getBottom());
+ private void removeAndRecycle(View view) {
+ removeView(view);
+ view.setOnClickListener(null);
+ view.setOnLongClickListener(null);
+ if (!(view.getTag() instanceof FolderInfo)) {
+ mActivityContext.getViewCache().recycleView(view.getSourceLayoutResId(), view);
}
+ view.setTag(null);
}
/**
* Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
*/
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+ int nextViewIndex = 0;
+ int numViewsAnimated = 0;
+
for (int i = 0; i < hotseatItemInfos.length; i++) {
- ItemInfo hotseatItemInfo = hotseatItemInfos[
- !mIsRtl ? i : hotseatItemInfos.length - i - 1];
- View hotseatView = mHotseatIconsContainer.getChildAt(i);
+ ItemInfo hotseatItemInfo = hotseatItemInfos[i];
+ if (hotseatItemInfo == null) {
+ continue;
+ }
// Replace any Hotseat views with the appropriate type if it's not already that type.
final int expectedLayoutResId;
boolean isFolder = false;
- boolean needsReinflate = false;
- if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
+ if (hotseatItemInfo.isPredictedItem()) {
expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
} else if (hotseatItemInfo instanceof FolderInfo) {
expectedLayoutResId = R.layout.folder_icon;
isFolder = true;
- // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so
- // if the info changes we need to reinflate. This should only happen if a new folder
- // is dragged to the position that another folder previously existed.
- needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo;
} else {
expectedLayoutResId = R.layout.taskbar_app_icon;
}
- if (hotseatView == null
- || hotseatView.getSourceLayoutResId() != expectedLayoutResId
- || needsReinflate) {
- mHotseatIconsContainer.removeView(hotseatView);
+
+ View hotseatView = null;
+ while (nextViewIndex < getChildCount()) {
+ hotseatView = getChildAt(nextViewIndex);
+
+ // see if the view can be reused
+ if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId)
+ || (isFolder && (hotseatView.getTag() != hotseatItemInfo))) {
+ // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation,
+ // so if the info changes we need to reinflate. This should only happen if a new
+ // folder is dragged to the position that another folder previously existed.
+ removeAndRecycle(hotseatView);
+ hotseatView = null;
+ } else {
+ // View found
+ break;
+ }
+ }
+
+ if (hotseatView == null) {
if (isFolder) {
FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
@@ -193,40 +166,68 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
} else {
hotseatView = inflate(expectedLayoutResId);
}
- int iconSize = mActivityContext.getDeviceProfile().iconSizePx;
- LayoutParams lp = new LayoutParams(iconSize, iconSize);
- lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
- mHotseatIconsContainer.addView(hotseatView, i, lp);
+ LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
+ hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
+ addView(hotseatView, nextViewIndex, lp);
}
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
if (hotseatView instanceof BubbleTextView
&& hotseatItemInfo instanceof WorkspaceItemInfo) {
- ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
- (WorkspaceItemInfo) hotseatItemInfo);
- hotseatView.setOnClickListener(mIconClickListener);
- hotseatView.setOnLongClickListener(mIconLongClickListener);
- } else if (isFolder) {
- hotseatView.setOnClickListener(mIconClickListener);
- hotseatView.setOnLongClickListener(mIconLongClickListener);
- } else {
- hotseatView.setOnClickListener(null);
- hotseatView.setOnLongClickListener(null);
- hotseatView.setTag(null);
+ BubbleTextView btv = (BubbleTextView) hotseatView;
+ WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) hotseatItemInfo;
+
+ boolean animate = btv.shouldAnimateIconChange((WorkspaceItemInfo) hotseatItemInfo);
+ btv.applyFromWorkspaceItem(workspaceInfo, animate, numViewsAnimated);
+ if (animate) {
+ numViewsAnimated++;
+ }
}
- updateHotseatItemVisibility(hotseatView);
+ setClickAndLongClickListenersForIcon(hotseatView);
+ nextViewIndex++;
+ }
+ // Remove remaining views
+ while (nextViewIndex < getChildCount()) {
+ removeAndRecycle(getChildAt(nextViewIndex));
}
}
- protected void updateHotseatItemsVisibility() {
- for (int i = mHotseatIconsContainer.getChildCount() - 1; i >= 0; i--) {
- updateHotseatItemVisibility(mHotseatIconsContainer.getChildAt(i));
- }
+ /**
+ * Sets OnClickListener and OnLongClickListener for the given view.
+ */
+ public void setClickAndLongClickListenersForIcon(View icon) {
+ icon.setOnClickListener(mIconClickListener);
+ icon.setOnLongClickListener(mIconLongClickListener);
}
- private void updateHotseatItemVisibility(View hotseatView) {
- hotseatView.setVisibility(
- hotseatView.getTag() != null ? VISIBLE : (mAreHolesAllowed ? INVISIBLE : GONE));
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ int count = getChildCount();
+ int spaceNeeded = count * (mItemMarginLeftRight * 2 + mIconTouchSize);
+ int navSpaceNeeded = ApiWrapper.getHotseatEndOffset(getContext());
+ boolean layoutRtl = isLayoutRtl();
+ int iconEnd = right - (right - left - spaceNeeded) / 2;
+ boolean needMoreSpaceForNav = layoutRtl ?
+ navSpaceNeeded > (iconEnd - spaceNeeded) :
+ iconEnd > (right - navSpaceNeeded);
+ if (needMoreSpaceForNav) {
+ int offset = layoutRtl ?
+ navSpaceNeeded - (iconEnd - spaceNeeded) :
+ (right - navSpaceNeeded) - iconEnd;
+ iconEnd += offset;
+ }
+ // Layout the children
+ mIconLayoutBounds.right = iconEnd;
+ mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2;
+ mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
+ for (int i = count; i > 0; i--) {
+ View child = getChildAt(i - 1);
+ iconEnd -= mItemMarginLeftRight;
+ int iconStart = iconEnd - mIconTouchSize;
+ child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
+ iconEnd = iconStart - mItemMarginLeftRight;
+ }
+ mIconLayoutBounds.left = iconEnd;
}
@Override
@@ -239,158 +240,54 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
@Override
public boolean onTouchEvent(MotionEvent event) {
- boolean handled = delegateTouchIfNecessary(event);
- return super.onTouchEvent(event) || handled;
+ if (!mTouchEnabled) {
+ return true;
+ }
+ if (mIconLayoutBounds.contains((int) event.getX(), (int) event.getY())) {
+ // Don't allow long pressing between icons.
+ return true;
+ }
+ if (mControllerCallbacks.onTouchEvent(event)) {
+ int oldAction = event.getAction();
+ try {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ return super.onTouchEvent(event);
+ } finally {
+ event.setAction(oldAction);
+ }
+ }
+ return super.onTouchEvent(event);
}
public void setTouchesEnabled(boolean touchEnabled) {
this.mTouchEnabled = touchEnabled;
}
- /**
- * User touched the Taskbar background. Determine whether the touch is close enough to a view
- * that we should forward the touches to it.
- * @return Whether a delegate view was chosen and it handled the touch event.
- */
- private boolean delegateTouchIfNecessary(MotionEvent event) {
- final float x = event.getX();
- final float y = event.getY();
- if (mDelegateView == null && event.getAction() == MotionEvent.ACTION_DOWN) {
- View delegateView = findDelegateView(x, y);
- if (delegateView != null) {
- mDelegateTargeted = true;
- mDelegateView = delegateView;
- mDelegateSlopBounds.set(mTempDelegateBounds);
- mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop);
- }
- }
-
- boolean sendToDelegate = mDelegateTargeted;
- boolean inBounds = true;
- switch (event.getAction()) {
- case MotionEvent.ACTION_MOVE:
- inBounds = mDelegateSlopBounds.contains(x, y);
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mDelegateTargeted = false;
- break;
- }
-
- boolean handled = false;
- if (sendToDelegate) {
- if (inBounds) {
- // Offset event coordinates to be inside the target view
- event.setLocation(mDelegateView.getWidth() / 2f, mDelegateView.getHeight() / 2f);
- } else {
- // Offset event coordinates to be outside the target view (in case it does
- // something like tracking pressed state)
- event.setLocation(-mTouchSlop * 2, -mTouchSlop * 2);
- }
- handled = mDelegateView.dispatchTouchEvent(event);
- // Cleanup if this was the last event to send to the delegate.
- if (!mDelegateTargeted) {
- mDelegateView = null;
- }
- }
- return handled;
- }
-
- /**
- * Return an item whose touch bounds contain the given coordinates,
- * or null if no such item exists.
- *
- * Also sets {@link #mTempDelegateBounds} to be the touch bounds of the chosen delegate view.
- */
- private @Nullable View findDelegateView(float x, float y) {
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (!child.isShown() || !child.isClickable()) {
- continue;
- }
- int childCenterX = child.getLeft() + child.getWidth() / 2;
- int childCenterY = child.getTop() + child.getHeight() / 2;
- mTempDelegateBounds.set(
- childCenterX - mIconTouchSize / 2f,
- childCenterY - mIconTouchSize / 2f,
- childCenterX + mIconTouchSize / 2f,
- childCenterY + mIconTouchSize / 2f);
- if (mTempDelegateBounds.contains(x, y)) {
- return child;
- }
- }
- return null;
- }
-
/**
* Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
* touch bounds.
*/
public boolean isEventOverAnyItem(MotionEvent ev) {
getLocationOnScreen(mTempOutLocation);
- float xInOurCoordinates = ev.getX() - mTempOutLocation[0];
- float yInOurCoorindates = ev.getY() - mTempOutLocation[1];
- return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null;
+ int xInOurCoordinates = (int) ev.getX() - mTempOutLocation[0];
+ int yInOurCoorindates = (int) ev.getY() - mTempOutLocation[1];
+ return isShown() && mIconLayoutBounds.contains(xInOurCoordinates, yInOurCoorindates);
+ }
+
+ public Rect getIconLayoutBounds() {
+ return mIconLayoutBounds;
}
/**
- * Add back/home/recents buttons into a single ViewGroup that will be inserted at
- * {@param navButtonStartIndex}
+ * Returns the app icons currently shown in the taskbar.
*/
- private void createNavButtons() {
- LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
- mActivityContext.getDeviceProfile().iconSizePx,
- mActivityContext.getDeviceProfile().iconSizePx
- );
- buttonParams.gravity = Gravity.CENTER;
-
- mSystemButtonContainer.addView(mButtonProvider.getBack(), buttonParams);
- mSystemButtonContainer.addView(mButtonProvider.getHome(), buttonParams);
- mSystemButtonContainer.addView(mButtonProvider.getRecents(), buttonParams);
- }
-
- @Override
- public boolean onDragEvent(DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_STARTED:
- mIsDraggingItem = true;
- AbstractFloatingView.closeAllOpenViews(mActivityContext);
- return true;
- case DragEvent.ACTION_DRAG_ENDED:
- mIsDraggingItem = false;
- break;
- }
- return super.onDragEvent(event);
- }
-
- public boolean isDraggingItem() {
- return mIsDraggingItem;
- }
-
- /**
- * @return The bounding box of where the hotseat elements are relative to this TaskbarView.
- */
- protected RectF getHotseatBounds() {
- RectF result;
- mDisableRelayout = true;
- boolean wereHolesAllowed = mAreHolesAllowed;
- setHolesAllowedInLayoutNoAnimation(true);
- result = new RectF(
- mHotseatIconsContainer.getLeft(),
- mHotseatIconsContainer.getTop(),
- mHotseatIconsContainer.getRight(),
- mHotseatIconsContainer.getBottom());
- setHolesAllowedInLayoutNoAnimation(wereHolesAllowed);
- mDisableRelayout = false;
-
- return result;
- }
-
- @Override
- public void requestLayout() {
- if (!mDisableRelayout) {
- super.requestLayout();
+ public View[] getIconViews() {
+ final int count = getChildCount();
+ View[] icons = new View[count];
+ for (int i = 0; i < count; i++) {
+ icons[i] = getChildAt(i);
}
+ return icons;
}
// FolderIconParent implemented methods.
@@ -421,7 +318,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
}
private View inflate(@LayoutRes int layoutResId) {
- return mActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
+ return mActivityContext.getViewCache().getView(layoutResId, mActivityContext, this);
}
@Override
@@ -429,11 +326,8 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
// Ignore, we just implement Insettable to draw behind system insets.
}
- public void setIconsVisibility(boolean isVisible) {
- mHotseatIconsContainer.setVisibility(isVisible ? VISIBLE : INVISIBLE);
- }
-
public boolean areIconsVisible() {
- return mHotseatIconsContainer.getVisibility() == VISIBLE;
+ // Consider the overall visibility
+ return getVisibility() == VISIBLE;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
new file mode 100644
index 0000000000..445a23bf3f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.AnimatedFloat.VALUE;
+
+import android.graphics.Rect;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.AnimatedFloat;
+
+/**
+ * Handles properties/data collection, then passes the results to TaskbarView to render.
+ */
+public class TaskbarViewController {
+ private static final Runnable NO_OP = () -> { };
+
+ public static final int ALPHA_INDEX_HOME = 0;
+ public static final int ALPHA_INDEX_KEYGUARD = 1;
+ public static final int ALPHA_INDEX_STASH = 2;
+ public static final int ALPHA_INDEX_RECENTS_DISABLED = 3;
+ public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
+ private static final int NUM_ALPHA_CHANNELS = 5;
+
+ private final TaskbarActivityContext mActivity;
+ private final TaskbarView mTaskbarView;
+ private final MultiValueAlpha mTaskbarIconAlpha;
+ private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale);
+ private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat(
+ this::updateTranslationY);
+ private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
+ this::updateTranslationY);
+ private AnimatedFloat mTaskbarNavButtonTranslationY;
+
+ private final TaskbarModelCallbacks mModelCallbacks;
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+
+ // Animation to align icons with Launcher, created lazily. This allows the controller to be
+ // active only during the animation and does not need to worry about layout changes.
+ private AnimatorPlaybackController mIconAlignControllerLazy = null;
+ private Runnable mOnControllerPreCreateCallback = NO_OP;
+
+ public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
+ mActivity = activity;
+ mTaskbarView = taskbarView;
+ mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
+ mTaskbarIconAlpha.setUpdateVisibility(true);
+ mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView);
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ mTaskbarView.init(new TaskbarViewCallbacks());
+ mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+
+ mTaskbarIconScaleForStash.updateValue(1f);
+
+ mModelCallbacks.init(controllers);
+ if (mActivity.isUserSetupComplete()) {
+ // Only load the callbacks if user setup is completed
+ LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
+ }
+ mTaskbarNavButtonTranslationY =
+ controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
+ }
+
+ public void onDestroy() {
+ LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
+ }
+
+ public boolean areIconsVisible() {
+ return mTaskbarView.areIconsVisible();
+ }
+
+ public MultiValueAlpha getTaskbarIconAlpha() {
+ return mTaskbarIconAlpha;
+ }
+
+ /**
+ * Should be called when the IME visibility changes, so we can make Taskbar not steal touches.
+ */
+ public void setImeIsVisible(boolean isImeVisible) {
+ mTaskbarView.setTouchesEnabled(!isImeVisible);
+ }
+
+ /**
+ * Should be called when the recents button is disabled, so we can hide taskbar icons as well.
+ */
+ public void setRecentsButtonDisabled(boolean isDisabled) {
+ // TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha.
+ mTaskbarIconAlpha.getProperty(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1);
+ }
+
+ /**
+ * Sets OnClickListener and OnLongClickListener for the given view.
+ */
+ public void setClickAndLongClickListenersForIcon(View icon) {
+ mTaskbarView.setClickAndLongClickListenersForIcon(icon);
+ }
+
+ /**
+ * Adds one time pre draw listener to the taskbar view, it is called before
+ * drawing a frame and invoked only once
+ * @param listener callback that will be invoked before drawing the next frame
+ */
+ public void addOneTimePreDrawListener(Runnable listener) {
+ mTaskbarView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ final ViewTreeObserver viewTreeObserver = mTaskbarView.getViewTreeObserver();
+ if (viewTreeObserver.isAlive()) {
+ listener.run();
+ viewTreeObserver.removeOnPreDrawListener(this);
+ }
+ return true;
+ }
+ });
+ }
+
+ public Rect getIconLayoutBounds() {
+ return mTaskbarView.getIconLayoutBounds();
+ }
+
+ public View[] getIconViews() {
+ return mTaskbarView.getIconViews();
+ }
+
+ public AnimatedFloat getTaskbarIconScaleForStash() {
+ return mTaskbarIconScaleForStash;
+ }
+
+ public AnimatedFloat getTaskbarIconTranslationYForStash() {
+ return mTaskbarIconTranslationYForStash;
+ }
+
+ /**
+ * Applies scale properties for the entire TaskbarView (rather than individual icons).
+ */
+ private void updateScale() {
+ float scale = mTaskbarIconScaleForStash.value;
+ mTaskbarView.setScaleX(scale);
+ mTaskbarView.setScaleY(scale);
+ }
+
+ private void updateTranslationY() {
+ mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
+ + mTaskbarIconTranslationYForStash.value);
+ }
+
+ /**
+ * Sets the taskbar icon alignment relative to Launcher hotseat icons
+ * @param alignmentRatio [0, 1]
+ * 0 => not aligned
+ * 1 => fully aligned
+ */
+ public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
+ if (mIconAlignControllerLazy == null) {
+ mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
+ }
+ mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
+ if (alignmentRatio <= 0 || alignmentRatio >= 1) {
+ // Cleanup lazy controller so that it is created again in next animation
+ mIconAlignControllerLazy = null;
+ }
+ }
+
+ /**
+ * Creates an animation for aligning the taskbar icons with the provided Launcher device profile
+ */
+ private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
+ mOnControllerPreCreateCallback.run();
+ PendingAnimation setter = new PendingAnimation(100);
+ Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
+ float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx;
+ int hotseatCellSize =
+ (launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right)
+ / launcherDp.numShownHotseatIcons;
+
+ int offsetY = launcherDp.getTaskbarOffsetY();
+ setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR);
+ setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, LINEAR);
+
+ int collapsedHeight = mActivity.getDefaultTaskbarWindowHeight();
+ int expandedHeight = Math.max(collapsedHeight,
+ mActivity.getDeviceProfile().taskbarSize + offsetY);
+ setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
+ anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
+
+ int count = mTaskbarView.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = mTaskbarView.getChildAt(i);
+ ItemInfo info = (ItemInfo) child.getTag();
+ setter.setFloat(child, SCALE_PROPERTY, scaleUp, LINEAR);
+
+ float childCenter = (child.getLeft() + child.getRight()) / 2;
+ float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
+ + hotseatCellSize / 2;
+ setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
+ }
+
+ AnimatorPlaybackController controller = setter.createPlaybackController();
+ mOnControllerPreCreateCallback = () -> controller.setPlayFraction(0);
+ return controller;
+ }
+
+ public void onRotationChanged(DeviceProfile deviceProfile) {
+ if (areIconsVisible()) {
+ // We only translate on rotation when on home
+ return;
+ }
+ mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
+ }
+
+ /**
+ * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
+ * touch bounds.
+ */
+ public boolean isEventOverAnyItem(MotionEvent ev) {
+ return mTaskbarView.isEventOverAnyItem(ev);
+ }
+
+ /**
+ * Callbacks for {@link TaskbarView} to interact with its controller.
+ */
+ public class TaskbarViewCallbacks {
+ private final float mSquaredTouchSlop = Utilities.squaredTouchSlop(mActivity);
+
+ private float mDownX, mDownY;
+ private boolean mCanceledStashHint;
+
+ public View.OnClickListener getIconOnClickListener() {
+ return mActivity.getItemOnClickListener();
+ }
+
+ public View.OnLongClickListener getIconOnLongClickListener() {
+ return mControllers.taskbarDragController::startDragOnLongClick;
+ }
+
+ public View.OnLongClickListener getBackgroundOnLongClickListener() {
+ return view -> mControllers.taskbarStashController
+ .updateAndAnimateIsManuallyStashedInApp(true);
+ }
+
+ /**
+ * Get the first chance to handle TaskbarView#onTouchEvent, and return whether we want to
+ * consume the touch so TaskbarView treats it as an ACTION_CANCEL.
+ */
+ public boolean onTouchEvent(MotionEvent motionEvent) {
+ final float x = motionEvent.getRawX();
+ final float y = motionEvent.getRawY();
+ switch (motionEvent.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mDownX = x;
+ mDownY = y;
+ mControllers.taskbarStashController.startStashHint(/* animateForward = */ true);
+ mCanceledStashHint = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (!mCanceledStashHint
+ && squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) {
+ mControllers.taskbarStashController.startStashHint(
+ /* animateForward= */ false);
+ mCanceledStashHint = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (!mCanceledStashHint) {
+ mControllers.taskbarStashController.startStashHint(
+ /* animateForward= */ false);
+ }
+ break;
+ }
+ return false;
+ }
+ }
+
+ public static final FloatProperty ICON_TRANSLATE_X =
+ new FloatProperty("taskbarAligmentTranslateX") {
+
+ @Override
+ public void setValue(View view, float v) {
+ if (view instanceof BubbleTextView) {
+ ((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
+ } else if (view instanceof FolderIcon) {
+ ((FolderIcon) view).setTranslationForTaskbarAlignmentAnimation(v);
+ } else {
+ view.setTranslationX(v);
+ }
+ }
+
+ @Override
+ public Float get(View view) {
+ if (view instanceof BubbleTextView) {
+ return ((BubbleTextView) view)
+ .getTranslationXForTaskbarAlignmentAnimation();
+ } else if (view instanceof FolderIcon) {
+ return ((FolderIcon) view).getTranslationXForTaskbarAlignmentAnimation();
+ }
+ return view.getTranslationX();
+ }
+ };
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 3b56830b79..061e85d9a9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,10 +17,15 @@
package com.android.launcher3.uioverrides;
import android.app.Person;
+import android.content.Context;
import android.content.pm.ShortcutInfo;
+import android.content.res.Resources;
import android.view.Display;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
public class ApiWrapper {
@@ -38,4 +43,31 @@ public class ApiWrapper {
public static boolean isInternalDisplay(Display display) {
return display.getType() == Display.TYPE_INTERNAL;
}
+
+ /**
+ * Returns a unique ID representing the display
+ */
+ public static String getUniqueId(Display display) {
+ return display.getUniqueId();
+ }
+
+ /**
+ * Returns the minimum space that should be left empty at the end of hotseat
+ */
+ public static int getHotseatEndOffset(Context context) {
+ if (SysUINavigationMode.INSTANCE.get(context).getMode() == Mode.THREE_BUTTONS) {
+ Resources res = context.getResources();
+ /*
+ * 3 nav buttons +
+ * Little space at the end for contextual buttons +
+ * Little space between icons and nav buttons
+ */
+ return 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+ + res.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin)
+ + res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing);
+ } else {
+ return 0;
+ }
+
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 1d523151db..0eaea83801 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -17,6 +17,8 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
@@ -27,8 +29,6 @@ import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
-import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION;
-import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.util.FloatProperty;
@@ -40,7 +40,6 @@ import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.views.RecentsView;
/**
@@ -97,13 +96,6 @@ public abstract class BaseRecentsViewStateController
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
- PagedOrientationHandler orientationHandler =
- ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
- FloatProperty taskViewsFloat = orientationHandler.getSplitSelectTaskOffset(
- TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
- mLauncher.getDeviceProfile());
- setter.setFloat(mRecentsView, taskViewsFloat,
- toState.getSplitSelectTranslation(mLauncher), LINEAR);
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
@@ -112,8 +104,9 @@ public abstract class BaseRecentsViewStateController
mRecentsView, getTaskModalnessProperty(),
toState.getOverviewModalness(),
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
- setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
- toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f, LINEAR);
+ boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
+ setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
+ showAsGrid ? INSTANT : FINAL_FRAME);
}
abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index d839a36213..ee6e8cee9b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,6 +15,16 @@
*/
package com.android.launcher3.uioverrides;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
@@ -23,8 +33,10 @@ import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Process;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -35,6 +47,8 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
@@ -45,6 +59,10 @@ import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.DoubleShadowBubbleTextView;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
/**
* A BubbleTextView with a ring around it's drawable
*/
@@ -53,6 +71,9 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
private static final int RING_SHADOW_COLOR = 0x99000000;
private static final float RING_EFFECT_RATIO = 0.095f;
+ private static final long ICON_CHANGE_ANIM_DURATION = 360;
+ private static final long ICON_CHANGE_ANIM_STAGGER = 50;
+
boolean mIsDrawingDot = false;
private final DeviceProfile mDeviceProfile;
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -67,6 +88,25 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
private int mPlateColor;
boolean mDrawForDrag = false;
+ // Used for the "slot-machine" education animation.
+ private List mSlotMachineIcons;
+ private Animator mSlotMachineAnim;
+ private float mSlotMachineIconTranslationY;
+
+ private static final FloatProperty SLOT_MACHINE_TRANSLATION_Y =
+ new FloatProperty("slotMachineTranslationY") {
+ @Override
+ public void setValue(PredictedAppIcon predictedAppIcon, float transY) {
+ predictedAppIcon.mSlotMachineIconTranslationY = transY;
+ predictedAppIcon.invalidate();
+ }
+
+ @Override
+ public Float get(PredictedAppIcon predictedAppIcon) {
+ return predictedAppIcon.mSlotMachineIconTranslationY;
+ }
+ };
+
public PredictedAppIcon(Context context) {
this(context, null, 0);
}
@@ -88,15 +128,38 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
@Override
public void onDraw(Canvas canvas) {
int count = canvas.save();
+ boolean isSlotMachineAnimRunning = mSlotMachineAnim != null;
if (!mIsPinned) {
drawEffect(canvas);
+ if (isSlotMachineAnimRunning) {
+ // Clip to to outside of the ring during the slot machine animation.
+ canvas.clipPath(mRingPath);
+ }
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
}
- super.onDraw(canvas);
+ if (isSlotMachineAnimRunning) {
+ drawSlotMachineIcons(canvas);
+ } else {
+ super.onDraw(canvas);
+ }
canvas.restoreToCount(count);
}
+ private void drawSlotMachineIcons(Canvas canvas) {
+ canvas.translate((getWidth() - getIconSize()) / 2f,
+ (getHeight() - getIconSize()) / 2f + mSlotMachineIconTranslationY);
+ for (Drawable icon : mSlotMachineIcons) {
+ icon.setBounds(0, 0, getIconSize(), getIconSize());
+ icon.draw(canvas);
+ canvas.translate(0, getSlotMachineIconPlusSpacingSize());
+ }
+ }
+
+ private float getSlotMachineIconPlusSpacingSize() {
+ return getIconSize() + getOutlineOffsetY();
+ }
+
@Override
protected void drawDotIfNecessary(Canvas canvas) {
mIsDrawingDot = true;
@@ -109,9 +172,17 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
}
@Override
- public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
- super.applyFromWorkspaceItem(info);
- mPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
+ // Create the slot machine animation first, since it uses the current icon to start.
+ Animator slotMachineAnim = animate
+ ? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
+ : null;
+ super.applyFromWorkspaceItem(info, animate, staggerIndex);
+ int oldPlateColor = mPlateColor;
+ int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
+ if (!animate) {
+ mPlateColor = newPlateColor;
+ }
if (mIsPinned) {
setContentDescription(info.contentDescription);
} else {
@@ -119,6 +190,76 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
getContext().getString(R.string.hotseat_prediction_content_description,
info.contentDescription));
}
+
+ if (animate) {
+ ValueAnimator plateColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(),
+ oldPlateColor, newPlateColor);
+ plateColorAnim.addUpdateListener(valueAnimator -> {
+ mPlateColor = (int) valueAnimator.getAnimatedValue();
+ invalidate();
+ });
+ AnimatorSet changeIconAnim = new AnimatorSet();
+ if (slotMachineAnim != null) {
+ changeIconAnim.play(slotMachineAnim);
+ }
+ changeIconAnim.play(plateColorAnim);
+ changeIconAnim.setStartDelay(staggerIndex * ICON_CHANGE_ANIM_STAGGER);
+ changeIconAnim.setDuration(ICON_CHANGE_ANIM_DURATION).start();
+ }
+ }
+
+ /**
+ * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
+ * and ending with the original icon.
+ */
+ public @Nullable Animator createSlotMachineAnim(List iconsToAnimate) {
+ return createSlotMachineAnim(iconsToAnimate, true);
+ }
+
+ /**
+ * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
+ * with the original icon, then cycling through the given icons, optionally ending back with
+ * the original icon.
+ * @param endWithOriginalIcon Whether we should land back on the icon we started with, rather
+ * than the last item in iconsToAnimate.
+ */
+ public @Nullable Animator createSlotMachineAnim(List iconsToAnimate,
+ boolean endWithOriginalIcon) {
+ if (mIsPinned || iconsToAnimate == null || iconsToAnimate.isEmpty()) {
+ return null;
+ }
+ if (mSlotMachineAnim != null) {
+ mSlotMachineAnim.end();
+ }
+
+ // Bookend the other animating icons with the original icon on both ends.
+ mSlotMachineIcons = new ArrayList<>(iconsToAnimate.size() + 2);
+ mSlotMachineIcons.add(getIcon());
+ iconsToAnimate.stream()
+ .map(iconInfo -> iconInfo.newThemedIcon(mContext))
+ .forEach(mSlotMachineIcons::add);
+ if (endWithOriginalIcon) {
+ mSlotMachineIcons.add(getIcon());
+ }
+
+ float finalTrans = -getSlotMachineIconPlusSpacingSize() * (mSlotMachineIcons.size() - 1);
+ Keyframe[] keyframes = new Keyframe[] {
+ Keyframe.ofFloat(0f, 0f),
+ Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
+ Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
+ };
+ keyframes[1].setInterpolator(ACCEL_DEACCEL);
+ keyframes[2].setInterpolator(ACCEL_DEACCEL);
+
+ mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+ PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
+ mSlotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
+ mSlotMachineIcons = null;
+ mSlotMachineAnim = null;
+ mSlotMachineIconTranslationY = 0;
+ invalidate();
+ }));
+ return mSlotMachineAnim;
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index c2c721adf8..1cf50f7f64 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -71,7 +71,7 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
}
}
activityOptions.options.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_EMPTY);
Object itemInfo = hostView.getTag();
if (itemInfo instanceof ItemInfo) {
mLauncher.addLaunchCookie((ItemInfo) itemInfo, activityOptions.options);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 1fc1643944..5d91298cae 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -52,10 +52,10 @@ import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
@@ -68,7 +68,9 @@ import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchControll
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
+import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
@@ -84,7 +86,6 @@ import com.android.quickstep.views.TaskView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@@ -109,7 +110,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
}
@Override
- protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
+ public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info,
+ InstanceId instanceId) {
// If the app launch is from any of the surfaces in AllApps then add the InstanceId from
// LiveSearchManager to recreate the AllApps session on the server side.
if (mAllAppsSessionLogId != null && ALL_APPS.equals(
@@ -117,8 +119,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
instanceId = mAllAppsSessionLogId;
}
- StatsLogger logger = getStatsLogManager()
- .logger().withItemInfo(info).withInstanceId(instanceId);
+ StatsLogger logger = statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId);
if (mAllAppsPredictions != null
&& (info.itemType == ITEM_TYPE_APPLICATION
@@ -141,6 +142,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
}
+ @Override
+ protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
+ int cellY, PendingRequestArgs args) {
+ if (container == CONTAINER_HOTSEAT) {
+ mHotseatPredictionController.onDeferredDrop(cellX, cellY);
+ }
+ super.completeAddShortcut(data, container, screenId, cellX, cellY, args);
+ }
+
@Override
protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
return new QuickstepAccessibilityDelegate(this);
@@ -168,7 +178,11 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
// Only pause is taskbar controller is not present
mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
- return super.startActivitySafely(v, intent, item);
+ boolean started = super.startActivitySafely(v, intent, item);
+ if (getTaskbarUIController() == null && !started) {
+ mHotseatPredictionController.setPauseUIUpdate(false);
+ }
+ return started;
}
@Override
@@ -233,12 +247,9 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
}
@Override
- public void bindWorkspaceItemsChanged(List updated) {
- super.bindWorkspaceItemsChanged(updated);
- if (getTaskbarUIController() != null && updated.stream()
- .filter(w -> w.container == CONTAINER_HOTSEAT).findFirst().isPresent()) {
- getTaskbarUIController().onHotseatUpdated();
- }
+ public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) {
+ super.bindWorkspaceComponentsRemoved(matcher);
+ mHotseatPredictionController.onModelItemsRemoved(matcher);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 996d36aadc..32ce1c40d7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -18,20 +18,22 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.LauncherState.SPLIT_PLACHOLDER_VIEW;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
-import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT;
+import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.FloatProperty;
+import android.util.Pair;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
@@ -39,6 +41,7 @@ import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.LauncherRecentsView;
@@ -65,6 +68,11 @@ public final class RecentsViewStateController extends
}
setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state);
mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
+ // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
+ // DepthController to prevent optimizations which might occlude the layers behind
+ mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi);
+
+ handleSplitSelectionState(state, null);
}
@Override
@@ -80,14 +88,12 @@ public final class RecentsViewStateController extends
builder.addListener(
AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals));
}
+ // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
+ // DepthController to prevent optimizations which might occlude the layers behind
+ builder.addListener(AnimatorListeners.forSuccessCallback(() ->
+ mLauncher.getDepthController().setHasContentBehindLauncher(toState.overviewUi)));
- // Create or dismiss split screen select animations
- LauncherState currentState = mLauncher.getStateManager().getState();
- if (isSplitSelectionState(toState) && !isSplitSelectionState(currentState)) {
- builder.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
- } else if (!isSplitSelectionState(toState) && isSplitSelectionState(currentState)) {
- builder.add(mRecentsView.cancelSplitSelect(true).buildAnim());
- }
+ handleSplitSelectionState(toState, builder);
setAlphas(builder, config, toState);
builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
@@ -95,10 +101,52 @@ public final class RecentsViewStateController extends
}
/**
- * @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
+ * Create or dismiss split screen select animations.
+ * @param builder if null then this will run the split select animations right away, otherwise
+ * will add animations to builder.
*/
- private boolean isSplitSelectionState(@NonNull LauncherState toState) {
- return toState == OVERVIEW_SPLIT_SELECT;
+ private void handleSplitSelectionState(@NonNull LauncherState toState,
+ @Nullable PendingAnimation builder) {
+ LauncherState currentState = mLauncher.getStateManager().getState();
+ boolean animate = builder != null;
+ PagedOrientationHandler orientationHandler =
+ ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
+ Pair taskViewsFloat =
+ orientationHandler.getSplitSelectTaskOffset(
+ TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
+ mLauncher.getDeviceProfile());
+
+ if (isSplitSelectionState(currentState, toState)) {
+ // Animation to "dismiss" selected taskView
+ PendingAnimation splitSelectInitAnimation =
+ mRecentsView.createSplitSelectInitAnimation();
+ // Add properties to shift remaining taskViews to get out of placeholder view
+ splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.first,
+ toState.getSplitSelectTranslation(mLauncher), LINEAR);
+ splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
+
+ if (!animate && isSplitSelectionState(currentState, toState)) {
+ splitSelectInitAnimation.buildAnim().start();
+ } else if (animate &&
+ isSplitSelectionState(currentState, toState)) {
+ builder.add(splitSelectInitAnimation.buildAnim());
+ }
+ }
+
+ if (isSplitSelectionState(currentState, toState)) {
+ mRecentsView.applySplitPrimaryScrollOffset();
+ } else {
+ mRecentsView.resetSplitPrimaryScrollOffset();
+ }
+ }
+
+ /**
+ * @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
+ * and {@param fromState} is not {@link LauncherState#OVERVIEW_SPLIT_SELECT}
+ */
+ private boolean isSplitSelectionState(@NonNull LauncherState fromState,
+ @NonNull LauncherState toState) {
+ return fromState != OVERVIEW_SPLIT_SELECT && toState == OVERVIEW_SPLIT_SELECT;
}
private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
@@ -106,16 +154,10 @@ public final class RecentsViewStateController extends
float clearAllButtonAlpha = state.areElementsVisible(mLauncher, CLEAR_ALL_BUTTON) ? 1 : 0;
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
- float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS)
- && mRecentsView.shouldShowOverviewActionsForState(state) ? 1 : 0;
+ float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0;
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
-
- float splitPlaceholderAlpha = state.areElementsVisible(mLauncher, SPLIT_PLACHOLDER_VIEW) ?
- 0.85f : 0;
- propertySetter.setFloat(mRecentsView.getSplitPlaceholder(), ALPHA_FLOAT,
- splitPlaceholderAlpha, LINEAR);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
deleted file mode 100644
index d14e8efdd6..0000000000
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2018 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.uioverrides.plugins;
-
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.Context;
-import android.os.Looper;
-
-import com.android.launcher3.Utilities;
-import com.android.systemui.shared.plugins.PluginInitializer;
-
-public class PluginInitializerImpl implements PluginInitializer {
- @Override
- public Looper getBgLooper() {
- return MODEL_EXECUTOR.getLooper();
- }
-
- @Override
- public void onPluginManagerInit() {
- }
-
- @Override
- public String[] getWhitelistedPlugins(Context context) {
- return new String[0];
- }
-
- @Override
- public PluginEnablerImpl getPluginEnabler(Context context) {
- return new PluginEnablerImpl(context);
- }
-
- @Override
- public void handleWtfs() {
- }
-
- public boolean isDebuggable() {
- return Utilities.IS_DEBUG_DEVICE;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index 2e422b77f6..df0ac7ce22 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -16,21 +16,29 @@ package com.android.launcher3.uioverrides.plugins;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
+import com.android.launcher3.Utilities;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.shared.plugins.PluginActionManager;
+import com.android.systemui.shared.plugins.PluginInstance;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.plugins.PluginPrefs;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
public class PluginManagerWrapper {
@@ -46,21 +54,36 @@ public class PluginManagerWrapper {
private PluginManagerWrapper(Context c) {
mContext = c;
- PluginInitializerImpl pluginInitializer = new PluginInitializerImpl();
- mPluginManager = new PluginManagerImpl(c, pluginInitializer);
- mPluginEnabler = pluginInitializer.getPluginEnabler(c);
+ mPluginEnabler = new PluginEnablerImpl(c);
+ List privilegedPlugins = Collections.emptyList();
+ PluginInstance.Factory instanceFactory = new PluginInstance.Factory(
+ getClass().getClassLoader(), new PluginInstance.InstanceFactory<>(),
+ new PluginInstance.VersionChecker(), privilegedPlugins,
+ Utilities.IS_DEBUG_DEVICE);
+ PluginActionManager.Factory instanceManagerFactory = new PluginActionManager.Factory(
+ c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR,
+ c.getSystemService(NotificationManager.class), mPluginEnabler,
+ privilegedPlugins, instanceFactory);
+
+ mPluginManager = new PluginManagerImpl(c, instanceManagerFactory,
+ Utilities.IS_DEBUG_DEVICE,
+ Optional.ofNullable(Thread.getDefaultUncaughtExceptionHandler()), mPluginEnabler,
+ new PluginPrefs(c), privilegedPlugins);
}
public PluginEnablerImpl getPluginEnabler() {
return mPluginEnabler;
}
- public void addPluginListener(PluginListener extends Plugin> listener, Class> pluginClass) {
+ /** */
+ public void addPluginListener(
+ PluginListener listener, Class pluginClass) {
addPluginListener(listener, pluginClass, false);
}
- public void addPluginListener(PluginListener extends Plugin> listener, Class> pluginClass,
- boolean allowMultiple) {
+ /** */
+ public void addPluginListener(
+ PluginListener listener, Class pluginClass, boolean allowMultiple) {
mPluginManager.addPluginListener(listener, pluginClass, allowMultiple);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 2cb313e29c..54eb5c37f4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -70,6 +70,11 @@ public class AllAppsState extends LauncherState {
return scaleAndTranslation;
}
+ @Override
+ public boolean isTaskbarStashed(Launcher launcher) {
+ return true;
+ }
+
@Override
protected float getDepthUnchecked(Context context) {
// The scrim fades in at approximately 50% of the swipe gesture.
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index fe5a3475ff..b7330072d4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -23,6 +23,7 @@ import android.graphics.Color;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -73,8 +74,7 @@ public class BackgroundAppState extends OverviewState {
return super.getVisibleElements(launcher)
& ~OVERVIEW_ACTIONS
& ~CLEAR_ALL_BUTTON
- & ~VERTICAL_SWIPE_INDICATOR
- | TASKBAR;
+ & ~VERTICAL_SWIPE_INDICATOR;
}
@Override
@@ -89,6 +89,10 @@ public class BackgroundAppState extends OverviewState {
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ if (dp.isTaskbarPresentInApps) {
+ return launcher.getColor(R.color.taskbar_background);
+ }
return Color.TRANSPARENT;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index bd251ca3c6..8c0d1c46a3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -21,14 +21,11 @@ import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERV
import android.content.Context;
import android.graphics.Rect;
import android.os.SystemProperties;
-import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -69,10 +66,7 @@ public class OverviewState extends LauncherState {
@Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
- Workspace workspace = launcher.getWorkspace();
- View workspacePage = workspace.getPageAt(workspace.getCurrentPage());
- float workspacePageWidth = workspacePage != null && workspacePage.getWidth() != 0
- ? workspacePage.getWidth() : launcher.getDeviceProfile().availableWidthPx;
+ float workspacePageWidth = launcher.getDeviceProfile().getWorkspaceWidth();
recentsView.getTaskSize(sTempRect);
float scale = (float) sTempRect.width() / workspacePageWidth;
float parallaxFactor = 0.5f;
@@ -84,16 +78,6 @@ public class OverviewState extends LauncherState {
return new float[] {NO_SCALE, NO_OFFSET};
}
- @Override
- public float getTaskbarScale(Launcher launcher) {
- return 1f;
- }
-
- @Override
- public float getTaskbarTranslationY(Launcher launcher) {
- return 0f;
- }
-
@Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
return new PageAlphaProvider(DEACCEL_2) {
@@ -112,6 +96,11 @@ public class OverviewState extends LauncherState {
return CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
}
+ @Override
+ public boolean isTaskbarStashed(Launcher launcher) {
+ return true;
+ }
+
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
return ColorTokens.OverviewScrim.resolveColor(launcher);
@@ -119,7 +108,7 @@ public class OverviewState extends LauncherState {
@Override
public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
- return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
+ return deviceProfile.overviewShowAsGrid;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 5a258b8458..24467e194e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -17,7 +17,9 @@ package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
import app.lawnchair.theme.color.ColorTokens;
@@ -42,6 +44,10 @@ public class QuickSwitchState extends BackgroundAppState {
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ if (dp.isTaskbarPresentInApps) {
+ return launcher.getColor(R.color.taskbar_background);
+ }
return ColorTokens.OverviewScrim.resolveColor(launcher);
}
@@ -53,6 +59,16 @@ public class QuickSwitchState extends BackgroundAppState {
@Override
public int getVisibleElements(Launcher launcher) {
- return TASKBAR;
+ return NONE;
+ }
+
+ @Override
+ public boolean isTaskbarStashed(Launcher launcher) {
+ return !launcher.getDeviceProfile().isTaskbarPresentInApps;
+ }
+
+ @Override
+ public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
+ return false;
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 0e2fbbc2bf..75cf5cb3a5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -17,6 +17,7 @@ package com.android.launcher3.uioverrides.states;
import static android.view.View.VISIBLE;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -44,6 +45,10 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD;
+import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD;
+import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_OPAQUE_THRESHOLD;
+import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_VISIBLE_THRESHOLD;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import android.animation.ValueAnimator;
@@ -52,6 +57,7 @@ import com.android.launcher3.CellLayout;
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.SysUINavigationMode;
@@ -171,6 +177,13 @@ public class QuickstepAtomicAnimationFactory extends
mHintToNormalDuration = (int) va.getDuration();
}
config.duration = Math.max(config.duration, mHintToNormalDuration);
+ } else if (fromState == ALL_APPS && toState == NORMAL) {
+ config.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
+ 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
+ 1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
+ config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(DEACCEL,
+ 1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD,
+ 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD));
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 6968494b0f..e79d56b631 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -17,8 +17,6 @@
package com.android.launcher3.uioverrides.states;
import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.views.RecentsView;
/**
@@ -30,11 +28,6 @@ public class SplitScreenSelectState extends OverviewState {
super(id);
}
- @Override
- public void onBackPressed(Launcher launcher) {
- launcher.getStateManager().goToState(OVERVIEW);
- }
-
@Override
public int getVisibleElements(Launcher launcher) {
return SPLIT_PLACHOLDER_VIEW;
@@ -43,13 +36,6 @@ public class SplitScreenSelectState extends OverviewState {
@Override
public float getSplitSelectTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
- int splitPosition = recentsView.getSplitPlaceholder().getSplitController()
- .getActiveSplitPositionOption().mStagePosition;
- if (!recentsView.shouldShiftThumbnailsForSplitSelect(splitPosition)) {
- return 0f;
- }
- PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
- int direction = orientationHandler.getSplitTranslationDirectionFactor(splitPosition);
- return launcher.getResources().getDimension(R.dimen.split_placeholder_size) * direction;
+ return recentsView.getSplitSelectTranslation();
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 283743dc64..ef6f53e8f5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -24,7 +24,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.ObjectAnimator;
@@ -38,11 +38,11 @@ import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.OverviewToHomeAnim;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 40c3e02238..f6148a7c8f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -41,7 +41,7 @@ import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
@@ -67,14 +67,15 @@ import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsView;
/**
* Handles quick switching to a recent task from the home screen. To give as much flexibility to
@@ -235,8 +236,10 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
// - RecentsView fade (if it's empty)
PendingAnimation xAnim = new PendingAnimation((long) (mXRange * 2));
xAnim.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1], LINEAR);
+ // Use QuickSwitchState instead of OverviewState to determine scrim color,
+ // since we need to take potential taskbar into account.
xAnim.setViewBackgroundColor(mLauncher.getScrimView(),
- toState.getWorkspaceScrimColor(mLauncher), LINEAR);
+ QUICK_SWITCH.getWorkspaceScrimColor(mLauncher), LINEAR);
if (mRecentsView.getTaskViewCount() == 0) {
xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR);
}
@@ -309,6 +312,11 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
}
});
overviewAnim.start();
+
+ // Create an empty state transition so StateListeners get onStateTransitionStart().
+ mLauncher.getStateManager().createAnimationToNewWorkspace(
+ OVERVIEW, config.duration, StateAnimationConfig.SKIP_ALL_ANIMATIONS)
+ .dispatchOnStart();
return;
}
@@ -383,6 +391,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
config.animFlags = SKIP_ALL_ANIMATIONS;
updateNonOverviewAnim(targetState, config);
nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
+ mNonOverviewAnim.dispatchOnStart();
new WorkspaceRevealAnim(mLauncher, false /* animateOverviewScrim */).start();
} else {
@@ -398,6 +407,14 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
nonOverviewAnim.setFloatValues(startProgress, endProgress);
mNonOverviewAnim.dispatchOnStart();
}
+ if (targetState == QUICK_SWITCH) {
+ // Navigating to quick switch, add scroll feedback since the first time is not
+ // considered a scroll by the RecentsView.
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(
+ RecentsView.SCROLL_VIBRATION_PRIMITIVE,
+ RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
+ RecentsView.SCROLL_VIBRATION_FALLBACK);
+ }
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 3c83d25b71..59ade49761 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -52,22 +52,22 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
/**
* The progress at which all apps content will be fully visible.
*/
- protected static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f;
+ public static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f;
/**
* Minimum clamping progress for fading in all apps content
*/
- protected static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f;
+ public static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f;
/**
* Minimum clamping progress for fading in all apps scrim
*/
- protected static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = .1f;
+ public static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = .1f;
/**
* Maximum clamping progress for opaque all apps scrim
*/
- protected static final float ALL_APPS_SCRIM_OPAQUE_THRESHOLD = .5f;
+ public static final float ALL_APPS_SCRIM_OPAQUE_THRESHOLD = .5f;
private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index 55dc7f7318..254f8c5e84 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -21,6 +21,7 @@ import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN;
@@ -53,17 +54,6 @@ public class StatusBarTouchController implements TouchController {
private static final String TAG = "StatusBarController";
- /**
- * Window flag: Enable touches to slide out of a window into neighboring
- * windows in mid-gesture instead of being captured for the duration of
- * the gesture.
- *
- * This flag changes the behavior of touch focus for this window only.
- * Touches can slide out of the window but they cannot necessarily slide
- * back in (unless the other window with touch focus permits it).
- */
- private static final int FLAG_SLIPPERY = 0x20000000;
-
private final Launcher mLauncher;
private final SystemUiProxy mSystemUiProxy;
private final float mTouchSlop;
@@ -165,6 +155,15 @@ public class StatusBarTouchController implements TouchController {
return true;
}
+ /**
+ * FLAG_SLIPPERY enables touches to slide out of a window into neighboring
+ * windows in mid-gesture instead of being captured for the duration of
+ * the gesture.
+ *
+ * This flag changes the behavior of touch focus for this window only.
+ * Touches can slide out of the window but they cannot necessarily slide
+ * back in (unless the other window with touch focus permits it).
+ */
private void setWindowSlippery(boolean enable) {
Window w = mLauncher.getWindow();
WindowManager.LayoutParams wlp = w.getAttributes();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 180af0bd9f..308bca62e4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -22,6 +22,7 @@ import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.os.SystemClock;
+import android.os.VibrationEffect;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -34,7 +35,6 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
@@ -42,6 +42,7 @@ import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -56,6 +57,12 @@ public abstract class TaskViewTouchController
private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
private static final long MAX_TASK_DISMISS_ANIMATION_DURATION = 600;
+ public static final int TASK_DISMISS_VIBRATION_PRIMITIVE =
+ Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1;
+ public static final float TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE = 1f;
+ public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK =
+ VibratorWrapper.EFFECT_TEXTURE_TICK;
+
protected final T mActivity;
private final SingleAxisSwipeDetector mDetector;
private final RecentsView mRecentsView;
@@ -77,6 +84,8 @@ public abstract class TaskViewTouchController
private TaskView mTaskBeingDragged;
+ private boolean mIsDismissHapticRunning = false;
+
public TaskViewTouchController(T activity) {
mActivity = activity;
mRecentsView = activity.getOverviewPanel();
@@ -158,26 +167,21 @@ public abstract class TaskViewTouchController
mTaskBeingDragged = view;
int upDirection = mRecentsView.getPagedOrientationHandler()
.getUpDirection(mIsRtl);
- if (!SysUINavigationMode.getMode(mActivity).hasGestures || (
- mActivity.getDeviceProfile().isTablet
- && FeatureFlags.ENABLE_OVERVIEW_GRID.get())) {
- // Don't allow swipe down to open if we don't support swipe up
- // to enter overview, or when grid layout is enabled.
- directionsToDetectScroll = upDirection;
- mAllowGoingUp = true;
- mAllowGoingDown = false;
- } else {
- // The task can be dragged up to dismiss it,
- // and down to open if it's the current page.
- mAllowGoingUp = true;
- if (i == mRecentsView.getCurrentPage()) {
- mAllowGoingDown = true;
- directionsToDetectScroll = DIRECTION_BOTH;
- } else {
- mAllowGoingDown = false;
- directionsToDetectScroll = upDirection;
- }
- }
+
+ // The task can be dragged up to dismiss it
+ mAllowGoingUp = true;
+
+ // The task can be dragged down to open it if:
+ // - It's the current page
+ // - We support gestures to enter overview
+ // - It's the focused task if in grid view
+ // - The task is snapped
+ mAllowGoingDown = i == mRecentsView.getCurrentPage()
+ && SysUINavigationMode.getMode(mActivity).hasGestures
+ && (!mRecentsView.showAsGrid() || mTaskBeingDragged.isFocusedTask())
+ && mRecentsView.isTaskInExpectedScrollPosition(i);
+
+ directionsToDetectScroll = mAllowGoingDown ? DIRECTION_BOTH : upDirection;
break;
}
}
@@ -233,7 +237,8 @@ public abstract class TaskViewTouchController
if (goingUp) {
currentInterpolator = Interpolators.LINEAR;
pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
- true /* animateTaskView */, true /* removeTask */, maxDuration);
+ true /* animateTaskView */, true /* removeTask */, maxDuration,
+ false /* dismissingForSplitSelection*/);
mEndDisplacement = -secondaryTaskDimension;
} else {
@@ -339,10 +344,10 @@ public abstract class TaskViewTouchController
fling = false;
}
PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
+ boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
float progress = mCurrentAnimation.getProgressFraction();
float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
if (fling) {
- boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
} else {
goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
@@ -362,6 +367,11 @@ public abstract class TaskViewTouchController
mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
velocity * orientationHandler.getSecondaryTranslationDirectionFactor(),
mEndDisplacement, animationDuration);
+ if (goingUp && goingToEnd && !mIsDismissHapticRunning) {
+ VibratorWrapper.INSTANCE.get(mActivity).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE,
+ TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE, TASK_DISMISS_VIBRATION_FALLBACK);
+ mIsDismissHapticRunning = true;
+ }
}
private void clearState() {
@@ -369,5 +379,6 @@ public abstract class TaskViewTouchController
mDetector.setDetectableScrollConditions(0, false);
mTaskBeingDragged = null;
mCurrentAnimation = null;
+ mIsDismissHapticRunning = false;
}
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index d8e90af73a..06ad1c03b8 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -36,7 +36,6 @@ import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -46,6 +45,7 @@ import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
@@ -55,6 +55,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
+import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
@@ -73,6 +74,7 @@ import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.WindowInsets;
import android.view.animation.Interpolator;
import android.widget.Toast;
+import android.window.PictureInPictureSurfaceTransaction;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -90,16 +92,18 @@ import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
+import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.InputProxyHandlerFactory;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.RecentsOrientedState;
@@ -107,7 +111,8 @@ import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
-import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -120,6 +125,8 @@ import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.function.Consumer;
import app.lawnchair.util.CompatibilityKt;
@@ -152,6 +159,17 @@ public abstract class AbsSwipeUpHandler,
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
private boolean mRecentsViewScrollLinked = false;
+ private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
+ new ActivityLifecycleCallbacksAdapter() {
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ if (mActivity != activity) {
+ return;
+ }
+ mRecentsView = null;
+ mActivity = null;
+ }
+ };
private static int getFlagForIndex(int index, String name) {
if (DEBUG_STATES) {
@@ -165,45 +183,51 @@ public abstract class AbsSwipeUpHandler,
getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
protected static final int STATE_LAUNCHER_STARTED =
getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
- protected static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
+ protected static final int STATE_LAUNCHER_DRAWN =
+ getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
+ // Called when the Launcher has connected to the touch interaction service (and the taskbar
+ // ui controller is initialized)
+ protected static final int STATE_LAUNCHER_BIND_TO_SERVICE =
+ getFlagForIndex(3, "STATE_LAUNCHER_BIND_TO_SERVICE");
// Internal initialization states
private static final int STATE_APP_CONTROLLER_RECEIVED =
- getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
+ getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
// Interaction finish states
private static final int STATE_SCALED_CONTROLLER_HOME =
- getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
+ getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME");
private static final int STATE_SCALED_CONTROLLER_RECENTS =
- getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
+ getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS");
protected static final int STATE_HANDLER_INVALIDATED =
- getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
+ getFlagForIndex(7, "STATE_HANDLER_INVALIDATED");
private static final int STATE_GESTURE_STARTED =
- getFlagForIndex(7, "STATE_GESTURE_STARTED");
+ getFlagForIndex(8, "STATE_GESTURE_STARTED");
private static final int STATE_GESTURE_CANCELLED =
- getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
+ getFlagForIndex(9, "STATE_GESTURE_CANCELLED");
private static final int STATE_GESTURE_COMPLETED =
- getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
+ getFlagForIndex(10, "STATE_GESTURE_COMPLETED");
private static final int STATE_CAPTURE_SCREENSHOT =
- getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
+ getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT");
protected static final int STATE_SCREENSHOT_CAPTURED =
- getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
+ getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED");
private static final int STATE_SCREENSHOT_VIEW_SHOWN =
- getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
+ getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN");
private static final int STATE_RESUME_LAST_TASK =
- getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
+ getFlagForIndex(14, "STATE_RESUME_LAST_TASK");
private static final int STATE_START_NEW_TASK =
- getFlagForIndex(14, "STATE_START_NEW_TASK");
+ getFlagForIndex(15, "STATE_START_NEW_TASK");
private static final int STATE_CURRENT_TASK_FINISHED =
- getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
+ getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED");
private static final int STATE_FINISH_WITH_NO_END =
- getFlagForIndex(16, "STATE_FINISH_WITH_NO_END");
+ getFlagForIndex(17, "STATE_FINISH_WITH_NO_END");
private static final int LAUNCHER_UI_STATES =
- STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
+ STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED |
+ STATE_LAUNCHER_BIND_TO_SERVICE;
public static final long MAX_SWIPE_DURATION = 350;
public static final long HOME_DURATION = StaggeredWorkspaceAnim.DURATION_MS;
@@ -215,6 +239,8 @@ public abstract class AbsSwipeUpHandler,
public static final long RECENTS_ATTACH_DURATION = 300;
+ private static final float MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS = 0.07f;
+
/**
* Used as the page index for logging when we return to the last task at the end of the gesture.
*/
@@ -223,7 +249,7 @@ public abstract class AbsSwipeUpHandler,
protected final TaskAnimationManager mTaskAnimationManager;
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
- private RunningWindowAnim mRunningWindowAnim;
+ private RunningWindowAnim[] mRunningWindowAnim;
// Possible second animation running at the same time as mRunningWindowAnim
private Animator mParallelRunningAnim;
private boolean mIsMotionPaused;
@@ -254,22 +280,33 @@ public abstract class AbsSwipeUpHandler,
private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
protected boolean mIsSwipingPipToHome;
+ // TODO(b/195473090) no split PIP for now, remove once we have more clarity
+ // can try to have RectFSpringAnim evaluate multiple rects at once
+ private final SwipePipToHomeAnimator[] mSwipePipToHomeAnimators =
+ new SwipePipToHomeAnimator[2];
+
+ // Interpolate RecentsView scale from start of quick switch scroll until this scroll threshold
+ private final float mQuickSwitchScaleScrollThreshold;
public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture,
InputConsumerController inputConsumer) {
- super(context, deviceState, gestureState, new TransformParams());
+ super(context, deviceState, gestureState);
mActivityInterface = gestureState.getActivityInterface();
mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
mInputConsumerProxy =
- new InputConsumerProxy(inputConsumer, () -> {
+ new InputConsumerProxy(context,
+ () -> mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation(),
+ inputConsumer, () -> {
endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
endLauncherTransitionController();
}, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
mTaskAnimationManager = taskAnimationManager;
mTouchTimeMs = touchTimeMs;
mContinuingLastGesture = continuingLastGesture;
+ mQuickSwitchScaleScrollThreshold = context.getResources().getDimension(
+ R.dimen.quick_switch_scaling_scroll_threshold);
initAfterSubclassConstructor();
initStateCallbacks();
@@ -387,9 +424,10 @@ public abstract class AbsSwipeUpHandler,
// Set up a entire animation lifecycle callback to notify the current recents view when
// the animation is canceled
mGestureState.runOnceAtState(STATE_RECENTS_ANIMATION_CANCELED, () -> {
- ThumbnailData snapshot = mGestureState.consumeRecentsAnimationCanceledSnapshot();
- if (snapshot != null) {
- mRecentsView.switchToScreenshot(snapshot, () -> {
+ HashMap snapshots =
+ mGestureState.consumeRecentsAnimationCanceledSnapshot();
+ if (snapshots != null) {
+ mRecentsView.switchToScreenshot(snapshots, () -> {
if (mRecentsAnimationController != null) {
mRecentsAnimationController.cleanupScreenshot();
}
@@ -400,7 +438,9 @@ public abstract class AbsSwipeUpHandler,
setupRecentsViewUi();
linkRecentsViewScroll();
+ activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
+ mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
return true;
}
@@ -422,7 +462,8 @@ public abstract class AbsSwipeUpHandler,
// RecentsView never updates the display rotation until swipe-up, force update
// RecentsOrientedState before passing to TaskViewSimulator.
mRecentsView.updateRecentsRotation();
- mTaskViewSimulator.setOrientationState(mRecentsView.getPagedViewOrientedState());
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+ .setOrientationState(mRecentsView.getPagedViewOrientedState()));
// If we've already ended the gesture and are going home, don't prepare recents UI,
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
@@ -431,6 +472,10 @@ public abstract class AbsSwipeUpHandler,
mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
maybeUpdateRecentsAttachedState(false /* animate */);
+ if (mGestureState.getEndTarget() != null) {
+ // Update the end target in case the gesture ended before we init.
+ mAnimationFactory.setEndTarget(mGestureState.getEndTarget());
+ }
};
if (mWasLauncherAlreadyVisible) {
// Launcher is visible, but might be about to stop. Thus, if we prepare recents
@@ -475,6 +520,11 @@ public abstract class AbsSwipeUpHandler,
mStateCallback.setState(STATE_LAUNCHER_STARTED);
}
+ private void onLauncherBindToService() {
+ mStateCallback.setState(STATE_LAUNCHER_BIND_TO_SERVICE);
+ flushOnRecentsAnimationAndLauncherBound();
+ }
+
private void onLauncherPresentAndGestureStarted() {
// Re-setup the recents UI when gesture starts, as the state could have been changed during
// that time by a previous window transition.
@@ -515,7 +565,26 @@ public abstract class AbsSwipeUpHandler,
}
protected void notifyGestureAnimationStartToRecents() {
- mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
+ ActivityManager.RunningTaskInfo[] runningTasks;
+ if (mIsSwipeForStagedSplit) {
+ int[] splitTaskIds =
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
+ runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length];
+ for (int i = 0; i < splitTaskIds.length; i++) {
+ int taskId = splitTaskIds[i];
+ // Order matters here, we want first indexed RunningTaskInfo to be leftTop task
+ for (ActivityManager.RunningTaskInfo rti : mGestureState.getRunningTasks()) {
+ if (taskId == rti.taskId) {
+ runningTasks[i] = rti;
+ break;
+ }
+
+ }
+ }
+ } else {
+ runningTasks = new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()};
+ }
+ mRecentsView.onGestureAnimationStart(runningTasks);
}
private void launcherFrameDrawn() {
@@ -547,7 +616,7 @@ public abstract class AbsSwipeUpHandler,
@Override
public void onMotionPauseDetected() {
mHasMotionEverBeenPaused = true;
- maybeUpdateRecentsAttachedState();
+ maybeUpdateRecentsAttachedState(true/* animate */, true/* moveFocusedTask */);
performHapticFeedback();
}
@@ -558,18 +627,24 @@ public abstract class AbsSwipeUpHandler,
};
}
- public void maybeUpdateRecentsAttachedState() {
+ private void maybeUpdateRecentsAttachedState() {
maybeUpdateRecentsAttachedState(true /* animate */);
}
+ private void maybeUpdateRecentsAttachedState(boolean animate) {
+ maybeUpdateRecentsAttachedState(animate, false /* moveFocusedTask */);
+ }
+
/**
* Determines whether to show or hide RecentsView. The window is always
* synchronized with its corresponding TaskView in RecentsView, so if
* RecentsView is shown, it will appear to be attached to the window.
*
* Note this method has no effect unless the navigation mode is NO_BUTTON.
+ * @param animate whether to animate when attaching RecentsView
+ * @param moveFocusedTask whether to move focused task to front when attaching
*/
- private void maybeUpdateRecentsAttachedState(boolean animate) {
+ private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveFocusedTask) {
if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
return;
}
@@ -588,6 +663,12 @@ public abstract class AbsSwipeUpHandler,
} else {
recentsAttachedToAppWindow = mHasMotionEverBeenPaused || mIsLikelyToStartNewTask;
}
+ if (moveFocusedTask && !mAnimationFactory.hasRecentsEverAttachedToAppWindow()
+ && recentsAttachedToAppWindow) {
+ // Only move focused task if RecentsView has never been attached before, to avoid
+ // TaskView jumping to new position as we move the tasks.
+ mRecentsView.moveFocusedTaskToFront();
+ }
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
// Reapply window transform throughout the attach animation, as the animation affects how
@@ -595,15 +676,15 @@ public abstract class AbsSwipeUpHandler,
if (animate) {
ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1);
reapplyWindowTransformAnim.addUpdateListener(anim -> {
- if (mRunningWindowAnim == null) {
- applyWindowTransform();
+ if (mRunningWindowAnim == null || mRunningWindowAnim.length == 0) {
+ applyScrollAndTransform();
}
});
reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start();
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
reapplyWindowTransformAnim::cancel);
} else {
- applyWindowTransform();
+ applyScrollAndTransform();
}
}
@@ -639,13 +720,24 @@ public abstract class AbsSwipeUpHandler,
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
WindowInsets result = view.onApplyWindowInsets(windowInsets);
buildAnimationController();
+ // Reapply the current shift to ensure it takes new insets into account, e.g. when long
+ // pressing to stash taskbar without moving the finger.
+ updateFinalShift();
return result;
}
private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
+ boolean isFirstCreation = mLauncherTransitionController == null;
mLauncherTransitionController = anim;
- mLauncherTransitionController.getNormalController().dispatchOnStart();
- updateLauncherTransitionProgress();
+ if (isFirstCreation) {
+ mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
+ // Wait until the gesture is started (touch slop was passed) to start in sync with
+ // mWindowTransitionController. This ensures we don't hide the taskbar background
+ // when long pressing to stash it, for instance.
+ mLauncherTransitionController.getNormalController().dispatchOnStart();
+ updateLauncherTransitionProgress();
+ });
+ }
}
public Intent getLaunchIntent() {
@@ -667,7 +759,7 @@ public abstract class AbsSwipeUpHandler,
}
updateSysUiFlags(mCurrentShift.value);
- applyWindowTransform();
+ applyScrollAndTransform();
updateLauncherTransitionProgress();
}
@@ -677,7 +769,8 @@ public abstract class AbsSwipeUpHandler,
|| !canCreateNewOrUpdateExistingLauncherTransitionController()) {
return;
}
- mLauncherTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
+ mLauncherTransitionController.setProgress(
+ Math.max(mCurrentShift.value, getScaleProgressDueToScroll()), mDragLengthFactor);
}
/**
@@ -712,24 +805,25 @@ public abstract class AbsSwipeUpHandler,
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
+ super.onRecentsAnimationStart(controller, targets);
ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+ mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
mRecentsAnimationController = controller;
mRecentsAnimationTargets = targets;
- mTransformParams.setTargetSet(mRecentsAnimationTargets);
- RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
- mGestureState.getRunningTaskId());
-
- if (runningTaskTarget != null) {
- mTaskViewSimulator.setPreview(runningTaskTarget);
- }
// Only initialize the device profile, if it has not been initialized before, as in some
// configurations targets.homeContentInsets may not be correct.
if (mActivity == null) {
- DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
- if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+ RemoteAnimationTargetCompat primaryTaskTarget = targets.apps[0];
+ // orientation state is independent of which remote target handle we use since both
+ // should be pointing to the same one. Just choose index 0 for now since that works for
+ // both split and non-split
+ RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState();
+ DeviceProfile dp = orientationState.getLauncherDeviceProfile();
+ if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) {
Rect overviewStackBounds = mActivityInterface
- .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+ .getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget);
dp = dp.getMultiWindowProfile(mContext,
new WindowBounds(overviewStackBounds, targets.homeContentInsets));
} else {
@@ -739,16 +833,14 @@ public abstract class AbsSwipeUpHandler,
dp.updateInsets(targets.homeContentInsets);
dp.updateIsSeascape(mContext);
initTransitionEndpoints(dp);
- mTaskViewSimulator.getOrientationState().setMultiWindowMode(dp.isMultiWindowMode);
+ orientationState.setMultiWindowMode(dp.isMultiWindowMode);
}
// Notify when the animation starts
- if (!mRecentsAnimationStartCallbacks.isEmpty()) {
- for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
- action.run();
- }
- mRecentsAnimationStartCallbacks.clear();
- }
+ flushOnRecentsAnimationAndLauncherBound();
+
+ TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
+ false /*shown*/, true /*animate*/);
// Only add the callback to enable the input consumer after we actually have the controller
mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
@@ -759,11 +851,16 @@ public abstract class AbsSwipeUpHandler,
}
@Override
- public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ public void onRecentsAnimationCanceled(HashMap thumbnailDatas) {
ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
mActivityInitListener.unregister();
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
+ if (mRecentsAnimationTargets != null) {
+ TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
+ true /*shown*/, true /*animate*/);
+ }
+
// Defer clearing the controller and the targets until after we've updated the state
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
@@ -853,9 +950,17 @@ public abstract class AbsSwipeUpHandler,
private void endRunningWindowAnim(boolean cancel) {
if (mRunningWindowAnim != null) {
if (cancel) {
- mRunningWindowAnim.cancel();
+ for (RunningWindowAnim r : mRunningWindowAnim) {
+ if (r != null) {
+ r.cancel();
+ }
+ }
} else {
- mRunningWindowAnim.end();
+ for (RunningWindowAnim r : mRunningWindowAnim) {
+ if (r != null) {
+ r.end();
+ }
+ }
}
}
if (mParallelRunningAnim != null) {
@@ -869,6 +974,9 @@ public abstract class AbsSwipeUpHandler,
// Fast-finish the attaching animation if it's still running.
maybeUpdateRecentsAttachedState(false);
final GestureEndTarget endTarget = mGestureState.getEndTarget();
+ // Wait until the given View (if supplied) draws before resuming the last task.
+ View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
+
if (endTarget != NEW_TASK) {
InteractionJankMonitorWrapper.cancel(
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
@@ -877,11 +985,13 @@ public abstract class AbsSwipeUpHandler,
InteractionJankMonitorWrapper.cancel(
InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
}
+
switch (endTarget) {
case HOME:
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify swipe-to-home (recents animation) is finished
SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
break;
case RECENTS:
mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -891,19 +1001,29 @@ public abstract class AbsSwipeUpHandler,
mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
break;
case LAST_TASK:
- mStateCallback.setState(STATE_RESUME_LAST_TASK);
+ if (postResumeLastTask != null) {
+ ViewUtils.postFrameDrawn(postResumeLastTask,
+ () -> mStateCallback.setState(STATE_RESUME_LAST_TASK));
+ } else {
+ mStateCallback.setState(STATE_RESUME_LAST_TASK);
+ }
+ if (mRecentsAnimationTargets != null) {
+ TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
+ true /*shown*/, false /*animate*/);
+ }
break;
}
ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + endTarget);
}
/** @return Whether this was the task we were waiting to appear, and thus handled it. */
- protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ protected boolean handleTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTarget) {
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
return false;
}
- if (mStateCallback.hasStates(STATE_START_NEW_TASK)
- && appearedTaskTarget.taskId == mGestureState.getLastStartedTaskId()) {
+ boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch(
+ targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
+ if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) {
reset();
return true;
}
@@ -984,6 +1104,7 @@ public abstract class AbsSwipeUpHandler,
isFling, isCancel);
// Set the state, but don't notify until the animation completes
mGestureState.setEndTarget(endTarget, false /* isAtomic */);
+ mAnimationFactory.setEndTarget(endTarget);
float endShift = endTarget.isLauncher ? 1 : 0;
final float startShift;
@@ -1028,9 +1149,6 @@ public abstract class AbsSwipeUpHandler,
if (mRecentsView != null) {
int nearestPage = mRecentsView.getDestinationPage();
boolean isScrolling = false;
- // Update page scroll before snapping to page to make sure we snapped to the
- // position calculated with target gesture in mind.
- mRecentsView.updateScrollSynchronously();
if (mRecentsView.getNextPage() != nearestPage) {
// We shouldn't really scroll to the next page when swiping up to recents.
// Only allow settling on the next page if it's nearest to the center.
@@ -1099,7 +1217,7 @@ public abstract class AbsSwipeUpHandler,
@UiThread
private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
GestureEndTarget target, PointF velocityPxPerMs) {
- runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
+ runOnRecentsAnimationAndLauncherBound(() -> animateToProgressInternal(start, end, duration,
interpolator, target, velocityPxPerMs));
}
@@ -1136,8 +1254,15 @@ public abstract class AbsSwipeUpHandler,
mActivityRestartListener);
mParallelRunningAnim = mActivityInterface.getParallelAnimationToLauncher(
- mGestureState.getEndTarget(), duration);
+ mGestureState.getEndTarget(), duration,
+ mTaskAnimationManager.getCurrentCallbacks());
if (mParallelRunningAnim != null) {
+ mParallelRunningAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mParallelRunningAnim = null;
+ }
+ });
mParallelRunningAnim.start();
}
}
@@ -1154,22 +1279,25 @@ public abstract class AbsSwipeUpHandler,
boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent;
boolean appCanEnterPip = !mDeviceState.isPipActive()
&& runningTaskTarget != null
- && runningTaskTarget.taskInfo != null
+ && runningTaskTarget.allowEnterPip
&& runningTaskTarget.taskInfo.pictureInPictureParams != null
&& runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
HomeAnimationFactory homeAnimFactory =
createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
runningTaskTarget);
- mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome() && appCanEnterPip;
- final RectFSpringAnim windowAnim;
+ mIsSwipingPipToHome = !mIsSwipeForStagedSplit
+ && homeAnimFactory.supportSwipePipToHome() && appCanEnterPip;
+ final RectFSpringAnim[] windowAnim;
if (mIsSwipingPipToHome) {
mSwipePipToHomeAnimator = createWindowAnimationToPip(
homeAnimFactory, runningTaskTarget, start);
- windowAnim = mSwipePipToHomeAnimator;
+ mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator;
+ windowAnim = mSwipePipToHomeAnimators;
} else {
mSwipePipToHomeAnimator = null;
windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
- windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+
+ windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (mRecentsAnimationController == null) {
@@ -1183,14 +1311,22 @@ public abstract class AbsSwipeUpHandler,
}
});
}
- windowAnim.start(mContext, velocityPxPerMs);
- mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
+ mRunningWindowAnim = new RunningWindowAnim[windowAnim.length];
+ for (int i = 0, windowAnimLength = windowAnim.length; i < windowAnimLength; i++) {
+ RectFSpringAnim windowAnimation = windowAnim[i];
+ if (windowAnimation == null) {
+ continue;
+ }
+ windowAnimation.start(mContext, velocityPxPerMs);
+ mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation);
+ }
homeAnimFactory.setSwipeVelocity(velocityPxPerMs.y);
homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
mLauncherTransitionController = null;
if (mRecentsView != null) {
- mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget());
+ mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(),
+ getRemoteTaskViewSimulators());
}
} else {
AnimatorSet animatorSet = new AnimatorSet();
@@ -1232,24 +1368,42 @@ public abstract class AbsSwipeUpHandler,
animatorSet.play(windowAnim);
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(
- animatorSet, mGestureState.getEndTarget());
+ animatorSet, mGestureState.getEndTarget(),
+ getRemoteTaskViewSimulators());
}
animatorSet.setDuration(duration).setInterpolator(interpolator);
animatorSet.start();
- mRunningWindowAnim = RunningWindowAnim.wrap(animatorSet);
+ mRunningWindowAnim = new RunningWindowAnim[]{RunningWindowAnim.wrap(animatorSet)};
}
}
+ private int calculateWindowRotation(RemoteAnimationTargetCompat runningTaskTarget,
+ RecentsOrientedState orientationState) {
+ if (runningTaskTarget.rotationChange != 0
+ && TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ return Math.abs(runningTaskTarget.rotationChange) == ROTATION_90
+ ? ROTATION_270 : ROTATION_90;
+ } else {
+ return orientationState.getDisplayRotation();
+ }
+ }
+
+ /**
+ * TODO(b/195473090) handle multiple task simulators (if needed) for PIP
+ */
private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
// Directly animate the app to PiP (picture-in-picture) mode
final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
- final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
- final int windowRotation = orientationState.getDisplayRotation();
+ final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState();
+ final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
final int homeRotation = orientationState.getRecentsActivityRotation();
- final Matrix homeToWindowPositionMap = new Matrix();
- final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
+ final Matrix[] homeToWindowPositionMaps = new Matrix[mRemoteTargetHandles.length];
+ final RectF startRect = updateProgressForStartRect(homeToWindowPositionMaps,
+ startProgress)[0];
+ final Matrix homeToWindowPositionMap = homeToWindowPositionMaps[0];
// Move the startRect to Launcher space as floatingIconView runs in Launcher
final Matrix windowToHomePositionMap = new Matrix();
homeToWindowPositionMap.invert(windowToHomePositionMap);
@@ -1278,7 +1432,7 @@ public abstract class AbsSwipeUpHandler,
// is not ROTATION_0 (which implies the rotation is turned on in launcher settings).
if (homeRotation == ROTATION_0
&& (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) {
- builder.setFromRotation(mTaskViewSimulator, windowRotation,
+ builder.setFromRotation(mRemoteTargetHandles[0].getTaskViewSimulator(), windowRotation,
taskInfo.displayCutoutInsets);
}
final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build();
@@ -1308,7 +1462,7 @@ public abstract class AbsSwipeUpHandler,
mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
});
- setupWindowAnimation(swipePipToHomeAnimator);
+ setupWindowAnimation(new RectFSpringAnim[]{swipePipToHomeAnimator});
return swipePipToHomeAnimator;
}
@@ -1335,19 +1489,19 @@ public abstract class AbsSwipeUpHandler,
* @param homeAnimationFactory The home animation factory.
*/
@Override
- protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
+ protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
- RectFSpringAnim anim =
+ RectFSpringAnim[] anim =
super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
setupWindowAnimation(anim);
return anim;
}
- private void setupWindowAnimation(RectFSpringAnim anim) {
- anim.addOnUpdateListener((v, r, p) -> {
+ private void setupWindowAnimation(RectFSpringAnim[] anims) {
+ anims[0].addOnUpdateListener((r, p) -> {
updateSysUiFlags(Math.max(p, mCurrentShift.value));
});
- anim.addAnimatorListener(new AnimationSuccessListener() {
+ anims[0].addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (mRecentsView != null) {
@@ -1359,7 +1513,7 @@ public abstract class AbsSwipeUpHandler,
}
});
if (mRecentsAnimationTargets != null) {
- mRecentsAnimationTargets.addReleaseCheck(anim);
+ mRecentsAnimationTargets.addReleaseCheck(anims[0]);
}
}
@@ -1370,7 +1524,9 @@ public abstract class AbsSwipeUpHandler,
mActivity.clearRunOnceOnStartCallback();
resetLauncherListeners();
}
- if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
+ if (mGestureState.isRecentsAnimationRunning() && mGestureState.getEndTarget() != null
+ && !mGestureState.getEndTarget().isLauncher) {
+ // Continued quick switch.
cancelCurrentAnimation();
} else {
mStateCallback.setStateOnUiThread(STATE_FINISH_WITH_NO_END);
@@ -1436,6 +1592,9 @@ public abstract class AbsSwipeUpHandler,
private void reset() {
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+ if (mActivity != null) {
+ mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
+ }
}
/**
@@ -1513,6 +1672,11 @@ public abstract class AbsSwipeUpHandler,
boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
+ if (mRecentsAnimationTargets != null) {
+ TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
+ true /*shown*/, false /*animate*/);
+ }
+
// Leave the pending invisible flag, as it may be used by wallpaper open animation.
if (mActivity != null) {
mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
@@ -1601,7 +1765,7 @@ public abstract class AbsSwipeUpHandler,
// If there are no targets or the animation not started, then there is nothing to finish
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
- maybeFinishSwipePipToHome();
+ maybeFinishSwipeToHome();
finishRecentsControllerToHome(
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
@@ -1610,11 +1774,12 @@ public abstract class AbsSwipeUpHandler,
}
/**
- * Resets the {@link #mIsSwipingPipToHome} and notifies SysUI that transition is finished
- * if applicable. This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
+ * Notifies SysUI that transition is finished if applicable and also pass leash transactions
+ * from Launcher to WM.
+ * This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
*/
- private void maybeFinishSwipePipToHome() {
- if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
+ private void maybeFinishSwipeToHome() {
+ if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
mSwipePipToHomeAnimator.getComponentName(),
mSwipePipToHomeAnimator.getDestinationBounds(),
@@ -1624,6 +1789,18 @@ public abstract class AbsSwipeUpHandler,
mSwipePipToHomeAnimator.getFinishTransaction(),
mSwipePipToHomeAnimator.getContentOverlay());
mIsSwipingPipToHome = false;
+ } else if (mIsSwipeForStagedSplit) {
+ // Transaction to hide the task to avoid flicker for entering PiP from split-screen.
+ PictureInPictureSurfaceTransaction tx =
+ new PictureInPictureSurfaceTransaction.Builder()
+ .setAlpha(0f)
+ .build();
+ int[] taskIds =
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
+ for (int taskId : taskIds) {
+ mRecentsAnimationController.setFinishTaskTransaction(taskId,
+ tx, null /* overlay */);
+ }
}
}
@@ -1636,7 +1813,10 @@ public abstract class AbsSwipeUpHandler,
endLauncherTransitionController();
mRecentsView.onSwipeUpAnimationSuccess();
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- mTaskAnimationManager.setLiveTileCleanUpHandler(mInputConsumerProxy::destroy);
+ mTaskAnimationManager.setLiveTileCleanUpHandler(() -> {
+ mRecentsView.cleanupRemoteTargets();
+ mInputConsumerProxy.destroy();
+ });
mTaskAnimationManager.enableLiveTileRestartListener();
}
@@ -1655,8 +1835,8 @@ public abstract class AbsSwipeUpHandler,
* depend on proper class initialization.
*/
protected void initAfterSubclassConstructor() {
- initTransitionEndpoints(
- mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
+ initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState().getLauncherDeviceProfile());
}
protected void performHapticFeedback() {
@@ -1673,13 +1853,14 @@ public abstract class AbsSwipeUpHandler,
protected void linkRecentsViewScroll() {
SurfaceTransactionApplier.create(mRecentsView, applier -> {
- mTransformParams.setSyncTransactionApplier(applier);
- runOnRecentsAnimationStart(() ->
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
+ .setSyncTransactionApplier(applier));
+ runOnRecentsAnimationAndLauncherBound(() ->
mRecentsAnimationTargets.addReleaseCheck(applier));
});
mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener);
- runOnRecentsAnimationStart(() ->
+ runOnRecentsAnimationAndLauncherBound(() ->
mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
mRecentsAnimationTargets));
mRecentsViewScrollLinked = true;
@@ -1725,14 +1906,26 @@ public abstract class AbsSwipeUpHandler,
}
/**
- * Runs the given {@param action} if the recents animation has already started, or queues it to
- * be run when it is next started.
+ * Runs the given {@param action} if the recents animation has already started and Launcher has
+ * been created and bound to the TouchInteractionService, or queues it to be run when it this
+ * next happens.
*/
- protected void runOnRecentsAnimationStart(Runnable action) {
- if (mRecentsAnimationTargets == null) {
- mRecentsAnimationStartCallbacks.add(action);
- } else {
- action.run();
+ private void runOnRecentsAnimationAndLauncherBound(Runnable action) {
+ mRecentsAnimationStartCallbacks.add(action);
+ flushOnRecentsAnimationAndLauncherBound();
+ }
+
+ private void flushOnRecentsAnimationAndLauncherBound() {
+ if (mRecentsAnimationTargets == null ||
+ !mStateCallback.hasStates(STATE_LAUNCHER_BIND_TO_SERVICE)) {
+ return;
+ }
+
+ if (!mRecentsAnimationStartCallbacks.isEmpty()) {
+ for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
+ action.run();
+ }
+ mRecentsAnimationStartCallbacks.clear();
}
}
@@ -1746,6 +1939,10 @@ public abstract class AbsSwipeUpHandler,
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ if (!controller.getFinishTargetIsLauncher()) {
+ TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
+ true /*shown*/, true /*animate*/);
+ }
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
if (mRecentsView != null) {
@@ -1754,9 +1951,9 @@ public abstract class AbsSwipeUpHandler,
}
@Override
- public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
if (mRecentsAnimationController != null) {
- if (handleTaskAppeared(appearedTaskTarget)) {
+ if (handleTaskAppeared(appearedTaskTargets)) {
mRecentsAnimationController.finish(false /* toRecents */,
null /* onFinishComplete */);
mActivityInterface.onLaunchTaskSuccess();
@@ -1796,21 +1993,59 @@ public abstract class AbsSwipeUpHandler,
/**
* Applies the transform on the recents animation
*/
- protected void applyWindowTransform() {
- if (mWindowTransitionController != null) {
- mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
- }
+ protected void applyScrollAndTransform() {
// No need to apply any transform if there is ongoing swipe-pip-to-home animator since
// that animator handles the leash solely.
- if (mRecentsAnimationTargets != null && !mIsSwipingPipToHome) {
- if (mRecentsViewScrollLinked && mRecentsView != null) {
- mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+ boolean notSwipingPipToHome = mRecentsAnimationTargets != null && !mIsSwipingPipToHome;
+ boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
+ for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
+ AnimatorControllerWithResistance playbackController =
+ remoteHandle.getPlaybackController();
+ if (playbackController != null) {
+ playbackController.setProgress(Math.max(mCurrentShift.value,
+ getScaleProgressDueToScroll()), mDragLengthFactor);
+ }
+
+ if (notSwipingPipToHome) {
+ TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
+ if (setRecentsScroll) {
+ taskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+ }
+ taskViewSimulator.apply(remoteHandle.getTransformParams());
}
- mTaskViewSimulator.apply(mTransformParams);
}
ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
}
+ // Scaling of RecentsView during quick switch based on amount of recents scroll
+ private float getScaleProgressDueToScroll() {
+ if (mActivity == null || !mActivity.getDeviceProfile().isTablet || mRecentsView == null
+ || !mRecentsViewScrollLinked) {
+ return 0;
+ }
+
+ float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage()));
+ int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue(
+ mRecentsView.getLastComputedTaskSize().width(),
+ mRecentsView.getLastComputedTaskSize().height());
+ maxScrollOffset += mRecentsView.getPageSpacing();
+
+ float maxScaleProgress =
+ MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS * mRecentsView.getMaxScaleForFullScreen();
+ float scaleProgress = maxScaleProgress;
+
+ if (scrollOffset < mQuickSwitchScaleScrollThreshold) {
+ scaleProgress = Utilities.mapToRange(scrollOffset, 0, mQuickSwitchScaleScrollThreshold,
+ 0, maxScaleProgress, ACCEL_DEACCEL);
+ } else if (scrollOffset > (maxScrollOffset - mQuickSwitchScaleScrollThreshold)) {
+ scaleProgress = Utilities.mapToRange(scrollOffset,
+ (maxScrollOffset - mQuickSwitchScaleScrollThreshold), maxScrollOffset,
+ maxScaleProgress, 0, ACCEL_DEACCEL);
+ }
+
+ return scaleProgress;
+ }
+
/**
* Used for winscope tracing, see launcher_trace.proto
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
@@ -1832,7 +2067,6 @@ public abstract class AbsSwipeUpHandler,
}
public interface Factory {
-
AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
}
}
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index f7e8781573..95c871099b 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -53,6 +53,16 @@ public class AnimatedFloat {
mUpdateCallback = updateCallback;
}
+ /**
+ * Returns an animation from the current value to the given value.
+ */
+ public ObjectAnimator animateToValue(float end) {
+ return animateToValue(value, end);
+ }
+
+ /**
+ * Returns an animation from the given start value to the given end value.
+ */
public ObjectAnimator animateToValue(float start, float end) {
cancelAnimation();
mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, start, end);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index fac4d52826..1d4ed4c487 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -30,6 +30,7 @@ import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
@@ -40,6 +41,7 @@ import android.graphics.Rect;
import android.os.Build;
import android.view.Gravity;
import android.view.MotionEvent;
+import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -48,10 +50,10 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.views.ScrimView;
@@ -65,6 +67,7 @@ import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import java.util.HashMap;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -77,12 +80,14 @@ public abstract class BaseActivityInterface thumbnailDatas,
+ Runnable runnable) {
ACTIVITY_TYPE activity = getCreatedActivity();
if (activity == null) {
return;
@@ -198,46 +207,31 @@ public abstract class BaseActivityInterface mCallback;
private boolean mIsAttachedToWindow;
+ private boolean mHasEverAttachedToWindow;
DefaultAnimationFactory(Consumer callback) {
mCallback = callback;
@@ -435,7 +466,7 @@ public abstract class BaseActivityInterface mActivity.getStateManager().goToState(
- controller.getInterpolatedProgress() > 0.5 ? mOverviewState : mBackgroundState,
+ controller.getInterpolatedProgress() > 0.5 ? mTargetState : mBackgroundState,
false));
RecentsView recentsView = mActivity.getOverviewPanel();
@@ -461,6 +492,9 @@ public abstract class BaseActivityInterface
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+ FallbackSwipeHandler.this::updateHomeActivityTransformDuringSwipeUp));
}
}
@@ -109,7 +110,9 @@ public class FallbackSwipeHandler extends
protected void initTransitionEndpoints(DeviceProfile dp) {
super.initTransitionEndpoints(dp);
if (mRunningOverHome) {
- mMaxLauncherScale = 1 / mTaskViewSimulator.getFullScreenScale();
+ // Full screen scale should be independent of remote target handle
+ mMaxLauncherScale = 1 / mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getFullScreenScale();
}
}
@@ -144,7 +147,7 @@ public class FallbackSwipeHandler extends
}
@Override
- protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ protected boolean handleTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTarget) {
if (mActiveAnimationFactory != null
&& mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) {
mActiveAnimationFactory = null;
@@ -173,7 +176,10 @@ public class FallbackSwipeHandler extends
@Override
protected void notifyGestureAnimationStartToRecents() {
if (mRunningOverHome) {
- mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask());
+ if (SysUINavigationMode.getMode(mContext).hasGestures) {
+ mRecentsView.onGestureAnimationStartOnHome(
+ new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()});
+ }
} else {
super.notifyGestureAnimationStartToRecents();
}
@@ -200,19 +206,24 @@ public class FallbackSwipeHandler extends
mHomeAlpha = new AnimatedFloat();
mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1);
mVerticalShiftForScale.value = mCurrentShift.value;
- mTransformParams.setHomeBuilderProxy(
- this::updateHomeActivityTransformDuringHomeAnim);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateHomeActivityTransformDuringHomeAnim));
} else {
mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
mHomeAlpha.value = 0;
-
- mHomeAlphaParams.setHomeBuilderProxy(
- this::updateHomeActivityTransformDuringHomeAnim);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateHomeActivityTransformDuringHomeAnim));
}
mRecentsAlpha.value = 1;
- mTransformParams.setBaseBuilderProxy(
- this::updateRecentsActivityTransformDuringHomeAnim);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateRecentsActivityTransformDuringHomeAnim));
}
@NonNull
@@ -249,7 +260,8 @@ public class FallbackSwipeHandler extends
}
}
- public boolean handleHomeTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ public boolean handleHomeTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
+ RemoteAnimationTargetCompat appearedTaskTarget = appearedTaskTargets[0];
if (appearedTaskTarget.activityType == ACTIVITY_TYPE_HOME) {
RemoteAnimationTargets targets = new RemoteAnimationTargets(
new RemoteAnimationTargetCompat[] {appearedTaskTarget},
@@ -304,8 +316,7 @@ public class FallbackSwipeHandler extends
}
@Override
- public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
- float radius) {
+ public void update(RectF currentRect, float progress, float radius) {
if (mSurfaceControl != null) {
currentRect.roundOut(mTempRect);
Transaction t = new Transaction();
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index e3ae36182e..ed0623da24 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -20,6 +20,7 @@ import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Intent;
@@ -35,6 +36,7 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
@@ -122,10 +124,6 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
public static final int STATE_RECENTS_ANIMATION_ENDED =
getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED");
- // Called when we create an overscroll window when swiping right to left on the most recent app
- public static final int STATE_OVERSCROLL_WINDOW_CREATED =
- getFlagForIndex("STATE_OVERSCROLL_WINDOW_CREATED");
-
// Called when RecentsView stops scrolling and settles on a TaskView.
public static final int STATE_RECENTS_SCROLLING_FINISHED =
getFlagForIndex("STATE_RECENTS_SCROLLING_FINISHED");
@@ -138,12 +136,13 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
private final int mGestureId;
private ActivityManager.RunningTaskInfo mRunningTask;
+ private ActivityManager.RunningTaskInfo[] mRunningTasks;
private GestureEndTarget mEndTarget;
private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
private Set mPreviouslyAppearedTaskIds = new HashSet<>();
private int mLastStartedTaskId = -1;
private RecentsAnimationController mRecentsAnimationController;
- private ThumbnailData mRecentsAnimationCanceledSnapshot;
+ private HashMap mRecentsAnimationCanceledSnapshots;
/** The time when the swipe up gesture is triggered. */
private long mSwipeUpStartTimeMs;
@@ -237,6 +236,14 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
return mRunningTask;
}
+ /**
+ * This will array will contain the task returned by {@link #getRunningTask()}
+ * @return the running tasks for this gesture.
+ */
+ public ActivityManager.RunningTaskInfo[] getRunningTasks() {
+ return mRunningTasks;
+ }
+
/**
* @return the running task id for this gesture.
*/
@@ -251,6 +258,15 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
mRunningTask = runningTask;
}
+ /**
+ * TODO(b/210903248) refactor to consolidate w/ method above
+ * Updates the running task for the gesture to be the given {@param runningTask}.
+ */
+ public void updateRunningTasks(ActivityManager.RunningTaskInfo[] runningTasks) {
+ mRunningTasks = runningTasks;
+ updateRunningTask(runningTasks[0]);
+ }
+
/**
* Updates the last task that appeared during this gesture.
*/
@@ -346,8 +362,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
* @return whether the recents animation is started but not yet ended
*/
public boolean isRecentsAnimationRunning() {
- return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_INITIALIZED) &&
- !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED);
+ return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_STARTED)
+ && !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED);
}
@Override
@@ -358,16 +374,16 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
@Override
- public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
- mRecentsAnimationCanceledSnapshot = thumbnailData;
+ public void onRecentsAnimationCanceled(HashMap thumbnailDatas) {
+ mRecentsAnimationCanceledSnapshots = thumbnailDatas;
mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED);
mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
- if (mRecentsAnimationCanceledSnapshot != null) {
+ if (mRecentsAnimationCanceledSnapshots != null) {
// Clean up the screenshot to finalize the recents animation cancel
if (mRecentsAnimationController != null) {
mRecentsAnimationController.cleanupScreenshot();
}
- mRecentsAnimationCanceledSnapshot = null;
+ mRecentsAnimationCanceledSnapshots = null;
}
}
@@ -382,10 +398,15 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
* while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is responsible for
* calling {@link RecentsAnimationController#cleanupScreenshot()}.
*/
- ThumbnailData consumeRecentsAnimationCanceledSnapshot() {
- ThumbnailData data = mRecentsAnimationCanceledSnapshot;
- mRecentsAnimationCanceledSnapshot = null;
- return data;
+ @Nullable
+ HashMap consumeRecentsAnimationCanceledSnapshot() {
+ if (mRecentsAnimationCanceledSnapshots != null) {
+ HashMap data =
+ new HashMap(mRecentsAnimationCanceledSnapshots);
+ mRecentsAnimationCanceledSnapshots = null;
+ return data;
+ }
+ return null;
}
void setSwipeUpStartTimeMs(long uptimeMs) {
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 0b2a057eb8..c455dc7462 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -36,9 +36,10 @@ public interface InputConsumer {
int TYPE_SCREEN_PINNED = 1 << 6;
int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
int TYPE_RESET_GESTURE = 1 << 8;
- int TYPE_OVERSCROLL = 1 << 9;
+ int TYPE_PROGRESS_DELEGATE = 1 << 9;
int TYPE_SYSUI_OVERLAY = 1 << 10;
int TYPE_ONE_HANDED = 1 << 11;
+ int TYPE_TASKBAR_STASH = 1 << 12;
String[] NAMES = new String[] {
"TYPE_NO_OP", // 0
@@ -50,9 +51,10 @@ public interface InputConsumer {
"TYPE_SCREEN_PINNED", // 6
"TYPE_OVERVIEW_WITHOUT_FOCUS", // 7
"TYPE_RESET_GESTURE", // 8
- "TYPE_OVERSCROLL", // 9
+ "TYPE_PROGRESS_DELEGATE", // 9
"TYPE_SYSUI_OVERLAY", // 10
"TYPE_ONE_HANDED", // 11
+ "TYPE_TASKBAR_STASH", // 12
};
InputConsumer NO_OP = () -> TYPE_NO_OP;
@@ -97,6 +99,8 @@ public interface InputConsumer {
default void onMotionEvent(MotionEvent ev) { }
+ default void onHoverEvent(MotionEvent ev) { }
+
default void onKeyEvent(KeyEvent ev) { }
default void onInputEvent(InputEvent ev) {
diff --git a/quickstep/src/com/android/quickstep/KtR.java b/quickstep/src/com/android/quickstep/KtR.java
new file mode 100644
index 0000000000..a768ef5253
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/KtR.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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 com.android.launcher3.R;
+
+/**
+ * Bridge class to allow using resources in Kotlin.
+ *
+ * TODO(b/204069723) Can't use resources directly in Kotlin
+ */
+public class KtR {
+ public static final class id {
+ public static int menu_option_layout = R.id.menu_option_layout;
+ }
+
+ public static final class dimen {
+ public static int task_menu_spacing = R.dimen.task_menu_spacing;
+ public static int task_menu_horizontal_padding = R.dimen.task_menu_horizontal_padding;
+ }
+
+ public static final class layout {
+ public static int task_menu_with_arrow = R.layout.task_menu_with_arrow;
+ public static int task_view_menu_option = R.layout.task_view_menu_option;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index ba26adc23a..8897efc825 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -71,7 +71,7 @@ public final class LauncherActivityInterface extends
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
PagedOrientationHandler orientationHandler) {
- calculateTaskSize(context, dp, outRect, orientationHandler);
+ calculateTaskSize(context, dp, outRect);
if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) {
return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
} else {
@@ -130,7 +130,6 @@ public final class LauncherActivityInterface extends
pa.addFloat(getDepthController(),
new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
fromDepthRatio, toDepthRatio, LINEAR);
-
}
};
@@ -173,7 +172,8 @@ public final class LauncherActivityInterface extends
}
@Nullable
- private LauncherTaskbarUIController getTaskbarController() {
+ @Override
+ public LauncherTaskbarUIController getTaskbarController() {
BaseQuickstepLauncher launcher = getCreatedActivity();
if (launcher == null) {
return null;
@@ -189,7 +189,7 @@ public final class LauncherActivityInterface extends
launcher != null && launcher.getStateManager().getState().overviewUi
? launcher.getOverviewPanel() : null;
if (recentsView == null || (!launcher.hasBeenResumed()
- && recentsView.getRunningTaskId() == -1)) {
+ && recentsView.getRunningTaskViewId() == -1)) {
// If live tile has ended, return null.
return null;
}
@@ -292,25 +292,23 @@ public final class LauncherActivityInterface extends
} else {
om.hideOverlay(150);
}
- }
-
- @Override
- void onOverviewServiceBound() {
- final BaseQuickstepLauncher activity = getCreatedActivity();
- if (activity == null) return;
- activity.getAppTransitionManager().registerRemoteTransitions();
+ LauncherTaskbarUIController taskbarController = getTaskbarController();
+ if (taskbarController != null) {
+ taskbarController.hideEdu();
+ }
}
@Override
public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
- long duration) {
+ long duration, RecentsAnimationCallbacks callbacks) {
LauncherTaskbarUIController uiController = getTaskbarController();
- Animator superAnimator = super.getParallelAnimationToLauncher(endTarget, duration);
- if (uiController == null) {
+ Animator superAnimator = super.getParallelAnimationToLauncher(
+ endTarget, duration, callbacks);
+ if (uiController == null || callbacks == null) {
return superAnimator;
}
LauncherState toState = stateFromGestureEndTarget(endTarget);
- Animator taskbarAnimator = uiController.createAnimToLauncher(toState, duration);
+ Animator taskbarAnimator = uiController.createAnimToLauncher(toState, callbacks, duration);
if (superAnimator == null) {
return taskbarAnimator;
} else {
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 19cad53bb1..a72935b250 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -18,18 +18,14 @@ package com.android.quickstep;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.Utilities.boundToRange;
import static com.android.launcher3.Utilities.dpToPx;
import static com.android.launcher3.Utilities.mapBoundToRange;
import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
-import static java.lang.Math.round;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -59,10 +55,8 @@ import com.android.launcher3.util.ObjectWrapper;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.FloatingView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.quickstep.util.AppCloseConfig;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
-import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.FloatingWidgetView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -106,9 +100,11 @@ public class LauncherSwipeHandlerV2 extends
boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
mActivity.getRootView().setForceHideBackArrow(true);
- mActivity.setHintUserWillBeActive();
+ if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ mActivity.setHintUserWillBeActive();
+ }
- if (!canUseWorkspaceView || appCanEnterPip) {
+ if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForStagedSplit) {
return new LauncherHomeAnimationFactory();
}
if (workspaceView instanceof LauncherAppWidgetHostView) {
@@ -119,8 +115,6 @@ public class LauncherSwipeHandlerV2 extends
}
private HomeAnimationFactory createIconHomeAnimationFactory(View workspaceView) {
- final ResourceProvider rp = DynamicResource.provider(mActivity);
- final float transY = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp));
RectF iconLocation = new RectF();
FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView,
true /* hideOriginal */, iconLocation, false /* isOpening */);
@@ -131,13 +125,15 @@ public class LauncherSwipeHandlerV2 extends
return new FloatingViewHomeAnimationFactory(floatingIconView) {
- // There is a delay in loading the icon, so we need to keep the window
- // opaque until it is ready.
- private boolean mIsFloatingIconReady = false;
+ @Nullable
+ @Override
+ protected View getViewIgnoredInWorkspaceRevealAnimation() {
+ return workspaceView;
+ }
+ @NonNull
@Override
public RectF getWindowTargetRect() {
- super.getWindowTargetRect();
return iconLocation;
}
@@ -150,24 +146,9 @@ public class LauncherSwipeHandlerV2 extends
}
@Override
- public boolean keepWindowOpaque() {
- if (mIsFloatingIconReady || floatingIconView.isVisibleToUser()) {
- mIsFloatingIconReady = true;
- return false;
- }
- return true;
- }
-
- @Override
- public void update(@Nullable AppCloseConfig config, RectF currentRect,
- float progress, float radius) {
- super.update(config, currentRect, progress, radius);
- int fgAlpha = 255;
- if (config != null && PROTOTYPE_APP_CLOSE.get()) {
- progress = config.getInterpolatedProgress();
- fgAlpha = config.getFgAlpha();
- }
- floatingIconView.update(1f, fgAlpha, currentRect, progress,
+ public void update(RectF currentRect, float progress, float radius) {
+ super.update(currentRect, progress, radius);
+ floatingIconView.update(1f /* alpha */, 255 /* fgAlpha */, currentRect, progress,
windowAlphaThreshold, radius, false);
}
};
@@ -179,14 +160,16 @@ public class LauncherSwipeHandlerV2 extends
final float floatingWidgetAlpha = isTargetTranslucent ? 0 : 1;
RectF backgroundLocation = new RectF();
Rect crop = new Rect();
- mTaskViewSimulator.getCurrentCropRect().roundOut(crop);
+ // We can assume there is only one remote target here because staged split never animates
+ // into the app icon, only into the homescreen
+ mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCropRect().roundOut(crop);
Size windowSize = new Size(crop.width(), crop.height());
int fallbackBackgroundColor =
FloatingWidgetView.getDefaultBackgroundColor(mContext, runningTaskTarget);
FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mActivity,
hostView, backgroundLocation, windowSize,
- mTaskViewSimulator.getCurrentCornerRadius(), isTargetTranslucent,
- fallbackBackgroundColor);
+ mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCornerRadius(),
+ isTargetTranslucent, fallbackBackgroundColor);
return new FloatingViewHomeAnimationFactory(floatingWidgetView) {
@@ -217,14 +200,8 @@ public class LauncherSwipeHandlerV2 extends
}
@Override
- public boolean keepWindowOpaque() {
- return false;
- }
-
- @Override
- public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
- float radius) {
- super.update(config, currentRect, progress, radius);
+ public void update(RectF currentRect, float progress, float radius) {
+ super.update(currentRect, progress, radius);
final float fallbackBackgroundAlpha =
1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE);
final float foregroundAlpha =
@@ -267,7 +244,7 @@ public class LauncherSwipeHandlerV2 extends
}
}
- return mActivity.getWorkspace().getFirstMatchForAppClose(launchCookieItemId,
+ return mActivity.getFirstMatchForAppClose(launchCookieItemId,
runningTaskView.getTask().key.getComponent().getPackageName(),
UserHandle.of(runningTaskView.getTask().key.userId));
}
@@ -283,27 +260,12 @@ public class LauncherSwipeHandlerV2 extends
private final float mTransY;
private final FloatingView mFloatingView;
private ValueAnimator mBounceBackAnimator;
- private final AnimatorSet mWorkspaceReveal;
FloatingViewHomeAnimationFactory(FloatingView floatingView) {
mFloatingView = floatingView;
ResourceProvider rp = DynamicResource.provider(mActivity);
mTransY = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp));
-
- mWorkspaceReveal = PROTOTYPE_APP_CLOSE.get()
- ? new WorkspaceRevealAnim(mActivity, true /* animateScrim */).getAnimators()
- : null;
- }
-
- @Override
- public @NonNull RectF getWindowTargetRect() {
- if (PROTOTYPE_APP_CLOSE.get()) {
- // We want the target rect to be at this offset position, so that all
- // launcher content can spring back upwards.
- mFloatingView.setPositionOffsetY(mTransY);
- }
- return super.getWindowTargetRect();
}
@Override
@@ -311,20 +273,6 @@ public class LauncherSwipeHandlerV2 extends
return false;
}
- @Override
- public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
- float radius) {
- if (config != null && PROTOTYPE_APP_CLOSE.get()) {
- DragLayer dl = mActivity.getDragLayer();
- float translationY = config.getWorkspaceTransY();
- dl.setTranslationY(translationY);
-
- long duration = mWorkspaceReveal.getDuration();
- long playTime = boundToRange(round(duration * progress), 0, duration);
- mWorkspaceReveal.setCurrentPlayTime(playTime);
- }
- }
-
protected void bounceBackToRestingPosition() {
final float startValue = mTransY;
final float endValue = 0;
@@ -358,31 +306,6 @@ public class LauncherSwipeHandlerV2 extends
mBounceBackAnimator.start();
}
- @Override
- public void setAnimation(RectFSpringAnim anim) {
- if (PROTOTYPE_APP_CLOSE.get()) {
- // Use a spring to put drag layer translation back to 0.
- anim.addAnimatorListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mFloatingView.setPositionOffsetY(0);
- bounceBackToRestingPosition();
- }
- });
-
- // Will be updated manually below so that the two animations are in sync.
- mWorkspaceReveal.start();
- mWorkspaceReveal.pause();
-
- anim.addAnimatorListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mWorkspaceReveal.end();
- }
- });
- }
- }
-
@Override
public void onCancel() {
mFloatingView.fastFinish();
@@ -415,13 +338,9 @@ public class LauncherSwipeHandlerV2 extends
@Override
public void playAtomicAnimation(float velocity) {
- if (!PROTOTYPE_APP_CLOSE.get()) {
- new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */,
- getViewIgnoredInWorkspaceRevealAnimation())
- .start();
- } else if (shouldPlayAtomicWorkspaceReveal()) {
- new WorkspaceRevealAnim(mActivity, true).start();
- }
+ new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */,
+ getViewIgnoredInWorkspaceRevealAnimation())
+ .start();
}
@Override
diff --git a/quickstep/src/com/android/quickstep/OrientationRectF.java b/quickstep/src/com/android/quickstep/OrientationRectF.java
new file mode 100644
index 0000000000..59a202cf76
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OrientationRectF.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.states.RotationHelper.deltaRotation;
+import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.MotionEvent;
+
+public class OrientationRectF extends RectF {
+
+ private static final String TAG = "OrientationRectF";
+ private static final boolean DEBUG = false;
+
+ private final int mRotation;
+ private final float mHeight;
+ private final float mWidth;
+
+ private final Matrix mTmpMatrix = new Matrix();
+ private final float[] mTmpPoint = new float[2];
+
+ public OrientationRectF(float left, float top, float right, float bottom, int rotation) {
+ super(left, top, right, bottom);
+ mRotation = rotation;
+ mHeight = bottom;
+ mWidth = right;
+ }
+
+ @Override
+ public String toString() {
+ String s = super.toString();
+ s += " rotation: " + mRotation;
+ return s;
+ }
+
+ @Override
+ public boolean contains(float x, float y) {
+ // Mark bottom right as included in the Rect (copied from Rect src, added "=" in "<=")
+ return left < right && top < bottom // check for empty first
+ && x >= left && x <= right && y >= top && y <= bottom;
+ }
+
+ public boolean applyTransformFromRotation(MotionEvent event, int currentRotation,
+ boolean forceTransform) {
+ return applyTransform(event, deltaRotation(currentRotation, mRotation), forceTransform);
+ }
+
+ public boolean applyTransformToRotation(MotionEvent event, int currentRotation,
+ boolean forceTransform) {
+ return applyTransform(event, deltaRotation(mRotation, currentRotation), forceTransform);
+ }
+
+ private boolean applyTransform(MotionEvent event, int deltaRotation, boolean forceTransform) {
+ mTmpMatrix.reset();
+ postDisplayRotation(deltaRotation, mHeight, mWidth, mTmpMatrix);
+ if (forceTransform) {
+ if (DEBUG) {
+ Log.d(TAG, "Transforming rotation due to forceTransform, "
+ + "deltaRotation: " + deltaRotation
+ + "mRotation: " + mRotation
+ + " this: " + this);
+ }
+ event.applyTransform(mTmpMatrix);
+ return true;
+ }
+ mTmpPoint[0] = event.getX();
+ mTmpPoint[1] = event.getY();
+ mTmpMatrix.mapPoints(mTmpPoint);
+
+ if (DEBUG) {
+ Log.d(TAG, "original: " + event.getX() + ", " + event.getY()
+ + " new: " + mTmpPoint[0] + ", " + mTmpPoint[1]
+ + " rect: " + this + " forceTransform: " + forceTransform
+ + " contains: " + contains(mTmpPoint[0], mTmpPoint[1])
+ + " this: " + this);
+ }
+
+ if (contains(mTmpPoint[0], mTmpPoint[1])) {
+ event.applyTransform(mTmpMatrix);
+ return true;
+ }
+ return false;
+ }
+
+ int getRotation() {
+ return mRotation;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 35a851ab65..ecff4f1a45 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -22,11 +22,7 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_UP;
-import static com.android.launcher3.states.RotationHelper.deltaRotation;
-import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
-
import android.content.res.Resources;
-import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
import android.util.Log;
@@ -44,8 +40,8 @@ import java.util.Objects;
/**
* Maintains state for supporting nav bars and tracking their gestures in multiple orientations.
- * See {@link OrientationRectF#applyTransform(MotionEvent, boolean)} for transformation of
- * MotionEvents from one orientation's coordinate space to another's.
+ * See {@link OrientationRectF#applyTransformToRotation(MotionEvent, int, boolean)} for
+ * transformation of MotionEvents from one orientation's coordinate space to another's.
*
* This class only supports single touch/pointer gesture tracking for touches started in a supported
* nav bar region.
@@ -95,9 +91,6 @@ class OrientationTouchTransformer {
private static final int QUICKSTEP_ROTATION_UNINITIALIZED = -1;
- private final Matrix mTmpMatrix = new Matrix();
- private final float[] mTmpPoint = new float[2];
-
private final Map mSwipeTouchRegions =
new HashMap();
private final RectF mAssistantLeftRegion = new RectF();
@@ -365,7 +358,7 @@ class OrientationTouchTransformer {
if (mLastRectTouched == null) {
return;
}
- mLastRectTouched.applyTransform(event, true);
+ mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation, true);
break;
}
case ACTION_CANCEL:
@@ -373,7 +366,7 @@ class OrientationTouchTransformer {
if (mLastRectTouched == null) {
return;
}
- mLastRectTouched.applyTransform(event, true);
+ mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation, true);
mLastRectTouched = null;
break;
}
@@ -387,14 +380,14 @@ class OrientationTouchTransformer {
if (rect == null) {
continue;
}
- if (rect.applyTransform(event, false)) {
+ if (rect.applyTransformFromRotation(event, mCurrentDisplay.rotation, false)) {
mLastRectTouched = rect;
- mActiveTouchRotation = rect.mRotation;
+ mActiveTouchRotation = rect.getRotation();
if (mEnableMultipleRegions
&& mCurrentDisplay.rotation == mActiveTouchRotation) {
// TODO(b/154580671) might make this block unnecessary
// Start a touch session for the default nav region for the display
- mQuickStepStartingRotation = mLastRectTouched.mRotation;
+ mQuickStepStartingRotation = mLastRectTouched.getRotation();
resetSwipeRegions();
}
if (DEBUG) {
@@ -423,65 +416,4 @@ class OrientationTouchTransformer {
pw.println(" mNavBarLargerGesturalHeight=" + mNavBarLargerGesturalHeight);
pw.println(" mOneHandedModeRegion=" + mOneHandedModeRegion);
}
-
- private class OrientationRectF extends RectF {
-
- private int mRotation;
- private float mHeight;
- private float mWidth;
-
- OrientationRectF(float left, float top, float right, float bottom, int rotation) {
- super(left, top, right, bottom);
- this.mRotation = rotation;
- mHeight = bottom;
- mWidth = right;
- }
-
- @Override
- public String toString() {
- String s = super.toString();
- s += " rotation: " + mRotation;
- return s;
- }
-
- @Override
- public boolean contains(float x, float y) {
- // Mark bottom right as included in the Rect (copied from Rect src, added "=" in "<=")
- return left < right && top < bottom // check for empty first
- && x >= left && x <= right && y >= top && y <= bottom;
- }
-
- boolean applyTransform(MotionEvent event, boolean forceTransform) {
- mTmpMatrix.reset();
- postDisplayRotation(deltaRotation(mCurrentDisplay.rotation, mRotation),
- mHeight, mWidth, mTmpMatrix);
- if (forceTransform) {
- if (DEBUG) {
- Log.d(TAG, "Transforming rotation due to forceTransform, "
- + "mCurrentRotation: " + mCurrentDisplay.rotation
- + "mRotation: " + mRotation
- + " this: " + this);
- }
- event.transform(mTmpMatrix);
- return true;
- }
- mTmpPoint[0] = event.getX();
- mTmpPoint[1] = event.getY();
- mTmpMatrix.mapPoints(mTmpPoint);
-
- if (DEBUG) {
- Log.d(TAG, "original: " + event.getX() + ", " + event.getY()
- + " new: " + mTmpPoint[0] + ", " + mTmpPoint[1]
- + " rect: " + this + " forceTransform: " + forceTransform
- + " contains: " + contains(mTmpPoint[0], mTmpPoint[1])
- + " this: " + this);
- }
-
- if (contains(mTmpPoint[0], mTmpPoint[1])) {
- event.transform(mTmpMatrix);
- return true;
- }
- return false;
- }
- }
}
diff --git a/quickstep/src/com/android/quickstep/OverscrollPluginFactory.java b/quickstep/src/com/android/quickstep/OverscrollPluginFactory.java
deleted file mode 100644
index 4c261abc7a..0000000000
--- a/quickstep/src/com/android/quickstep/OverscrollPluginFactory.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.systemui.plugins.OverscrollPlugin;
-
-/**
- * Resource overrideable factory for forcing a local overscroll plugin.
- * Override {@link R.string#overscroll_plugin_factory_class} to set a different class.
- */
-public class OverscrollPluginFactory implements ResourceBasedOverride {
- public static final MainThreadInitializedObject INSTANCE = forOverride(
- OverscrollPluginFactory.class,
- R.string.overscroll_plugin_factory_class);
-
- /**
- * Get the plugin that is defined locally in launcher, as opposed to a dynamic side loaded one.
- */
- public OverscrollPlugin getLocalOverscrollPlugin() {
- return null;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 5d1f90885a..17baa3ad91 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -32,12 +32,15 @@ import androidx.annotation.UiThread;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
/**
* Helper class to handle various atomic commands for switching between Overview.
@@ -49,6 +52,7 @@ public class OverviewCommandHelper {
public static final int TYPE_SHOW_NEXT_FOCUS = 2;
public static final int TYPE_HIDE = 3;
public static final int TYPE_TOGGLE = 4;
+ public static final int TYPE_HOME = 5;
private static final String TRANSITION_NAME = "Transition:toOverview";
@@ -114,11 +118,12 @@ public class OverviewCommandHelper {
mPendingCommands.clear();
}
+ @Nullable
private TaskView getNextTask(RecentsView view) {
final TaskView runningTaskView = view.getRunningTaskView();
if (runningTaskView == null) {
- return view.getTaskViewCount() > 0 ? view.getTaskViewAt(0) : null;
+ return view.getTaskViewAt(0);
} else {
final TaskView nextTask = view.getNextTaskView();
return nextTask != null ? nextTask : runningTaskView;
@@ -154,6 +159,11 @@ public class OverviewCommandHelper {
// already hidden
return true;
}
+ if (cmd.type == TYPE_HOME) {
+ mService.startActivity(mOverviewComponentObserver.getHomeIntent());
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
+ return true;
+ }
} else {
switch (cmd.type) {
case TYPE_SHOW:
@@ -168,6 +178,10 @@ public class OverviewCommandHelper {
}
case TYPE_TOGGLE:
return launchTask(recents, getNextTask(recents), cmd);
+ case TYPE_HOME:
+ recents.startHome();
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
+ return true;
}
}
@@ -200,7 +214,7 @@ public class OverviewCommandHelper {
}
@Override
- public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ public void onRecentsAnimationCanceled(HashMap thumbnailDatas) {
interactionHandler.onGestureCancelled();
cmd.removeListener(this);
@@ -244,8 +258,8 @@ public class OverviewCommandHelper {
// Ensure that recents view has focus so that it receives the followup key inputs
TaskView taskView = rv.getNextTaskView();
if (taskView == null) {
- if (rv.getTaskViewCount() > 0) {
- taskView = rv.getTaskViewAt(0);
+ taskView = rv.getTaskViewAt(0);
+ if (taskView != null) {
taskView.requestFocus();
} else {
rv.requestFocus();
@@ -258,6 +272,14 @@ public class OverviewCommandHelper {
scheduleNextTask(cmd);
}
+ public void dump(PrintWriter pw) {
+ pw.println("OverviewCommandHelper:");
+ pw.println(" mPendingCommands=" + mPendingCommands.size());
+ if (!mPendingCommands.isEmpty()) {
+ pw.println(" pendingCommandType=" + mPendingCommands.get(0).type);
+ }
+ }
+
private static class CommandInfo {
public final long createTime = SystemClock.elapsedRealtime();
public final int type;
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 65847f11bb..a551f55de5 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -24,8 +24,6 @@ import android.util.Log;
import com.android.launcher3.BuildConfig;
import com.android.launcher3.MainProcessInitializer;
-import com.android.launcher3.util.Executors;
-import com.android.quickstep.logging.SettingsChangeLogger;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.ThreadedRendererCompat;
@@ -62,9 +60,5 @@ public class QuickstepProcessInitializer extends MainProcessInitializer {
// Elevate GPU priority for Quickstep and Remote animations.
ThreadedRendererCompat.setContextPriority(
ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
-
- // Initialize settings logger after a default timeout
- Executors.MAIN_EXECUTOR.getHandler()
- .postDelayed(() -> new SettingsChangeLogger(context), SETUP_DELAY_MILLIS);
}
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 39af0db8ff..95ab62f1f6 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -2,10 +2,10 @@ package com.android.quickstep;
import android.app.Activity;
import android.content.Context;
+import android.graphics.Rect;
import android.os.Bundle;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.testing.TestInformationHandler;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
@@ -21,7 +21,7 @@ public class QuickstepTestInformationHandler extends TestInformationHandler {
}
@Override
- public Bundle call(String method) {
+ public Bundle call(String method, String arg) {
final Bundle response = new Bundle();
switch (method) {
case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
@@ -53,20 +53,30 @@ public class QuickstepTestInformationHandler extends TestInformationHandler {
Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
}
- case TestProtocol.REQUEST_OVERVIEW_SHARE_ENABLED: {
- response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- FeatureFlags.ENABLE_OVERVIEW_SHARE.get());
+ case TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET: {
+ if (!mDeviceProfile.isTablet) {
+ return null;
+ }
+ Rect focusedTaskRect = new Rect();
+ LauncherActivityInterface.INSTANCE.calculateTaskSize(mContext, mDeviceProfile,
+ focusedTaskRect);
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, focusedTaskRect.height());
return response;
}
- case TestProtocol.REQUEST_OVERVIEW_CONTENT_PUSH_ENABLED: {
- response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- FeatureFlags.ENABLE_OVERVIEW_CONTENT_PUSH.get());
+ case TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET: {
+ if (!mDeviceProfile.isTablet) {
+ return null;
+ }
+ Rect gridTaskRect = new Rect();
+ LauncherActivityInterface.INSTANCE.calculateGridTaskSize(mContext, mDeviceProfile,
+ gridTaskRect, PagedOrientationHandler.PORTRAIT);
+ response.putParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, gridTaskRect);
return response;
}
}
- return super.call(method);
+ return super.call(method, arg);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 36432a74af..5ba17ae295 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -22,22 +22,23 @@ import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.os.Build;
import android.os.Process;
-import android.util.Log;
+import android.os.RemoteException;
import android.util.SparseBooleanArray;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.quickstep.util.GroupTask;
import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.KeyguardManagerCompat;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.recents.IRecentTasksListener;
+import com.android.wm.shell.util.GroupedRecentTaskInfo;
+import com.android.wm.shell.util.StagedSplitBounds;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
import java.util.function.Consumer;
import app.lawnchair.LawnchairApp;
@@ -46,13 +47,13 @@ import app.lawnchair.LawnchairApp;
* Manages the recent task list from the system, caching it as necessary.
*/
@TargetApi(Build.VERSION_CODES.R)
-public class RecentTasksList extends TaskStackChangeListener {
+public class RecentTasksList {
private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
private final KeyguardManagerCompat mKeyguardManager;
private final LooperExecutor mMainThreadExecutor;
- private final ActivityManagerWrapper mActivityManagerWrapper;
+ private final SystemUiProxy mSysUiProxy;
// The list change id, increments as the task list changes in the system
private int mChangeId;
@@ -64,14 +65,17 @@ public class RecentTasksList extends TaskStackChangeListener {
private TaskLoadResult mResultsUi = INVALID_RESULT;
public RecentTasksList(LooperExecutor mainThreadExecutor,
- KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) {
+ KeyguardManagerCompat keyguardManager, SystemUiProxy sysUiProxy) {
mMainThreadExecutor = mainThreadExecutor;
mKeyguardManager = keyguardManager;
mChangeId = 1;
- mActivityManagerWrapper = activityManagerWrapper;
- if (LawnchairApp.isRecentsEnabled()) {
- TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
- }
+ mSysUiProxy = sysUiProxy;
+ sysUiProxy.registerRecentTasksListener(new IRecentTasksListener.Stub() {
+ @Override
+ public void onRecentTasksChanged() throws RemoteException {
+ mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged);
+ }
+ });
}
@VisibleForTesting
@@ -82,10 +86,11 @@ public class RecentTasksList extends TaskStackChangeListener {
/**
* Fetches the task keys skipping any local cache.
*/
- public void getTaskKeys(int numTasks, Consumer> callback) {
+ public void getTaskKeys(int numTasks, Consumer> callback) {
// Kick off task loading in the background
UI_HELPER_EXECUTOR.execute(() -> {
- ArrayList tasks = loadTasksInBackground(numTasks, -1, true /* loadKeysOnly */);
+ ArrayList tasks = loadTasksInBackground(numTasks, -1,
+ true /* loadKeysOnly */);
mMainThreadExecutor.execute(() -> callback.accept(tasks));
});
}
@@ -97,14 +102,15 @@ public class RecentTasksList extends TaskStackChangeListener {
* @param callback The callback to receive the list of recent tasks
* @return The change id of the current task list
*/
- public synchronized int getTasks(boolean loadKeysOnly, Consumer> callback) {
+ public synchronized int getTasks(boolean loadKeysOnly,
+ Consumer> callback) {
final int requestLoadId = mChangeId;
if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
// The list is up to date, send the callback on the next frame,
// so that requestID can be returned first.
if (callback != null) {
// Copy synchronously as the changeId might change by next frame
- ArrayList result = copyOf(mResultsUi);
+ ArrayList result = copyOf(mResultsUi);
mMainThreadExecutor.post(() -> {
callback.accept(result);
});
@@ -124,7 +130,7 @@ public class RecentTasksList extends TaskStackChangeListener {
mLoadingTasksInBackground = false;
mResultsUi = loadResult;
if (callback != null) {
- ArrayList result = copyOf(mResultsUi);
+ ArrayList result = copyOf(mResultsUi);
callback.accept(result);
}
});
@@ -140,35 +146,7 @@ public class RecentTasksList extends TaskStackChangeListener {
return mChangeId == changeId;
}
- @Override
- public void onTaskStackChanged() {
- invalidateLoadedTasks();
- }
-
- @Override
- public void onRecentTaskListUpdated() {
- // In some cases immediately after booting, the tasks in the system recent task list may be
- // loaded, but not in the active task hierarchy in the system. These tasks are displayed in
- // overview, but removing them don't result in a onTaskStackChanged() nor a onTaskRemoved()
- // callback (those are for changes to the active tasks), but the task list is still updated,
- // so we should also invalidate the change id to ensure we load a new list instead of
- // reusing a stale list.
- invalidateLoadedTasks();
- }
-
- @Override
- public void onTaskRemoved(int taskId) {
- invalidateLoadedTasks();
- }
-
-
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- invalidateLoadedTasks();
- }
-
- @Override
- public synchronized void onActivityUnpinned() {
+ public void onRecentTasksChanged() {
invalidateLoadedTasks();
}
@@ -184,8 +162,8 @@ public class RecentTasksList extends TaskStackChangeListener {
@VisibleForTesting
TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
int currentUserId = Process.myUserHandle().getIdentifier();
- List rawTasks =
- mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId);
+ ArrayList rawTasks =
+ mSysUiProxy.getRecentTasks(numTasks, currentUserId);
// The raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(rawTasks);
@@ -201,45 +179,84 @@ public class RecentTasksList extends TaskStackChangeListener {
};
TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
- for (ActivityManager.RecentTaskInfo rawTask : rawTasks) {
- Task.TaskKey taskKey = new Task.TaskKey(rawTask);
- Task task;
- if (!loadKeysOnly) {
- boolean isLocked = tmpLockedUsers.get(taskKey.userId);
- task = Task.from(taskKey, rawTask, isLocked);
- } else {
- task = new Task(taskKey);
+ for (GroupedRecentTaskInfo rawTask : rawTasks) {
+ ActivityManager.RecentTaskInfo taskInfo1 = rawTask.mTaskInfo1;
+ ActivityManager.RecentTaskInfo taskInfo2 = rawTask.mTaskInfo2;
+ Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
+ Task task1 = loadKeysOnly
+ ? new Task(task1Key)
+ : Task.from(task1Key, taskInfo1,
+ tmpLockedUsers.get(task1Key.userId) /* isLocked */);
+ task1.setLastSnapshotData(taskInfo1);
+ Task task2 = null;
+ if (taskInfo2 != null) {
+ Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
+ task2 = loadKeysOnly
+ ? new Task(task2Key)
+ : Task.from(task2Key, taskInfo2,
+ tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+ task2.setLastSnapshotData(taskInfo2);
}
- task.setLastSnapshotData(rawTask);
- allTasks.add(task);
+ final SplitConfigurationOptions.StagedSplitBounds launcherSplitBounds =
+ convertSplitBounds(rawTask.mStagedSplitBounds);
+ allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
}
return allTasks;
}
- private ArrayList copyOf(ArrayList tasks) {
- ArrayList newTasks = new ArrayList<>();
+ private SplitConfigurationOptions.StagedSplitBounds convertSplitBounds(
+ StagedSplitBounds shellSplitBounds) {
+ return shellSplitBounds == null ?
+ null :
+ new SplitConfigurationOptions.StagedSplitBounds(
+ shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds,
+ shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId);
+ }
+
+ private ArrayList copyOf(ArrayList tasks) {
+ ArrayList newTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
- newTasks.add(new Task(tasks.get(i)));
+ newTasks.add(new GroupTask(tasks.get(i)));
}
return newTasks;
}
- private static class TaskLoadResult extends ArrayList {
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "RecentTasksList:");
+ writer.println(prefix + " mChangeId=" + mChangeId);
+ writer.println(prefix + " mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks=");
+ for (GroupTask task : mResultsUi) {
+ writer.println(prefix + " t1=" + task.task1.key.id
+ + " t2=" + (task.hasMultipleTasks() ? task.task2.key.id : "-1"));
+ }
+ writer.println(prefix + " ]");
+ int currentUserId = Process.myUserHandle().getIdentifier();
+ ArrayList rawTasks =
+ mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
+ writer.println(prefix + " rawTasks=[");
+ for (GroupedRecentTaskInfo task : rawTasks) {
+ writer.println(prefix + " t1=" + task.mTaskInfo1.taskId
+ + " t2=" + (task.mTaskInfo2 != null ? task.mTaskInfo2.taskId : "-1"));
+ }
+ writer.println(prefix + " ]");
+ }
- final int mId;
+ private static class TaskLoadResult extends ArrayList {
+
+ final int mRequestId;
// If the result was loaded with keysOnly = true
final boolean mKeysOnly;
- TaskLoadResult(int id, boolean keysOnly, int size) {
+ TaskLoadResult(int requestId, boolean keysOnly, int size) {
super(size);
- mId = id;
+ mRequestId = requestId;
mKeysOnly = keysOnly;
}
boolean isValidForRequest(int requestId, boolean loadKeysOnly) {
- return mId == requestId && (!mKeysOnly || loadKeysOnly);
+ return mRequestId == requestId && (!mKeysOnly || loadKeysOnly);
}
}
}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 589d4436d9..a9224f6ebf 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -22,8 +22,8 @@ import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DU
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
import static com.android.launcher3.Utilities.createHomeIntent;
-import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
@@ -61,6 +61,8 @@ import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.FallbackTaskbarUIController;
+import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ActivityTracker;
import com.android.launcher3.util.RunnableList;
@@ -74,9 +76,9 @@ import com.android.quickstep.fallback.RecentsDragLayer;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.SplitPlaceholderView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -98,11 +100,15 @@ public final class RecentsActivity extends StatefulActivity {
private Handler mUiHandler = new Handler(Looper.getMainLooper());
private static final long HOME_APPEAR_DURATION = 250;
+ private static final long RECENTS_ANIMATION_TIMEOUT = 1000;
private RecentsDragLayer mDragLayer;
private ScrimView mScrimView;
private FallbackRecentsView mFallbackRecentsView;
private OverviewActionsView mActionsView;
+ private TISBindHelper mTISBindHelper;
+ private @Nullable TaskbarManager mTaskbarManager;
+ private @Nullable FallbackTaskbarUIController mTaskbarUIController;
private Configuration mOldConfig;
@@ -111,6 +117,11 @@ public final class RecentsActivity extends StatefulActivity {
// Strong refs to runners which are cleared when the activity is destroyed
private RemoteAnimationFactory mActivityLaunchAnimationRunner;
+ // For handling degenerate cases where starting an activity doesn't actually trigger the remote
+ // animation callback
+ private final Handler mHandler = new Handler();
+ private final Runnable mAnimationStartTimeoutRunnable = this::onAnimationStartTimeout;
+
/**
* Init drag layer and overview panel views.
*/
@@ -123,13 +134,31 @@ public final class RecentsActivity extends StatefulActivity {
mActionsView = findViewById(R.id.overview_actions_view);
SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
- SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
- splitPlaceholderView.init(
- new SplitSelectStateController(mUiHandler, SystemUiProxy.INSTANCE.get(this))
- );
-
+ SplitSelectStateController controller =
+ new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this),
+ getStateManager(), null /*depthController*/);
mDragLayer.recreateControllers();
- mFallbackRecentsView.init(mActionsView, splitPlaceholderView);
+ mFallbackRecentsView.init(mActionsView, controller);
+
+ mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
+ }
+
+ private void onTISConnected(TouchInteractionService.TISBinder binder) {
+ mTaskbarManager = binder.getTaskbarManager();
+ mTaskbarManager.setActivity(this);
+ }
+
+ @Override
+ public void runOnBindToTouchInteractionService(Runnable r) {
+ mTISBindHelper.runOnBindToTouchInteractionService(r);
+ }
+
+ public void setTaskbarUIController(FallbackTaskbarUIController taskbarUIController) {
+ mTaskbarUIController = taskbarUIController;
+ }
+
+ public FallbackTaskbarUIController getTaskbarUIController() {
+ return mTaskbarUIController;
}
@Override
@@ -197,6 +226,16 @@ public final class RecentsActivity extends StatefulActivity {
// TODO(b/137318995) This should go home, but doing so removes freeform windows
}
+ /**
+ * Called if the remote animation callback from #getActivityLaunchOptions() hasn't called back
+ * in a reasonable time due to a conflict with the recents animation.
+ */
+ private void onAnimationStartTimeout() {
+ if (mActivityLaunchAnimationRunner != null) {
+ mActivityLaunchAnimationRunner.onAnimationCancelled();
+ }
+ }
+
@Override
public ActivityOptionsWrapper getActivityLaunchOptions(final View v, @Nullable ItemInfo item) {
if (!(v instanceof TaskView)) {
@@ -211,6 +250,7 @@ public final class RecentsActivity extends StatefulActivity {
public void onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
RemoteAnimationTargetCompat[] nonAppTargets, AnimationResult result) {
+ mHandler.removeCallbacks(mAnimationStartTimeoutRunnable);
AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
wallpaperTargets, nonAppTargets);
anim.addListener(resetStateListener());
@@ -220,6 +260,7 @@ public final class RecentsActivity extends StatefulActivity {
@Override
public void onAnimationCancelled() {
+ mHandler.removeCallbacks(mAnimationStartTimeoutRunnable);
onEndCallback.executeAllAndDestroy();
}
};
@@ -229,13 +270,14 @@ public final class RecentsActivity extends StatefulActivity {
RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat(
wrapper, RECENTS_LAUNCH_DURATION,
RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
- - STATUS_BAR_TRANSITION_PRE_DELAY);
+ - STATUS_BAR_TRANSITION_PRE_DELAY, getIApplicationThread());
final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(
ActivityOptionsCompat.makeRemoteAnimation(adapterCompat),
onEndCallback);
if (Utilities.ATLEAST_S) {
activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
}
+ mHandler.postDelayed(mAnimationStartTimeoutRunnable, RECENTS_ANIMATION_TIMEOUT);
return activityOptions;
}
@@ -293,7 +335,7 @@ public final class RecentsActivity extends StatefulActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mStateManager = new StateManager<>(this, RecentsState.DEFAULT);
+ mStateManager = new StateManager<>(this, RecentsState.BG_LAUNCHER);
mOldConfig = new Configuration(getResources().getConfiguration());
initDeviceProfile();
@@ -353,6 +395,11 @@ public final class RecentsActivity extends StatefulActivity {
super.onDestroy();
ACTIVITY_TRACKER.onActivityDestroyed(this);
mActivityLaunchAnimationRunner = null;
+
+ mTISBindHelper.onDestroy();
+ if (mTaskbarManager != null) {
+ mTaskbarManager.clearActivity(this);
+ }
}
@Override
@@ -375,7 +422,8 @@ public final class RecentsActivity extends StatefulActivity {
LauncherAnimationRunner runner = new LauncherAnimationRunner(
getMainThreadHandler(), mAnimationToHomeFactory, true);
RemoteAnimationAdapterCompat adapterCompat =
- new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0);
+ new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0,
+ getIApplicationThread());
startActivity(createHomeIntent(),
ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle());
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index a21c7140de..b50267633b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -16,11 +16,14 @@
package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.graphics.Rect;
import android.util.ArraySet;
+import android.view.RemoteAnimationTarget;
import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
@@ -29,6 +32,8 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.Set;
/**
@@ -39,6 +44,7 @@ public class RecentsAnimationCallbacks implements
com.android.systemui.shared.system.RecentsAnimationListener {
private final Set mListeners = new ArraySet<>();
+ private final SystemUiProxy mSystemUiProxy;
private final boolean mAllowMinimizeSplitScreen;
// TODO(141886704): Remove these references when they are no longer needed
@@ -46,7 +52,9 @@ public class RecentsAnimationCallbacks implements
private boolean mCancelled;
- public RecentsAnimationCallbacks(boolean allowMinimizeSplitScreen) {
+ public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy,
+ boolean allowMinimizeSplitScreen) {
+ mSystemUiProxy = systemUiProxy;
mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
}
@@ -70,7 +78,7 @@ public class RecentsAnimationCallbacks implements
public void notifyAnimationCanceled() {
mCancelled = true;
- onAnimationCanceled(null);
+ onAnimationCanceled(new HashMap<>());
}
// Called only in Q platform
@@ -89,8 +97,19 @@ public class RecentsAnimationCallbacks implements
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
Rect homeContentInsets, Rect minimizedHomeBounds) {
+ // Convert appTargets to type RemoteAnimationTarget for all apps except Home app
+ RemoteAnimationTarget[] nonHomeApps = Arrays.stream(appTargets)
+ .filter(remoteAnimationTarget ->
+ remoteAnimationTarget.activityType != ACTIVITY_TYPE_HOME)
+ .map(RemoteAnimationTargetCompat::unwrap)
+ .toArray(RemoteAnimationTarget[]::new);
+
+ RemoteAnimationTarget[] nonAppTargets =
+ mSystemUiProxy.onGoingToRecentsLegacy(mCancelled, nonHomeApps);
+
RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
- wallpaperTargets, homeContentInsets, minimizedHomeBounds);
+ wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets),
+ homeContentInsets, minimizedHomeBounds);
mController = new RecentsAnimationController(animationController,
mAllowMinimizeSplitScreen, this::onAnimationFinished);
@@ -108,20 +127,20 @@ public class RecentsAnimationCallbacks implements
@BinderThread
@Override
- public final void onAnimationCanceled(ThumbnailData thumbnailData) {
+ public final void onAnimationCanceled(HashMap thumbnailDatas) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
for (RecentsAnimationListener listener : getListeners()) {
- listener.onRecentsAnimationCanceled(thumbnailData);
+ listener.onRecentsAnimationCanceled(thumbnailDatas);
}
});
}
@BinderThread
@Override
- public void onTaskAppeared(RemoteAnimationTargetCompat app) {
+ public void onTasksAppeared(RemoteAnimationTargetCompat[] apps) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
for (RecentsAnimationListener listener : getListeners()) {
- listener.onTaskAppeared(app);
+ listener.onTasksAppeared(apps);
}
});
}
@@ -149,16 +168,17 @@ public class RecentsAnimationCallbacks implements
* Callback from the system when the recents animation is canceled. {@param thumbnailData}
* is passed back for rendering screenshot to replace live tile.
*/
- default void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {}
+ default void onRecentsAnimationCanceled(
+ @NonNull HashMap thumbnailDatas) {}
/**
* Callback made whenever the recents animation is finished.
*/
- default void onRecentsAnimationFinished(RecentsAnimationController controller) {}
+ default void onRecentsAnimationFinished(@NonNull RecentsAnimationController controller) {}
/**
* Callback made when a task started from the recents is ready for an app transition.
*/
- default void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {}
+ default void onTasksAppeared(@NonNull RemoteAnimationTargetCompat[] appearedTaskTarget) {}
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 53b66752e2..f343485e7d 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -46,6 +46,8 @@ public class RecentsAnimationController {
private boolean mUseLauncherSysBarFlags = false;
private boolean mSplitScreenMinimized = false;
private boolean mFinishRequested = false;
+ // Only valid when mFinishRequested == true.
+ private boolean mFinishTargetIsLauncher;
private RunnableList mPendingFinishCallbacks = new RunnableList();
public RecentsAnimationController(RecentsAnimationControllerCompat controller,
@@ -145,6 +147,7 @@ public class RecentsAnimationController {
// Finish not yet requested
mFinishRequested = true;
+ mFinishTargetIsLauncher = toRecents;
mOnFinishedListener.accept(this);
mPendingFinishCallbacks.add(callback);
UI_HELPER_EXECUTOR.execute(() -> {
@@ -217,4 +220,12 @@ public class RecentsAnimationController {
public RecentsAnimationControllerCompat getController() {
return mController;
}
+
+ /**
+ * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether
+ * the animation was finished to launcher vs an app.
+ */
+ public boolean getFinishTargetIsLauncher() {
+ return mFinishTargetIsLauncher;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 6491beaaae..6a5e6d3457 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -18,6 +18,7 @@ package com.android.quickstep;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
@@ -58,8 +59,6 @@ import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
@@ -67,7 +66,6 @@ import androidx.annotation.BinderThread;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
@@ -85,7 +83,6 @@ import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Manages the state of the system during a swipe up gesture.
@@ -147,7 +144,7 @@ public class RecentsAnimationDeviceState implements
mContext = context;
mDisplayController = DisplayController.INSTANCE.get(context);
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
- mDisplayId = mDisplayController.getInfo().id;
+ mDisplayId = DEFAULT_DISPLAY;
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
runOnDestroy(() -> mDisplayController.removeChangeListener(this));
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
@@ -399,14 +396,6 @@ public class RecentsAnimationDeviceState implements
&& mGestureBlockedActivities.contains(runningTaskInfo.topActivity);
}
- /**
- * @return the packages of gesture-blocked activities.
- */
- public List getGestureBlockedActivityPackages() {
- return mGestureBlockedActivities.stream().map(ComponentName::getPackageName)
- .collect(Collectors.toList());
- }
-
/**
* Updates the system ui state flags from SystemUI.
*/
@@ -592,8 +581,7 @@ public class RecentsAnimationDeviceState implements
final Info displayInfo = mDisplayController.getInfo();
return (mRotationTouchHelper.touchInOneHandedModeRegion(ev)
&& displayInfo.rotation != Surface.ROTATION_90
- && displayInfo.rotation != Surface.ROTATION_270
- && displayInfo.densityDpi < DisplayMetrics.DENSITY_600);
+ && displayInfo.rotation != Surface.ROTATION_270);
}
return false;
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index 3861bab04b..b6d9016727 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -31,9 +31,9 @@ public class RecentsAnimationTargets extends RemoteAnimationTargets {
public final Rect minimizedHomeBounds;
public RecentsAnimationTargets(RemoteAnimationTargetCompat[] apps,
- RemoteAnimationTargetCompat[] wallpapers, Rect homeContentInsets,
- Rect minimizedHomeBounds) {
- super(apps, wallpapers, new RemoteAnimationTargetCompat[0], MODE_CLOSING);
+ RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps,
+ Rect homeContentInsets, Rect minimizedHomeBounds) {
+ super(apps, wallpapers, nonApps, MODE_CLOSING);
this.homeContentInsets = homeContentInsets;
this.minimizedHomeBounds = minimizedHomeBounds;
}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index c6c3c64f01..f75a6986f2 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -24,6 +24,7 @@ import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
import android.content.Context;
+import android.content.Intent;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
@@ -34,6 +35,7 @@ import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.IconProvider.IconChangeListener;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -41,6 +43,7 @@ import com.android.systemui.shared.system.KeyguardManagerCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -73,7 +76,7 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
private RecentsModel(Context context) {
mContext = context;
mTaskList = new RecentTasksList(MAIN_EXECUTOR,
- new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance());
+ new KeyguardManagerCompat(context), SystemUiProxy.INSTANCE.get(context));
IconProvider iconProvider = new LawnchairIconProvider(context);
mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider);
@@ -100,7 +103,7 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
* always called on the UI thread.
* @return the request id associated with this call.
*/
- public int getTasks(Consumer> callback) {
+ public int getTasks(Consumer> callback) {
return mTaskList.getTasks(false /* loadKeysOnly */, callback);
}
@@ -125,9 +128,9 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
* @param callback Receives true if task is removed, false otherwise
*/
public void isTaskRemoved(int taskId, Consumer callback) {
- mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> {
- for (Task task : tasks) {
- if (task.key.id == taskId) {
+ mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> {
+ for (GroupTask group : taskGroups) {
+ if (group.containsTask(taskId)) {
callback.accept(false);
return;
}
@@ -153,14 +156,15 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
ActivityManager.RunningTaskInfo runningTask =
ActivityManagerWrapper.getInstance().getRunningTask();
int runningTaskId = runningTask != null ? runningTask.id : -1;
- mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {
- for (Task task : tasks) {
- if (task.key.id == runningTaskId) {
+ mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), taskGroups -> {
+ for (GroupTask group : taskGroups) {
+ if (group.containsTask(runningTaskId)) {
// Skip the running task, it's not going to have an up-to-date snapshot by the
// time the user next enters overview
continue;
}
- mThumbnailCache.updateThumbnailInCache(task);
+ mThumbnailCache.updateThumbnailInCache(group.task1);
+ mThumbnailCache.updateThumbnailInCache(group.task2);
}
});
}
@@ -179,7 +183,7 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
@Override
public void onTaskRemoved(int taskId) {
- Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
+ Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, new Intent(), null, 0, 0);
mThumbnailCache.remove(stubKey);
mIconCache.onTaskRemoved(stubKey);
}
@@ -222,6 +226,11 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
mThumbnailChangeListeners.remove(listener);
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "RecentsModel:");
+ mTaskList.dump(" ", writer);
+ }
+
/**
* Listener for receiving various task properties changes
*/
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index c032889db5..b20d48806a 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -77,8 +77,12 @@ public class RemoteAnimationTargets {
* Gets the navigation bar remote animation target if exists.
*/
public RemoteAnimationTargetCompat getNavBarRemoteAnimationTarget() {
+ return getNonAppTargetOfType(TYPE_NAVIGATION_BAR);
+ }
+
+ public RemoteAnimationTargetCompat getNonAppTargetOfType(int type) {
for (RemoteAnimationTargetCompat target : nonApps) {
- if (target.windowType == TYPE_NAVIGATION_BAR) {
+ if (target.windowType == type) {
return target;
}
}
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
new file mode 100644
index 0000000000..ed1a06d05d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2021 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 androidx.annotation.Nullable;
+
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.util.LauncherSplitScreenListener;
+import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TransformParams;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Glues together the necessary components to animate a remote target using a
+ * {@link TaskViewSimulator}
+ */
+public class RemoteTargetGluer {
+ private RemoteTargetHandle[] mRemoteTargetHandles;
+ private StagedSplitBounds mStagedSplitBounds;
+
+ /**
+ * Use this constructor if remote targets are split-screen independent
+ */
+ public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy,
+ RemoteAnimationTargets targets) {
+ mRemoteTargetHandles = createHandles(context, sizingStrategy, targets.apps.length);
+ }
+
+ /**
+ * Use this constructor if you want the number of handles created to match the number of active
+ * running tasks
+ */
+ public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
+ int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getRunningSplitTaskIds();
+ mRemoteTargetHandles = createHandles(context, sizingStrategy, splitIds.length == 2 ? 2 : 1);
+ }
+
+ private RemoteTargetHandle[] createHandles(Context context,
+ BaseActivityInterface sizingStrategy, int numHandles) {
+ RemoteTargetHandle[] handles = new RemoteTargetHandle[numHandles];
+ for (int i = 0; i < numHandles; i++) {
+ TaskViewSimulator tvs = new TaskViewSimulator(context, sizingStrategy);
+ TransformParams transformParams = new TransformParams();
+ handles[i] = new RemoteTargetHandle(tvs, transformParams);
+ }
+ return handles;
+ }
+
+ /**
+ * Pairs together {@link TaskViewSimulator}s and {@link TransformParams} into a
+ * {@link RemoteTargetHandle}
+ * Assigns only the apps associated with {@param targets} into their own TaskViewSimulators.
+ * Length of targets.apps should match that of {@link #mRemoteTargetHandles}.
+ *
+ * If split screen may be active when this is called, you might want to use
+ * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}
+ */
+ public RemoteTargetHandle[] assignTargets(RemoteAnimationTargets targets) {
+ for (int i = 0; i < mRemoteTargetHandles.length; i++) {
+ RemoteAnimationTargetCompat primaryTaskTarget = targets.apps[i];
+ mRemoteTargetHandles[i].mTransformParams.setTargetSet(
+ createRemoteAnimationTargetsForTarget(targets, null));
+ mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
+ }
+ return mRemoteTargetHandles;
+ }
+
+ /**
+ * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this matches the
+ * apps in targets.apps to that of the _active_ split screened tasks.
+ * See {@link #assignTargetsForSplitScreen(RemoteAnimationTargets, int[])}
+ */
+ public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
+ int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getRunningSplitTaskIds();
+ return assignTargetsForSplitScreen(targets, splitIds);
+ }
+
+ /**
+ * Assigns the provided splitIDs to the {@link #mRemoteTargetHandles}, with index 0 will being
+ * the left/top task, index 1 right/bottom
+ */
+ public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets,
+ int[] splitIds) {
+ RemoteAnimationTargetCompat topLeftTarget; // only one set if single/fullscreen task
+ RemoteAnimationTargetCompat bottomRightTarget;
+ if (mRemoteTargetHandles.length == 1) {
+ // If we're not in split screen, the splitIds count doesn't really matter since we
+ // should always hit this case.
+ mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
+ if (targets.apps.length > 0) {
+ // Unclear why/when target.apps length == 0, but it sure does happen :(
+ topLeftTarget = targets.apps[0];
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget, null);
+ }
+ } else {
+ // split screen
+ topLeftTarget = targets.findTask(splitIds[0]);
+ bottomRightTarget = targets.findTask(splitIds[1]);
+
+ // remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude,
+ // vice versa
+ mStagedSplitBounds = new StagedSplitBounds(
+ topLeftTarget.screenSpaceBounds,
+ bottomRightTarget.screenSpaceBounds, splitIds[0], splitIds[1]);
+ mRemoteTargetHandles[0].mTransformParams.setTargetSet(
+ createRemoteAnimationTargetsForTarget(targets, bottomRightTarget));
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget,
+ mStagedSplitBounds);
+
+ mRemoteTargetHandles[1].mTransformParams.setTargetSet(
+ createRemoteAnimationTargetsForTarget(targets, topLeftTarget));
+ mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(bottomRightTarget,
+ mStagedSplitBounds);
+ }
+ return mRemoteTargetHandles;
+ }
+
+ /**
+ * Ensures that we aren't excluding ancillary targets such as home/recents
+ *
+ * @param targetToExclude Will be excluded from the resulting return value.
+ * Pass in {@code null} to not exclude anything
+ * @return RemoteAnimationTargets where all the app targets from the passed in
+ * {@param targets} are included except {@param targetToExclude}
+ */
+ private RemoteAnimationTargets createRemoteAnimationTargetsForTarget(
+ RemoteAnimationTargets targets,
+ @Nullable RemoteAnimationTargetCompat targetToExclude) {
+ int finalLength = targets.unfilteredApps.length - (targetToExclude == null ? 0 : 1);
+ RemoteAnimationTargetCompat[] targetsWithoutExcluded =
+ new RemoteAnimationTargetCompat[finalLength];
+ int i = 0;
+ for (RemoteAnimationTargetCompat targetCompat : targets.unfilteredApps) {
+ if (targetCompat == targetToExclude) {
+ continue;
+ }
+ targetsWithoutExcluded[i] = targetCompat;
+ i++;
+ }
+ return new RemoteAnimationTargets(targetsWithoutExcluded,
+ targets.wallpapers, targets.nonApps, targets.targetMode);
+ }
+
+ public RemoteTargetHandle[] getRemoteTargetHandles() {
+ return mRemoteTargetHandles;
+ }
+
+ public StagedSplitBounds getStagedSplitBounds() {
+ return mStagedSplitBounds;
+ }
+
+ /**
+ * Container to keep together all the associated objects whose properties need to be updated to
+ * animate a single remote app target
+ */
+ public static class RemoteTargetHandle {
+ private final TaskViewSimulator mTaskViewSimulator;
+ private final TransformParams mTransformParams;
+ @Nullable
+ private AnimatorControllerWithResistance mPlaybackController;
+
+ public RemoteTargetHandle(TaskViewSimulator taskViewSimulator,
+ TransformParams transformParams) {
+ mTransformParams = transformParams;
+ mTaskViewSimulator = taskViewSimulator;
+ }
+
+ public TaskViewSimulator getTaskViewSimulator() {
+ return mTaskViewSimulator;
+ }
+
+ public TransformParams getTransformParams() {
+ return mTransformParams;
+ }
+
+ @Nullable
+ public AnimatorControllerWithResistance getPlaybackController() {
+ return mPlaybackController;
+ }
+
+ public void setPlaybackController(
+ @Nullable AnimatorControllerWithResistance playbackController) {
+ mPlaybackController = playbackController;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 8e9b565f43..479a661272 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
@@ -25,7 +26,6 @@ import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
import android.content.Context;
import android.content.res.Resources;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
@@ -35,7 +35,6 @@ import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.quickstep.util.RecentsOrientedState;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -148,7 +147,7 @@ public class RotationTouchHelper implements
mDisplayController = DisplayController.INSTANCE.get(mContext);
Resources resources = mContext.getResources();
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(mContext);
- mDisplayId = mDisplayController.getInfo().id;
+ mDisplayId = DEFAULT_DISPLAY;
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
() -> LawnchairUtilsKt.getWindowCornerRadius(mContext));
diff --git a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
new file mode 100644
index 0000000000..f474796962
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
+import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
+import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
+public class SimpleOrientationTouchTransformer implements
+ DisplayController.DisplayInfoChangeListener {
+
+ public static final MainThreadInitializedObject INSTANCE =
+ new MainThreadInitializedObject<>(SimpleOrientationTouchTransformer::new);
+
+ private OrientationRectF mOrientationRectF;
+
+ public SimpleOrientationTouchTransformer(Context context) {
+ DisplayController.INSTANCE.get(context).addChangeListener(this);
+ onDisplayInfoChanged(context, DisplayController.INSTANCE.get(context).getInfo(),
+ CHANGE_ALL);
+ }
+
+ @Override
+ public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+ if ((flags & (CHANGE_ROTATION | CHANGE_ACTIVE_SCREEN)) == 0) {
+ return;
+ }
+ mOrientationRectF = new OrientationRectF(0, 0, info.currentSize.y, info.currentSize.x,
+ info.rotation);
+ }
+
+ public void transform(MotionEvent ev, int rotation) {
+ mOrientationRectF.applyTransformToRotation(ev, rotation, true /* forceTransform */);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 44954555b0..8e9b668c15 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -17,7 +17,7 @@ package com.android.quickstep;
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_SELECT;
import android.animation.Animator;
import android.content.Context;
@@ -27,7 +27,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
@@ -36,28 +35,32 @@ import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.util.AppCloseConfig;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.RectFSpringAnim2;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
-public abstract class SwipeUpAnimationLogic {
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+public abstract class SwipeUpAnimationLogic implements
+ RecentsAnimationCallbacks.RecentsAnimationListener{
protected static final Rect TEMP_RECT = new Rect();
+ protected final RemoteTargetGluer mTargetGluer;
protected DeviceProfile mDp;
protected final Context mContext;
protected final RecentsAnimationDeviceState mDeviceState;
protected final GestureState mGestureState;
- protected final TaskViewSimulator mTaskViewSimulator;
- protected final TransformParams mTransformParams;
+ protected RemoteTargetHandle[] mRemoteTargetHandles;
// Shift in the range of [0, 1].
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
@@ -70,37 +73,48 @@ public abstract class SwipeUpAnimationLogic {
// How much further we can drag past recents, as a factor of mTransitionDragLength.
protected float mDragLengthFactor = 1;
- protected AnimatorControllerWithResistance mWindowTransitionController;
+ protected boolean mIsSwipeForStagedSplit;
public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
- GestureState gestureState, TransformParams transformParams) {
+ GestureState gestureState) {
mContext = context;
mDeviceState = deviceState;
mGestureState = gestureState;
- mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
- mTransformParams = transformParams;
- mTaskViewSimulator.getOrientationState().update(
- mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
- mDeviceState.getRotationTouchHelper().getDisplayRotation());
+ mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() &&
+ LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getRunningSplitTaskIds().length > 1;
+
+ mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
+ mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().getOrientationState().update(
+ mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
+ mDeviceState.getRotationTouchHelper().getDisplayRotation()
+ ));
}
protected void initTransitionEndpoints(DeviceProfile dp) {
mDp = dp;
-
- mTaskViewSimulator.setDp(dp);
mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
- dp, mContext, TEMP_RECT,
- mTaskViewSimulator.getOrientationState().getOrientationHandler());
+ dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState().getOrientationHandler());
mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
- PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
- mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR);
- AnimatorPlaybackController normalController = pa.createPlaybackController();
- mWindowTransitionController = AnimatorControllerWithResistance.createForRecents(
- normalController, mContext, mTaskViewSimulator.getOrientationState(),
- mDp, mTaskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
- mTaskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE);
+ for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
+ PendingAnimation pendingAnimation = new PendingAnimation(mTransitionDragLength * 2);
+ TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
+ taskViewSimulator.setDp(dp);
+ taskViewSimulator.addAppToOverviewAnim(pendingAnimation, LINEAR);
+ AnimatorPlaybackController playbackController =
+ pendingAnimation.createPlaybackController();
+
+ remoteHandle.setPlaybackController(AnimatorControllerWithResistance.createForRecents(
+ playbackController, mContext, taskViewSimulator.getOrientationState(),
+ mDp, taskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
+ taskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE
+ ));
+ }
}
@UiThread
@@ -125,7 +139,9 @@ public abstract class SwipeUpAnimationLogic {
public abstract void updateFinalShift();
protected PagedOrientationHandler getOrientationHandler() {
- return mTaskViewSimulator.getOrientationState().getOrientationHandler();
+ // OrientationHandler should be independent of remote target, can directly take one
+ return mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState().getOrientationHandler();
}
protected abstract class HomeAnimationFactory {
@@ -167,10 +183,7 @@ public abstract class SwipeUpAnimationLogic {
public void setAnimation(RectFSpringAnim anim) { }
- public boolean keepWindowOpaque() { return false; }
-
- public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
- float radius) { }
+ public void update(RectF currentRect, float progress, float radius) { }
public void onCancel() { }
@@ -207,16 +220,35 @@ public abstract class SwipeUpAnimationLogic {
* @param startProgress The progress of {@link #mCurrentShift} to start thw window from.
* @return {@link RectF} represents the bounds as starting point in window space.
*/
- protected RectF updateProgressForStartRect(Matrix outMatrix, float startProgress) {
+ protected RectF[] updateProgressForStartRect(Matrix[] outMatrix, float startProgress) {
mCurrentShift.updateValue(startProgress);
- mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
- RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
+ RectF[] startRects = new RectF[mRemoteTargetHandles.length];
+ for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
+ i < mRemoteTargetHandlesLength; i++) {
+ RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
+ TaskViewSimulator tvs = remoteHandle.getTaskViewSimulator();
+ tvs.apply(remoteHandle.getTransformParams().setProgress(startProgress));
- mTaskViewSimulator.applyWindowToHomeRotation(outMatrix);
+ startRects[i] = new RectF(tvs.getCurrentCropRect());
+ outMatrix[i] = new Matrix();
+ tvs.applyWindowToHomeRotation(outMatrix[i]);
+ tvs.getCurrentMatrix().mapRect(startRects[i]);
+ }
+ return startRects;
+ }
- final RectF startRect = new RectF(cropRectF);
- mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
- return startRect;
+ /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
+ protected void runActionOnRemoteHandles(Consumer consumer) {
+ for (RemoteTargetHandle handle : mRemoteTargetHandles) {
+ consumer.accept(handle);
+ }
+ }
+
+ /** @return only the TaskViewSimulators from {@link #mRemoteTargetHandles} */
+ protected TaskViewSimulator[] getRemoteTaskViewSimulators() {
+ return Arrays.stream(mRemoteTargetHandles)
+ .map(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator())
+ .toArray(TaskViewSimulator[]::new);
}
/**
@@ -224,33 +256,41 @@ public abstract class SwipeUpAnimationLogic {
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
* @param homeAnimationFactory The home animation factory.
*/
- protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
+ protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
+ // TODO(b/195473584) compute separate end targets for different staged split
final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
+ RectFSpringAnim[] out = new RectFSpringAnim[mRemoteTargetHandles.length];
+ Matrix[] homeToWindowPositionMap = new Matrix[mRemoteTargetHandles.length];
+ RectF[] startRects = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
+ for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
+ i < mRemoteTargetHandlesLength; i++) {
+ RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
+ out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory,
+ targetRect, remoteHandle.getTransformParams(),
+ remoteHandle.getTaskViewSimulator(), startRects[i], homeToWindowPositionMap[i]);
+ }
+ return out;
+ }
- Matrix homeToWindowPositionMap = new Matrix();
- final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
- RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
-
+ private RectFSpringAnim getWindowAnimationToHomeInternal(
+ HomeAnimationFactory homeAnimationFactory, RectF targetRect,
+ TransformParams transformParams, TaskViewSimulator taskViewSimulator,
+ RectF startRect, Matrix homeToWindowPositionMap) {
+ RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect());
// Move the startRect to Launcher space as floatingIconView runs in Launcher
Matrix windowToHomePositionMap = new Matrix();
homeToWindowPositionMap.invert(windowToHomePositionMap);
windowToHomePositionMap.mapRect(startRect);
- RectFSpringAnim anim;
- if (PROTOTYPE_APP_CLOSE.get()) {
- anim = new RectFSpringAnim2(startRect, targetRect, mContext,
- mTaskViewSimulator.getCurrentCornerRadius(),
- homeAnimationFactory.getEndRadius(cropRectF));
- } else {
- anim = new RectFSpringAnim(startRect, targetRect, mContext);
- }
+ RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext, mDp);
homeAnimationFactory.setAnimation(anim);
SpringAnimationRunner runner = new SpringAnimationRunner(
- homeAnimationFactory, cropRectF, homeToWindowPositionMap);
- anim.addOnUpdateListener(runner);
+ homeAnimationFactory, cropRectF, homeToWindowPositionMap,
+ transformParams, taskViewSimulator);
anim.addAnimatorListener(runner);
+ anim.addOnUpdateListener(runner);
return anim;
}
@@ -262,6 +302,7 @@ public abstract class SwipeUpAnimationLogic {
final RectF mWindowCurrentRect = new RectF();
final Matrix mHomeToWindowPositionMap;
+ private final TransformParams mLocalTransformParams;
final HomeAnimationFactory mAnimationFactory;
final AnimatorPlaybackController mHomeAnim;
@@ -271,41 +312,36 @@ public abstract class SwipeUpAnimationLogic {
final float mEndRadius;
SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
- Matrix homeToWindowPositionMap) {
+ Matrix homeToWindowPositionMap, TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator) {
mAnimationFactory = factory;
mHomeAnim = factory.createActivityAnimationToHome();
mCropRectF = cropRectF;
mHomeToWindowPositionMap = homeToWindowPositionMap;
+ mLocalTransformParams = transformParams;
cropRectF.roundOut(mCropRect);
// End on a "round-enough" radius so that the shape reveal doesn't have to do too much
// rounding at the end of the animation.
- mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
+ mStartRadius = taskViewSimulator.getCurrentCornerRadius();
mEndRadius = factory.getEndRadius(cropRectF);
}
@Override
- public void onUpdate(@Nullable AppCloseConfig config, RectF currentRect, float progress) {
+ public void onUpdate(RectF currentRect, float progress) {
mHomeAnim.setPlayFraction(progress);
mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
float alpha = mAnimationFactory.getWindowAlpha(progress);
- if (config != null && PROTOTYPE_APP_CLOSE.get()) {
- alpha = config.getWindowAlpha();
- cornerRadius = config.getCornerRadius();
- }
- if (mAnimationFactory.keepWindowOpaque()) {
- alpha = 1f;
- }
- mTransformParams
+ mLocalTransformParams
.setTargetAlpha(alpha)
.setCornerRadius(cornerRadius);
- mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
- mAnimationFactory.update(config, currentRect, progress,
- mMatrix.mapRadius(cornerRadius));
+ mLocalTransformParams.applySurfaceParams(mLocalTransformParams
+ .createSurfaceParams(this));
+ mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
}
@Override
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 096ad6c188..d52b4c735c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
+import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.app.PendingIntent;
@@ -33,6 +35,8 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.MotionEvent;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import com.android.launcher3.Utilities;
@@ -46,11 +50,17 @@ import com.android.systemui.shared.system.smartspace.ISmartspaceTransitionContro
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.pip.IPipAnimationListener;
+import com.android.wm.shell.recents.IRecentTasks;
+import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
import com.android.wm.shell.startingsurface.IStartingWindow;
import com.android.wm.shell.startingsurface.IStartingWindowListener;
import com.android.wm.shell.transition.IShellTransitions;
+import com.android.wm.shell.util.GroupedRecentTaskInfo;
+
+import java.util.ArrayList;
+import java.util.Arrays;
/**
* Holds the reference to SystemUI.
@@ -69,16 +79,21 @@ public class SystemUiProxy implements ISystemUiProxy,
private IOneHanded mOneHanded;
private IShellTransitions mShellTransitions;
private IStartingWindow mStartingWindow;
+ private IRecentTasks mRecentTasks;
private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
MAIN_EXECUTOR.execute(() -> clearProxy());
};
- // Save the listeners passed into the proxy since when set/register these listeners,
- // setProxy may not have been called, eg. OverviewProxyService is not connected yet.
- private IPipAnimationListener mPendingPipAnimationListener;
- private ISplitScreenListener mPendingSplitScreenListener;
- private IStartingWindowListener mPendingStartingWindowListener;
- private ISmartspaceCallback mPendingSmartspaceCallback;
+ // Save the listeners passed into the proxy since OverviewProxyService may not have been bound
+ // yet, and we'll need to set/register these listeners with SysUI when they do. Note that it is
+ // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
+ // in case SysUI needs to rebind.
+ private IPipAnimationListener mPipAnimationListener;
+ private ISplitScreenListener mSplitScreenListener;
+ private IStartingWindowListener mStartingWindowListener;
+ private ISmartspaceCallback mSmartspaceCallback;
+ private IRecentTasksListener mRecentTasksListener;
+ private final ArrayList mRemoteTransitions = new ArrayList<>();
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -86,6 +101,7 @@ public class SystemUiProxy implements ISystemUiProxy,
private float mLastNavButtonAlpha;
private boolean mLastNavButtonAnimate;
private boolean mHasNavButtonAlphaBeenSet = false;
+ private Runnable mPendingSetNavButtonAlpha = null;
// TODO(141886704): Find a way to remove this
private int mLastSystemUiStateFlags;
@@ -111,6 +127,17 @@ public class SystemUiProxy implements ISystemUiProxy,
}
}
+ @Override
+ public void onImeSwitcherPressed() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onImeSwitcherPressed();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onImeSwitcherPressed", e);
+ }
+ }
+ }
+
@Override
public void setHomeRotationEnabled(boolean enabled) {
if (mSystemUiProxy != null) {
@@ -130,7 +157,7 @@ public class SystemUiProxy implements ISystemUiProxy,
public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
IOneHanded oneHanded, IShellTransitions shellTransitions,
- IStartingWindow startingWindow,
+ IStartingWindow startingWindow, IRecentTasks recentTasks,
ISmartspaceTransitionController smartSpaceTransitionController) {
unlinkToDeath();
mSystemUiProxy = proxy;
@@ -140,28 +167,36 @@ public class SystemUiProxy implements ISystemUiProxy,
mShellTransitions = shellTransitions;
mStartingWindow = startingWindow;
mSmartspaceTransitionController = smartSpaceTransitionController;
+ mRecentTasks = recentTasks;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
- if (mPendingPipAnimationListener != null && mPip != null) {
- setPinnedStackAnimationListener(mPendingPipAnimationListener);
- mPendingPipAnimationListener = null;
+ if (mPipAnimationListener != null && mPip != null) {
+ setPinnedStackAnimationListener(mPipAnimationListener);
}
- if (mPendingSplitScreenListener != null && mSplitScreen != null) {
- registerSplitScreenListener(mPendingSplitScreenListener);
- mPendingSplitScreenListener = null;
+ if (mSplitScreenListener != null && mSplitScreen != null) {
+ registerSplitScreenListener(mSplitScreenListener);
}
- if (mPendingStartingWindowListener != null && mStartingWindow != null) {
- setStartingWindowListener(mPendingStartingWindowListener);
- mPendingStartingWindowListener = null;
+ if (mStartingWindowListener != null && mStartingWindow != null) {
+ setStartingWindowListener(mStartingWindowListener);
}
- if (mPendingSmartspaceCallback != null && mSmartspaceTransitionController != null) {
- setSmartspaceCallback(mPendingSmartspaceCallback);
- mPendingSmartspaceCallback = null;
+ if (mSmartspaceCallback != null && mSmartspaceTransitionController != null) {
+ setSmartspaceCallback(mSmartspaceCallback);
+ }
+ for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) {
+ registerRemoteTransition(mRemoteTransitions.get(i));
+ }
+ if (mRecentTasksListener != null && mRecentTasks != null) {
+ registerRecentTasksListener(mRecentTasksListener);
+ }
+
+ if (mPendingSetNavButtonAlpha != null) {
+ mPendingSetNavButtonAlpha.run();
+ mPendingSetNavButtonAlpha = null;
}
}
public void clearProxy() {
- setProxy(null, null, null, null, null, null, null);
+ setProxy(null, null, null, null, null, null, null, null);
}
// TODO(141886704): Find a way to remove this
@@ -241,14 +276,18 @@ public class SystemUiProxy implements ISystemUiProxy,
boolean changed = Float.compare(alpha, mLastNavButtonAlpha) != 0
|| animate != mLastNavButtonAnimate
|| !mHasNavButtonAlphaBeenSet;
- if (mSystemUiProxy != null && changed) {
- mLastNavButtonAlpha = alpha;
- mLastNavButtonAnimate = animate;
- mHasNavButtonAlphaBeenSet = true;
- try {
- mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
+ if (changed) {
+ if (mSystemUiProxy == null) {
+ mPendingSetNavButtonAlpha = () -> setNavBarButtonAlpha(alpha, animate);
+ } else {
+ mLastNavButtonAlpha = alpha;
+ mLastNavButtonAnimate = animate;
+ mHasNavButtonAlphaBeenSet = true;
+ try {
+ mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
+ }
}
}
}
@@ -359,7 +398,7 @@ public class SystemUiProxy implements ISystemUiProxy,
try {
mSystemUiProxy.setSplitScreenMinimized(minimized);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call stopScreenPinning", e);
+ Log.w(TAG, "Failed call setSplitScreenMinimized", e);
}
}
}
@@ -406,6 +445,33 @@ public class SystemUiProxy implements ISystemUiProxy,
// just a placeholder
}
+ public void notifyTaskbarStatus(boolean visible, boolean stashed) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call notifyTaskbarStatus with arg: " +
+ visible + ", " + stashed, e);
+ }
+ }
+ }
+
+ /**
+ * NOTE: If called to suspend, caller MUST call this method to also un-suspend
+ * @param suspend should be true to stop auto-hide, false to resume normal behavior
+ */
+ @Override
+ public void notifyTaskbarAutohideSuspend(boolean suspend) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.notifyTaskbarAutohideSuspend(suspend);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call notifyTaskbarAutohideSuspend with arg: " +
+ suspend, e);
+ }
+ }
+ }
+
@Override
public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
Insets visibleInsets, Task.TaskKey task) {
@@ -466,9 +532,8 @@ public class SystemUiProxy implements ISystemUiProxy,
} catch (RemoteException e) {
Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
}
- } else {
- mPendingPipAnimationListener = listener;
}
+ mPipAnimationListener = listener;
}
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
@@ -506,9 +571,8 @@ public class SystemUiProxy implements ISystemUiProxy,
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerSplitScreenListener");
}
- } else {
- mPendingSplitScreenListener = listener;
}
+ mSplitScreenListener = listener;
}
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
@@ -519,68 +583,44 @@ public class SystemUiProxy implements ISystemUiProxy,
Log.w(TAG, "Failed call unregisterSplitScreenListener");
}
}
- mPendingSplitScreenListener = null;
- }
-
- public void setSideStageVisibility(boolean visible) {
- if (mSplitScreen != null) {
- try {
- mSplitScreen.setSideStageVisibility(visible);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setSideStageVisibility");
- }
- }
- }
-
- public void exitSplitScreen() {
- if (mSplitScreen != null) {
- try {
- mSplitScreen.exitSplitScreen();
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call exitSplitScreen");
- }
- }
- }
-
- public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- if (mSplitScreen != null) {
- try {
- mSplitScreen.exitSplitScreenOnHide(exitSplitScreenOnHide);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call exitSplitScreen");
- }
- }
- }
-
- public void startTask(int taskId, int stage, int position, Bundle options) {
- if (mSplitScreen != null) {
- try {
- mSplitScreen.startTask(taskId, stage, position, options);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call startTask");
- }
- }
+ mSplitScreenListener = null;
}
/** Start multiple tasks in split-screen simultaneously. */
public void startTasks(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions,
- @SplitConfigurationOptions.StagePosition int sidePosition,
+ @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
RemoteTransitionCompat remoteTransition) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startTasks(mainTaskId, mainOptions, sideTaskId, sideOptions,
- sidePosition, remoteTransition.getTransition());
+ sidePosition, splitRatio, remoteTransition.getTransition());
} catch (RemoteException e) {
Log.w(TAG, "Failed call startTask");
}
}
}
- public void startShortcut(String packageName, String shortcutId, int stage, int position,
+ /**
+ * Start multiple tasks in split-screen simultaneously.
+ */
+ public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
+ Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
+ float splitRatio, RemoteAnimationAdapter adapter) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
+ sideOptions, sidePosition, splitRatio, adapter);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call startTasksWithLegacyTransition");
+ }
+ }
+ }
+
+ public void startShortcut(String packageName, String shortcutId, int position,
Bundle options, UserHandle user) {
if (mSplitScreen != null) {
try {
- mSplitScreen.startShortcut(packageName, shortcutId, stage, position, options,
+ mSplitScreen.startShortcut(packageName, shortcutId, position, options,
user);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startShortcut");
@@ -588,11 +628,11 @@ public class SystemUiProxy implements ISystemUiProxy,
}
}
- public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
Bundle options) {
if (mSplitScreen != null) {
try {
- mSplitScreen.startIntent(intent, fillInIntent, stage, position, options);
+ mSplitScreen.startIntent(intent, fillInIntent, position, options);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntent");
}
@@ -609,6 +649,25 @@ public class SystemUiProxy implements ISystemUiProxy,
}
}
+ /**
+ * Call this when going to recents so that shell can set-up and provide appropriate leashes
+ * for animation (eg. DividerBar).
+ *
+ * @param cancel true if recents starting is being cancelled.
+ * @return RemoteAnimationTargets of windows that need to animate but only exist in shell.
+ */
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ RemoteAnimationTarget[] apps) {
+ if (mSplitScreen != null) {
+ try {
+ return mSplitScreen.onGoingToRecentsLegacy(cancel, apps);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onGoingToRecentsLegacy");
+ }
+ }
+ return null;
+ }
+
//
// One handed
//
@@ -646,6 +705,9 @@ public class SystemUiProxy implements ISystemUiProxy,
Log.w(TAG, "Failed call registerRemoteTransition");
}
}
+ if (!mRemoteTransitions.contains(remoteTransition)) {
+ mRemoteTransitions.add(remoteTransition);
+ }
}
public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
@@ -656,6 +718,7 @@ public class SystemUiProxy implements ISystemUiProxy,
Log.w(TAG, "Failed call registerRemoteTransition");
}
}
+ mRemoteTransitions.remove(remoteTransition);
}
//
@@ -672,12 +735,10 @@ public class SystemUiProxy implements ISystemUiProxy,
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
- } else {
- mPendingStartingWindowListener = listener;
}
+ mStartingWindowListener = listener;
}
-
//
// SmartSpace transitions
//
@@ -689,8 +750,45 @@ public class SystemUiProxy implements ISystemUiProxy,
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
- } else {
- mPendingSmartspaceCallback = callback;
}
+ mSmartspaceCallback = callback;
+ }
+
+ //
+ // Recents
+ //
+
+ public void registerRecentTasksListener(IRecentTasksListener listener) {
+ if (mRecentTasks != null) {
+ try {
+ mRecentTasks.registerRecentTasksListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call registerRecentTasksListener", e);
+ }
+ }
+ mRecentTasksListener = listener;
+ }
+
+ public void unregisterRecentTasksListener(IRecentTasksListener listener) {
+ if (mRecentTasks != null) {
+ try {
+ mRecentTasks.unregisterRecentTasksListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call unregisterRecentTasksListener");
+ }
+ }
+ mRecentTasksListener = null;
+ }
+
+ public ArrayList getRecentTasks(int numTasks, int userId) {
+ if (mRecentTasks != null) {
+ try {
+ return new ArrayList<>(Arrays.asList(mRecentTasks.getRecentTasks(numTasks,
+ RECENT_IGNORE_UNAVAILABLE, userId)));
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call getRecentTasks", e);
+ }
+ }
+ return new ArrayList<>();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 67bd85fffe..82c7c08153 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -20,6 +20,7 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.app.ActivityManager;
import android.content.Context;
@@ -27,7 +28,9 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.SystemProperties;
import android.util.Log;
+import android.view.RemoteAnimationTarget;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
@@ -41,6 +44,9 @@ import com.android.systemui.shared.system.RemoteTransitionCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import java.util.Arrays;
+import java.util.HashMap;
+
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
public static final boolean ENABLE_SHELL_TRANSITIONS =
SystemProperties.getBoolean("persist.debug.shell_transit", false);
@@ -106,9 +112,17 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
// But force-finish it anyways
finishRunningRecentsAnimation(false /* toHome */);
+ if (mCallbacks != null) {
+ // If mCallbacks still != null, that means we are getting this startRecentsAnimation()
+ // before the previous one got onRecentsAnimationStart(). In that case, cleanup the
+ // previous animation so it doesn't mess up/listen to state changes in this animation.
+ cleanUpRecentsAnimation();
+ }
+
final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
mLastGestureState = gestureState;
- mCallbacks = new RecentsAnimationCallbacks(activityInterface.allowMinimizeSplitScreen());
+ mCallbacks = new RecentsAnimationCallbacks(SystemUiProxy.INSTANCE.get(mCtx),
+ activityInterface.allowMinimizeSplitScreen());
mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
@@ -126,7 +140,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
}
@Override
- public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ public void onRecentsAnimationCanceled(HashMap thumbnailDatas) {
cleanUpRecentsAnimation();
}
@@ -136,18 +150,29 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
}
@Override
- public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
+ RemoteAnimationTargetCompat appearedTaskTarget = appearedTaskTargets[0];
BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
+ // Convert appTargets to type RemoteAnimationTarget for all apps except Home app
+ RemoteAnimationTarget[] nonHomeApps = Arrays.stream(appearedTaskTargets)
+ .filter(remoteAnimationTarget ->
+ remoteAnimationTarget.activityType != ACTIVITY_TYPE_HOME)
+ .map(RemoteAnimationTargetCompat::unwrap)
+ .toArray(RemoteAnimationTarget[]::new);
+
+ RemoteAnimationTarget[] nonAppTargets =
+ SystemUiProxy.INSTANCE.getNoCreate()
+ .onGoingToRecentsLegacy(false, nonHomeApps);
+
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
&& activityInterface.getCreatedActivity() != null) {
RecentsView recentsView =
activityInterface.getCreatedActivity().getOverviewPanel();
if (recentsView != null) {
- RemoteAnimationTargetCompat[] apps = new RemoteAnimationTargetCompat[1];
- apps[0] = appearedTaskTarget;
- recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId, apps,
+ recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
+ appearedTaskTargets,
new RemoteAnimationTargetCompat[0] /* wallpaper */,
- new RemoteAnimationTargetCompat[0] /* nonApps */);
+ RemoteAnimationTargetCompat.wrap(nonAppTargets) /* nonApps */);
return;
}
}
@@ -169,7 +194,8 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
if (ENABLE_SHELL_TRANSITIONS) {
RemoteTransitionCompat transition = new RemoteTransitionCompat(mCallbacks,
- mController != null ? mController.getController() : null);
+ mController != null ? mController.getController() : null,
+ mCtx.getIApplicationThread());
Bundle options = ActivityOptionsCompat.makeRemoteTransition(transition)
.setTransientLaunch().toBundle();
mCtx.startActivity(intent, options);
@@ -194,6 +220,22 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
return mCallbacks;
}
+ public void endLiveTile() {
+ if (mLastGestureState == null) {
+ return;
+ }
+ BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+ && activityInterface.getCreatedActivity() != null) {
+ RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
+ if (recentsView != null) {
+ recentsView.switchToScreenshot(null,
+ () -> recentsView.finishRecentsAnimation(true /* toRecents */,
+ false /* shouldPip */, null));
+ }
+ }
+ }
+
public void setLiveTileCleanUpHandler(Runnable cleanUpHandler) {
mLiveTileCleanUpHandler = cleanUpHandler;
}
@@ -262,6 +304,11 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
mLastAppearedTaskTarget = null;
}
+ @Nullable
+ public RecentsAnimationCallbacks getCurrentCallbacks() {
+ return mCallbacks;
+ }
+
public void dump() {
// TODO
}
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index e75d751582..276e1c2a6f 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -23,6 +23,7 @@ import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBN
import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
import android.annotation.SuppressLint;
+import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Matrix;
@@ -51,8 +52,10 @@ import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.util.ArrayList;
import java.util.List;
@@ -63,41 +66,45 @@ import java.util.List;
public class TaskOverlayFactory implements ResourceBasedOverride {
public static List getEnabledShortcuts(TaskView taskView,
- DeviceProfile deviceProfile) {
+ DeviceProfile deviceProfile, TaskIdAttributeContainer taskContainer) {
final ArrayList shortcuts = new ArrayList<>();
final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
+ boolean hasMultipleTasks = taskView.getTaskIds()[1] != -1;
for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
- SystemShortcut shortcut = menuOption.getShortcut(activity, taskView);
- if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
- FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
- addSplitOptions(shortcuts, activity, taskView, deviceProfile);
+ if (hasMultipleTasks && !menuOption.showForSplitscreen()) {
continue;
}
- if (shortcut != null) {
+ SystemShortcut shortcut = menuOption.getShortcut(activity, taskContainer);
+ if (shortcut == null) {
+ continue;
+ }
+
+ if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
+ FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+ addSplitOptions(shortcuts, activity, taskView, deviceProfile);
+ } else {
shortcuts.add(shortcut);
}
}
RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState();
- boolean canLauncherRotate = orientedState.canRecentsActivityRotate();
+ boolean canLauncherRotate = orientedState.isRecentsActivityRotationAllowed();
boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
// Add overview actions to the menu when in in-place rotate landscape mode.
if (!canLauncherRotate && isInLandscape) {
// Add screenshot action to task menu.
SystemShortcut screenshotShortcut = TaskShortcutFactory.SCREENSHOT
- .getShortcut(activity, taskView);
+ .getShortcut(activity, taskContainer);
if (screenshotShortcut != null) {
- screenshotShortcut.setHasFinishRecentsInAction(true);
shortcuts.add(screenshotShortcut);
}
// Add modal action only if display orientation is the same as the device orientation.
if (orientedState.getDisplayRotation() == ROTATION_0) {
SystemShortcut modalShortcut = TaskShortcutFactory.MODAL
- .getShortcut(activity, taskView);
+ .getShortcut(activity, taskContainer);
if (modalShortcut != null) {
- modalShortcut.setHasFinishRecentsInAction(true);
shortcuts.add(modalShortcut);
}
}
@@ -106,10 +113,35 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
}
- public static void addSplitOptions(List outShortcuts,
+ /**
+ * Does NOT add split options in the following scenarios:
+ * * The taskView to add split options is already showing split screen tasks
+ * * There aren't at least 2 tasks in overview to show split options for
+ * * Device is in "Lock task mode"
+ * * The taskView to show split options for is the focused task AND we haven't started
+ * scrolling in overview (if we haven't scrolled, there's a split overview action button so
+ * we don't need this menu option)
+ */
+ private static void addSplitOptions(List outShortcuts,
BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) {
- PagedOrientationHandler orientationHandler =
- taskView.getRecentsView().getPagedOrientationHandler();
+ RecentsView recentsView = taskView.getRecentsView();
+ PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ int[] taskViewTaskIds = taskView.getTaskIds();
+ boolean taskViewHasMultipleTasks = taskViewTaskIds[0] != -1 &&
+ taskViewTaskIds[1] != -1;
+ boolean notEnoughTasksToSplit = recentsView.getTaskViewCount() < 2;
+ boolean isFocusedTask = deviceProfile.overviewShowAsGrid && taskView.isFocusedTask();
+ boolean isTaskInExpectedScrollPosition =
+ recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
+ ActivityManager activityManager =
+ (ActivityManager) taskView.getContext().getSystemService(Context.ACTIVITY_SERVICE);
+ boolean isLockTaskMode = activityManager.isInLockTaskMode();
+
+ if (taskViewHasMultipleTasks || notEnoughTasksToSplit || isLockTaskMode ||
+ (isFocusedTask && isTaskInExpectedScrollPosition)) {
+ return;
+ }
+
List positions =
orientationHandler.getSplitPositionOptions(deviceProfile);
for (SplitPositionOption option : positions) {
@@ -212,6 +244,11 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
}
}
+ private void enterSplitSelect() {
+ RecentsView overviewPanel = mThumbnailView.getTaskView().getRecentsView();
+ overviewPanel.initiateSplitSelect(mThumbnailView.getTaskView());
+ }
+
/**
* Called when the overlay is no longer used.
*/
@@ -305,18 +342,14 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
mTask = task;
}
- public void onShare() {
- if (mIsAllowedByPolicy) {
- endLiveTileMode(() -> mImageApi.startShareActivity(null));
- } else {
- showBlockedByPolicyMessage();
- }
- }
-
@SuppressLint("NewApi")
public void onScreenshot() {
endLiveTileMode(() -> saveScreenshot(mTask));
}
+
+ public void onSplit() {
+ endLiveTileMode(TaskOverlay.this::enterSplitSelect);
+ }
}
}
@@ -325,10 +358,10 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
* controller.
*/
public interface OverlayUICallbacks {
- /** User has indicated they want to share the current task. */
- void onShare();
-
/** User has indicated they want to screenshot the current task. */
void onScreenshot();
+
+ /** User wants to start split screen with current app. */
+ void onSplit();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index a0fcfe9002..5d3a91215c 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -36,7 +36,6 @@ import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
@@ -46,6 +45,7 @@ import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
@@ -62,9 +62,31 @@ import java.util.List;
* Represents a system shortcut that can be shown for a recent task.
*/
public interface TaskShortcutFactory {
- SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
+ SystemShortcut getShortcut(BaseDraggingActivity activity,
+ TaskIdAttributeContainer taskContainer);
- TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, view.getItemInfo());
+ default boolean showForSplitscreen() {
+ return false;
+ }
+
+ TaskShortcutFactory APP_INFO = new TaskShortcutFactory() {
+ @Override
+ public SystemShortcut getShortcut(BaseDraggingActivity activity,
+ TaskIdAttributeContainer taskContainer) {
+ TaskView taskView = taskContainer.getTaskView();
+ AppInfo.SplitAccessibilityInfo accessibilityInfo =
+ new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(),
+ TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
+ taskContainer.getA11yNodeId()
+ );
+ return new AppInfo(activity, taskContainer.getItemInfo(), accessibilityInfo);
+ }
+
+ @Override
+ public boolean showForSplitscreen() {
+ return true;
+ }
+ };
abstract class MultiWindowFactory implements TaskShortcutFactory {
@@ -83,28 +105,28 @@ public interface TaskShortcutFactory {
protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
@Override
- public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView taskView) {
- final Task task = taskView.getTask();
+ public SystemShortcut getShortcut(BaseDraggingActivity activity,
+ TaskIdAttributeContainer taskContainer) {
+ final Task task = taskContainer.getTask();
if (!task.isDockable) {
return null;
}
if (!isAvailable(activity, task.key.displayId)) {
return null;
}
- return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this,
+ return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskContainer, this,
mLauncherEvent);
}
}
class SplitSelectSystemShortcut extends SystemShortcut {
private final TaskView mTaskView;
- private SplitPositionOption mSplitPositionOption;
+ private final SplitPositionOption mSplitPositionOption;
public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
SplitPositionOption option) {
- super(option.mIconResId, option.mTextResId, target, taskView.getItemInfo());
+ super(option.iconResId, option.textResId, target, taskView.getItemInfo());
mTaskView = taskView;
mSplitPositionOption = option;
- setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
}
@Override
@@ -113,7 +135,7 @@ public interface TaskShortcutFactory {
}
}
- class MultiWindowSystemShortcut extends SystemShortcut {
+ class MultiWindowSystemShortcut extends SystemShortcut {
private Handler mHandler;
@@ -124,13 +146,14 @@ public interface TaskShortcutFactory {
private final LauncherEvent mLauncherEvent;
public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
- TaskView taskView, MultiWindowFactory factory, LauncherEvent launcherEvent) {
- super(iconRes, textRes, activity, taskView.getItemInfo());
+ TaskIdAttributeContainer taskContainer, MultiWindowFactory factory,
+ LauncherEvent launcherEvent) {
+ super(iconRes, textRes, activity, taskContainer.getItemInfo());
mLauncherEvent = launcherEvent;
mHandler = new Handler(Looper.getMainLooper());
- mTaskView = taskView;
+ mTaskView = taskContainer.getTaskView();
mRecentsView = activity.getOverviewPanel();
- mThumbnailView = taskView.getThumbnail();
+ mThumbnailView = taskContainer.getThumbnailView();
mFactory = factory;
}
@@ -220,6 +243,7 @@ public interface TaskShortcutFactory {
}
}
+ /** @Deprecated */
TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(R.drawable.ic_split_screen,
R.string.recent_task_option_split_screen, LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP) {
@@ -233,16 +257,6 @@ public interface TaskShortcutFactory {
&& (displayId == -1 || displayId == DEFAULT_DISPLAY);
}
- @Override
- public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView taskView) {
- SystemShortcut shortcut = super.getShortcut(activity, taskView);
- if (shortcut != null && FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
- // Disable if there's only one recent app for split screen
- shortcut.setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
- }
- return shortcut;
- }
-
@Override
protected ActivityOptions makeLaunchOptions(Activity activity) {
final ActivityCompat act = new ActivityCompat(activity);
@@ -285,7 +299,7 @@ public interface TaskShortcutFactory {
}
};
- TaskShortcutFactory PIN = (activity, tv) -> {
+ TaskShortcutFactory PIN = (activity, taskContainer) -> {
if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
return null;
}
@@ -296,18 +310,20 @@ public interface TaskShortcutFactory {
// We shouldn't be able to pin while an app is locked.
return null;
}
- return new PinSystemShortcut(activity, tv);
+ return new PinSystemShortcut(activity, taskContainer);
};
- class PinSystemShortcut extends SystemShortcut {
+ class PinSystemShortcut extends SystemShortcut {
private static final String TAG = "PinSystemShortcut";
private final TaskView mTaskView;
- public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) {
- super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, tv.getItemInfo());
- mTaskView = tv;
+ public PinSystemShortcut(BaseDraggingActivity target,
+ TaskIdAttributeContainer taskContainer) {
+ super(R.drawable.ic_pin, R.string.recent_task_option_pin, target,
+ taskContainer.getItemInfo());
+ mTaskView = taskContainer.getTaskView();
}
@Override
@@ -321,20 +337,22 @@ public interface TaskShortcutFactory {
}
}
- TaskShortcutFactory INSTALL = (activity, view) ->
+ TaskShortcutFactory INSTALL = (activity, taskContainer) ->
InstantAppResolver.newInstance(activity).isInstantApp(activity,
- view.getTask().getTopComponent().getPackageName())
- ? new SystemShortcut.Install(activity, view.getItemInfo()) : null;
+ taskContainer.getTask().getTopComponent().getPackageName())
+ ? new SystemShortcut.Install(activity, taskContainer.getItemInfo()) : null;
- TaskShortcutFactory WELLBEING = (activity, view) ->
- WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, view.getItemInfo());
+ TaskShortcutFactory WELLBEING = (activity, taskContainer) ->
+ WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, taskContainer.getItemInfo());
- TaskShortcutFactory SCREENSHOT = (activity, tv) -> tv.getThumbnail().getTaskOverlay()
- .getScreenshotShortcut(activity, tv.getItemInfo());
+ TaskShortcutFactory SCREENSHOT = (activity, taskContainer) ->
+ taskContainer.getThumbnailView().getTaskOverlay()
+ .getScreenshotShortcut(activity, taskContainer.getItemInfo());
- TaskShortcutFactory MODAL = (activity, tv) -> {
+ TaskShortcutFactory MODAL = (activity, taskContainer) -> {
if (ENABLE_OVERVIEW_SELECTIONS.get()) {
- return tv.getThumbnail().getTaskOverlay().getModalStateSystemShortcut(tv.getItemInfo());
+ return taskContainer.getThumbnailView()
+ .getTaskOverlay().getModalStateSystemShortcut(taskContainer.getItemInfo());
}
return null;
};
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index a8a0219594..3175ba8463 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -104,6 +104,9 @@ public class TaskThumbnailCache {
* Synchronously fetches the thumbnail for the given {@param task} and puts it in the cache.
*/
public void updateThumbnailInCache(Task task) {
+ if (task == null) {
+ return;
+ }
Preconditions.assertUIThread();
// Fetch the thumbnail for this task and put it in the cache
if (task.thumbnail == null) {
@@ -131,7 +134,8 @@ public class TaskThumbnailCache {
Preconditions.assertUIThread();
boolean lowResolution = !mHighResLoadingState.isEnabled();
- if (task.thumbnail != null && (!task.thumbnail.reducedResolution || lowResolution)) {
+ if (task.thumbnail != null && task.thumbnail.thumbnail != null
+ && (!task.thumbnail.reducedResolution || lowResolution)) {
// Nothing to load, the thumbnail is already high-resolution or matches what the
// request, so just callback
callback.accept(task.thumbnail);
@@ -149,7 +153,8 @@ public class TaskThumbnailCache {
Preconditions.assertUIThread();
ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
- if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || lowResolution)) {
+ if (cachedThumbnail != null && cachedThumbnail.thumbnail != null
+ && (!cachedThumbnail.reducedResolution || lowResolution)) {
// Already cached, lets use that thumbnail
callback.accept(cachedThumbnail);
return null;
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 37fda73d44..5d9a537165 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -27,10 +28,11 @@ import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FAD
import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_IN_INTERPOLATOR;
import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_OUT_INTERPOLATOR;
import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.SPLIT_DIVIDER_ANIM_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
@@ -41,6 +43,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
@@ -62,15 +65,16 @@ import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
@@ -79,6 +83,9 @@ import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Utility class for helpful methods related to {@link TaskView} objects and their tasks.
*/
@@ -139,39 +146,21 @@ public final class TaskViewUtils {
// If the opening task id is not currently visible in overview, then fall back to normal app
// icon launch animation
- TaskView taskView = recentsView.getTaskView(openingTaskId);
+ TaskView taskView = recentsView.getTaskViewByTaskId(openingTaskId);
if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
return null;
}
return taskView;
}
- public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
- RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets,
- RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
+ public static void createRecentsWindowAnimator(
+ @NonNull TaskView v, boolean skipViewChanges,
+ @NonNull RemoteAnimationTargetCompat[] appTargets,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
+ @NonNull RemoteAnimationTargetCompat[] nonAppTargets,
+ @Nullable DepthController depthController,
PendingAnimation out) {
- boolean isRunningTask = v.isRunningTask();
- TransformParams params = null;
- TaskViewSimulator tsv = null;
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
- params = v.getRecentsView().getLiveTileParams();
- tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
- }
- createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets, nonAppTargets,
- depthController, out, params, tsv);
- }
-
- /**
- * Creates an animation that controls the window of the opening targets for the recents launch
- * animation.
- */
- public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
- RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets,
- RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
- PendingAnimation out, @Nullable TransformParams params,
- @Nullable TaskViewSimulator tsv) {
+ RecentsView recentsView = v.getRecentsView();
boolean isQuickSwitch = v.isEndQuickswitchCuj();
v.setEndQuickswitchCuj(false);
@@ -182,64 +171,77 @@ public final class TaskViewUtils {
inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
final RemoteAnimationTargetCompat navBarTarget = targets.getNavBarRemoteAnimationTarget();
- if (params == null) {
- SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
- targets.addReleaseCheck(applier);
+ SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+ targets.addReleaseCheck(applier);
- params = new TransformParams()
- .setSyncTransactionApplier(applier)
- .setTargetSet(targets);
+ RemoteTargetHandle[] remoteTargetHandles;
+ RemoteTargetHandle[] recentsViewHandles = recentsView.getRemoteTargetHandles();
+ if (v.isRunningTask() && recentsViewHandles != null) {
+ // Re-use existing handles
+ remoteTargetHandles = recentsViewHandles;
+ } else {
+ RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
+ recentsView.getSizeStrategy(), targets);
+ if (v.containsMultipleTasks()) {
+ remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets, v.getTaskIds());
+ } else {
+ remoteTargetHandles = gluer.assignTargets(targets);
+ }
+ }
+ for (RemoteTargetHandle remoteTargetGluer : remoteTargetHandles) {
+ remoteTargetGluer.getTransformParams().setSyncTransactionApplier(applier);
}
- final RecentsView recentsView = v.getRecentsView();
int taskIndex = recentsView.indexOfChild(v);
Context context = v.getContext();
DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
- boolean showAsGrid = dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
+ boolean showAsGrid = dp.overviewShowAsGrid;
boolean parallaxCenterAndAdjacentTask =
taskIndex != recentsView.getCurrentPage() && !showAsGrid;
- float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex);
- int startScroll = recentsView.getScrollOffset(taskIndex);
+ int taskRectTranslationPrimary = recentsView.getScrollOffset(taskIndex);
+ int taskRectTranslationSecondary = showAsGrid ? (int) v.getGridTranslationY() : 0;
- TaskViewSimulator topMostSimulator = null;
+ RemoteTargetHandle[] topMostSimulators = null;
- if (tsv == null && targets.apps.length > 0) {
- tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
- tsv.setDp(dp);
+ if (!v.isRunningTask()) {
+ // TVSs already initialized from the running task, no need to re-init
+ for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
+ TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
+ tvsLocal.setDp(dp);
- // RecentsView never updates the display rotation until swipe-up so the value may
- // be stale. Use the display value instead.
- int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation;
- tsv.getOrientationState().update(displayRotation, displayRotation);
+ // RecentsView never updates the display rotation until swipe-up so the value may
+ // be stale. Use the display value instead.
+ int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation;
+ tvsLocal.getOrientationState().update(displayRotation, displayRotation);
- tsv.setPreview(targets.apps[targets.apps.length - 1]);
- tsv.fullScreenProgress.value = 0;
- tsv.recentsViewScale.value = 1;
- if (showAsGrid) {
- tsv.taskSecondaryTranslation.value = gridTranslationSecondary;
+ tvsLocal.fullScreenProgress.value = 0;
+ tvsLocal.recentsViewScale.value = 1;
+ tvsLocal.setIsGridTask(v.isGridTask());
+ tvsLocal.getOrientationState().getOrientationHandler().set(tvsLocal,
+ TaskViewSimulator::setTaskRectTranslation, taskRectTranslationPrimary,
+ taskRectTranslationSecondary);
+
+ // Fade in the task during the initial 20% of the animation
+ out.addFloat(targetHandle.getTransformParams(), TransformParams.TARGET_ALPHA, 0, 1,
+ clampToProgress(LINEAR, 0, 0.2f));
}
- tsv.setScroll(startScroll);
-
- // Fade in the task during the initial 20% of the animation
- out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
- clampToProgress(LINEAR, 0, 0.2f));
}
- if (tsv != null) {
- out.setFloat(tsv.fullScreenProgress,
+ for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
+ TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
+ out.setFloat(tvsLocal.fullScreenProgress,
AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
- out.setFloat(tsv.recentsViewScale,
- AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
- if (showAsGrid) {
- out.setFloat(tsv.taskSecondaryTranslation, AnimatedFloat.VALUE, 0,
- TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL);
- }
- out.setFloat(tsv.recentsViewScroll, AnimatedFloat.VALUE, 0,
+ out.setFloat(tvsLocal.recentsViewScale,
+ AnimatedFloat.VALUE, tvsLocal.getFullScreenScale(),
+ TOUCH_RESPONSE_INTERPOLATOR);
+ out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
TOUCH_RESPONSE_INTERPOLATOR);
- TaskViewSimulator finalTsv = tsv;
- TransformParams finalParams = params;
- out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
+ out.addOnFrameCallback(() -> {
+ for (RemoteTargetHandle handle : remoteTargetHandles) {
+ handle.getTaskViewSimulator().apply(handle.getTransformParams());
+ }
+ });
if (navBarTarget != null) {
final Rect cropRect = new Rect();
out.addOnFrameListener(new MultiValueUpdateListener() {
@@ -252,15 +254,20 @@ public final class TaskViewUtils {
public void onUpdate(float percent, boolean initOnly) {
final SurfaceParams.Builder navBuilder =
new SurfaceParams.Builder(navBarTarget.leash);
- if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
- finalTsv.getCurrentCropRect().round(cropRect);
- navBuilder.withMatrix(finalTsv.getCurrentMatrix())
- .withWindowCrop(cropRect)
- .withAlpha(mNavFadeIn.value);
- } else {
- navBuilder.withAlpha(mNavFadeOut.value);
+
+ // TODO Do we need to operate over multiple TVSs for the navbar leash?
+ for (RemoteTargetHandle handle : remoteTargetHandles) {
+ if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
+ TaskViewSimulator taskViewSimulator = handle.getTaskViewSimulator();
+ taskViewSimulator.getCurrentCropRect().round(cropRect);
+ navBuilder.withMatrix(taskViewSimulator.getCurrentMatrix())
+ .withWindowCrop(cropRect)
+ .withAlpha(mNavFadeIn.value);
+ } else {
+ navBuilder.withAlpha(mNavFadeOut.value);
+ }
+ handle.getTransformParams().applySurfaceParams(navBuilder.build());
}
- finalParams.applySurfaceParams(navBuilder.build());
}
});
} else if (inLiveTileMode) {
@@ -272,14 +279,16 @@ public final class TaskViewUtils {
controller.animateNavigationBarToApp(RECENTS_LAUNCH_DURATION);
}
}
- topMostSimulator = tsv;
+ topMostSimulators = remoteTargetHandles;
}
- if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
+ if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulators.length > 0) {
out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
- TaskViewSimulator simulatorToCopy = topMostSimulator;
- simulatorToCopy.apply(params);
+ RemoteTargetHandle[] simulatorCopies = topMostSimulators;
+ for (RemoteTargetHandle handle : simulatorCopies) {
+ handle.getTaskViewSimulator().apply(handle.getTransformParams());
+ }
// Mt represents the overall transformation on the thumbnailView relative to the
// Launcher's rootView
@@ -293,36 +302,49 @@ public final class TaskViewUtils {
// During animation we apply transformation on the thumbnailView (and not the rootView)
// to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
// Mt K(0)` K(t) Mt`
- TaskThumbnailView ttv = v.getThumbnail();
- RectF tvBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight());
- float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()};
- getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
- RectF tvBoundsInRoot = new RectF(
- tvBoundsMapped[0], tvBoundsMapped[1],
- tvBoundsMapped[2], tvBoundsMapped[3]);
+ TaskThumbnailView[] thumbnails = v.getThumbnails();
+ Matrix[] mt = new Matrix[simulatorCopies.length];
+ Matrix[] mti = new Matrix[simulatorCopies.length];
+ for (int i = 0; i < thumbnails.length; i++) {
+ TaskThumbnailView ttv = thumbnails[i];
+ RectF localBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight());
+ float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()};
+ getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
+ RectF localBoundsInRoot = new RectF(
+ tvBoundsMapped[0], tvBoundsMapped[1],
+ tvBoundsMapped[2], tvBoundsMapped[3]);
+ Matrix localMt = new Matrix();
+ localMt.setRectToRect(localBounds, localBoundsInRoot, ScaleToFit.FILL);
+ mt[i] = localMt;
- Matrix mt = new Matrix();
- mt.setRectToRect(tvBounds, tvBoundsInRoot, ScaleToFit.FILL);
-
- Matrix mti = new Matrix();
- mt.invert(mti);
-
- Matrix k0i = new Matrix();
- simulatorToCopy.getCurrentMatrix().invert(k0i);
+ Matrix localMti = new Matrix();
+ localMt.invert(localMti);
+ mti[i] = localMti;
+ }
+ Matrix[] k0i = new Matrix[simulatorCopies.length];
+ for (int i = 0; i < simulatorCopies.length; i++) {
+ k0i[i] = new Matrix();
+ simulatorCopies[i].getTaskViewSimulator().getCurrentMatrix().invert(k0i[i]);
+ }
Matrix animationMatrix = new Matrix();
out.addOnFrameCallback(() -> {
- animationMatrix.set(mt);
- animationMatrix.postConcat(k0i);
- animationMatrix.postConcat(simulatorToCopy.getCurrentMatrix());
- animationMatrix.postConcat(mti);
- ttv.setAnimationMatrix(animationMatrix);
+ for (int i = 0; i < simulatorCopies.length; i++) {
+ animationMatrix.set(mt[i]);
+ animationMatrix.postConcat(k0i[i]);
+ animationMatrix.postConcat(simulatorCopies[i]
+ .getTaskViewSimulator().getCurrentMatrix());
+ animationMatrix.postConcat(mti[i]);
+ thumbnails[i].setAnimationMatrix(animationMatrix);
+ }
});
out.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- ttv.setAnimationMatrix(null);
+ for (TaskThumbnailView ttv : thumbnails) {
+ ttv.setAnimationMatrix(null);
+ }
}
});
}
@@ -365,8 +387,8 @@ public final class TaskViewUtils {
* device is considered in multiWindowMode and things like insets and stuff change
* and calculations have to be adjusted in the animations for that
*/
- public static void composeRecentsSplitLaunchAnimator(@NonNull TaskView initialView,
- @NonNull TaskView v, @NonNull TransitionInfo transitionInfo,
+ public static void composeRecentsSplitLaunchAnimator(@NonNull Task initalTask,
+ @NonNull Task secondTask, @NonNull TransitionInfo transitionInfo,
SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
final TransitionInfo.Change[] splitRoots = new TransitionInfo.Change[2];
@@ -376,7 +398,7 @@ public final class TaskViewUtils {
final int mode = change.getMode();
// Find the target tasks' root tasks since those are the split stages that need to
// be animated (the tasks themselves are children and thus inherit animation).
- if (taskId == initialView.getTask().key.id || taskId == v.getTask().key.id) {
+ if (taskId == initalTask.key.id || taskId == secondTask.key.id) {
if (!(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
throw new IllegalStateException(
"Expected task to be showing, but it is " + mode);
@@ -385,7 +407,7 @@ public final class TaskViewUtils {
throw new IllegalStateException("Initiating multi-split launch but the split"
+ "root of " + taskId + " is already visible or has broken hierarchy.");
}
- splitRoots[taskId == initialView.getTask().key.id ? 0 : 1] =
+ splitRoots[taskId == initalTask.key.id ? 0 : 1] =
transitionInfo.getChange(change.getParent());
}
}
@@ -404,78 +426,102 @@ public final class TaskViewUtils {
finishCallback.run();
}
- /** Legacy version (until shell transitions are enabled) */
- public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull AnimatorSet anim,
- @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets,
+ /**
+ * Legacy version (until shell transitions are enabled)
+ *
+ * If {@param launchingTaskView} is not null, then this will play the tasks launch animation
+ * from the position of the GroupedTaskView (when user taps on the TaskView to start it).
+ * Technically this case should be taken care of by
+ * {@link #composeRecentsSplitLaunchAnimatorLegacy()} below, but the way we launch tasks whether
+ * it's a single task or multiple tasks results in different entry-points.
+ *
+ * If it is null, then it will simply fade in the starting apps and fade out launcher (for the
+ * case where launcher handles animating starting split tasks from app icon) */
+ public static void composeRecentsSplitLaunchAnimatorLegacy(
+ @Nullable GroupedTaskView launchingTaskView,
+ @NonNull Task initialTask,
+ @NonNull Task secondTask, @NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
- @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing,
- @NonNull StateManager stateManager, @NonNull DepthController depthController,
- int targetStage) {
- PendingAnimation out = new PendingAnimation(RECENTS_LAUNCH_DURATION);
- boolean isRunningTask = v.isRunningTask();
- TransformParams params = null;
- TaskViewSimulator tvs = null;
- RecentsView recentsView = v.getRecentsView();
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
- params = recentsView.getLiveTileParams();
- tvs = recentsView.getLiveTileTaskViewSimulator();
+ @NonNull RemoteAnimationTargetCompat[] nonAppTargets,
+ @NonNull StateManager stateManager,
+ @Nullable DepthController depthController,
+ @NonNull Runnable finishCallback) {
+ if (launchingTaskView != null) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ RecentsView recentsView = launchingTaskView.getRecentsView();
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ finishCallback.run();
+ }
+ });
+ composeRecentsLaunchAnimator(animatorSet, launchingTaskView,
+ appTargets, wallpaperTargets, nonAppTargets,
+ true, stateManager,
+ recentsView, depthController);
+ animatorSet.start();
+ return;
}
- boolean inLiveTileMode =
- ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1;
- final RemoteAnimationTargets targets =
- new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
- inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
+ final ArrayList openingTargets = new ArrayList<>();
+ final ArrayList closingTargets = new ArrayList<>();
+ for (RemoteAnimationTargetCompat appTarget : appTargets) {
+ final int taskId = appTarget.taskInfo != null ? appTarget.taskInfo.taskId : -1;
+ final int mode = appTarget.mode;
+ final SurfaceControl leash = appTarget.leash.getSurfaceControl();
+ if (leash == null) {
+ continue;
+ }
- if (params == null) {
- SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
- targets.addReleaseCheck(applier);
-
- params = new TransformParams()
- .setSyncTransactionApplier(applier)
- .setTargetSet(targets);
+ if (mode == MODE_OPENING) {
+ openingTargets.add(leash);
+ } else if (taskId == initialTask.key.id || taskId == secondTask.key.id) {
+ throw new IllegalStateException("Expected task to be opening, but it is " + mode);
+ } else if (mode == MODE_CLOSING) {
+ closingTargets.add(leash);
+ }
}
- Rect crop = new Rect();
- Context context = v.getContext();
- DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
- if (tvs == null && targets.apps.length > 0) {
- tvs = new TaskViewSimulator(recentsView.getContext(), recentsView.getSizeStrategy());
- tvs.setDp(dp);
-
- // RecentsView never updates the display rotation until swipe-up so the value may
- // be stale. Use the display value instead.
- int displayRotation = DisplayController.INSTANCE.get(recentsView.getContext())
- .getInfo().rotation;
- tvs.getOrientationState().update(displayRotation, displayRotation);
-
- tvs.setPreview(targets.apps[targets.apps.length - 1]);
- tvs.fullScreenProgress.value = 0;
- tvs.recentsViewScale.value = 1;
-// tvs.setScroll(startScroll);
-
- // Fade in the task during the initial 20% of the animation
- out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
- clampToProgress(LINEAR, 0, 0.2f));
+ for (int i = 0; i < nonAppTargets.length; ++i) {
+ final SurfaceControl leash = appTargets[i].leash.getSurfaceControl();
+ if (nonAppTargets[i].windowType == TYPE_DOCK_DIVIDER && leash != null) {
+ openingTargets.add(leash);
+ }
}
- TaskViewSimulator topMostSimulator = null;
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(SPLIT_LAUNCH_DURATION);
+ animator.addUpdateListener(valueAnimator -> {
+ float progress = valueAnimator.getAnimatedFraction();
+ for (SurfaceControl leash: openingTargets) {
+ t.setAlpha(leash, progress);
+ }
+ for (SurfaceControl leash: closingTargets) {
+ t.setAlpha(leash, 1 - progress);
+ }
+ t.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ for (SurfaceControl leash: openingTargets) {
+ t.show(leash).setAlpha(leash, 0.0f);
+ }
+ t.apply();
+ }
- if (tvs != null) {
- out.setFloat(tvs.fullScreenProgress,
- AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
- out.setFloat(tvs.recentsViewScale,
- AnimatedFloat.VALUE, tvs.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
- out.setFloat(tvs.recentsViewScroll,
- AnimatedFloat.VALUE, 0, TOUCH_RESPONSE_INTERPOLATOR);
-
- TaskViewSimulator finalTsv = tvs;
- TransformParams finalParams = params;
- out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
- topMostSimulator = tvs;
- }
-
- anim.play(out.buildAnim());
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (SurfaceControl leash: closingTargets) {
+ t.hide(leash);
+ }
+ super.onAnimationEnd(animation);
+ finishCallback.run();
+ }
+ });
+ animator.start();
}
public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@@ -483,13 +529,18 @@ public final class TaskViewUtils {
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
@NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing,
@NonNull StateManager stateManager, @NonNull RecentsView recentsView,
- @NonNull DepthController depthController) {
+ @Nullable DepthController depthController) {
boolean skipLauncherChanges = !launcherClosing;
TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
nonAppTargets, depthController, pa);
+ if (launcherClosing) {
+ // TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app"
+ TaskViewUtils.setSplitAuxiliarySurfacesShown(nonAppTargets,
+ true /*shown*/, true /*animate*/, pa);
+ }
Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
@@ -498,7 +549,7 @@ public final class TaskViewUtils {
if (launcherClosing) {
Context context = v.getContext();
DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
- launcherAnim = dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()
+ launcherAnim = dp.overviewShowAsGrid
? ObjectAnimator.ofFloat(recentsView, RecentsView.CONTENT_ALPHA, 0)
: recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
@@ -542,4 +593,90 @@ public final class TaskViewUtils {
stateManager.setCurrentAnimation(anim, childStateAnimation);
anim.addListener(windowAnimEndListener);
}
+
+ public static void setSplitAuxiliarySurfacesShown(RemoteAnimationTargetCompat[] nonApps,
+ boolean shown, boolean animate) {
+ setSplitAuxiliarySurfacesShown(nonApps, shown, animate,null);
+ }
+
+ private static void setSplitAuxiliarySurfacesShown(
+ @NonNull RemoteAnimationTargetCompat[] nonApps, boolean shown, boolean animate,
+ @Nullable PendingAnimation splitLaunchAnimation) {
+ if (nonApps == null || nonApps.length == 0) {
+ return;
+ }
+
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ List auxiliarySurfaces = new ArrayList<>(nonApps.length);
+ boolean hasSurfaceToAnimate = false;
+ for (int i = 0; i < nonApps.length; ++i) {
+ final RemoteAnimationTargetCompat targ = nonApps[i];
+ final SurfaceControl leash = targ.leash.getSurfaceControl();
+ if (targ.windowType == TYPE_DOCK_DIVIDER && leash != null) {
+ auxiliarySurfaces.add(leash);
+ hasSurfaceToAnimate = true;
+ }
+ }
+ if (!hasSurfaceToAnimate) {
+ return;
+ }
+
+ if (!animate) {
+ for (SurfaceControl leash : auxiliarySurfaces) {
+ t.setAlpha(leash, shown ? 1 : 0);
+ if (shown) {
+ t.show(leash);
+ } else {
+ t.hide(leash);
+ }
+ }
+ t.apply();
+ return;
+ }
+
+ ValueAnimator dockFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
+ dockFadeAnimator.addUpdateListener(valueAnimator -> {
+ float progress = valueAnimator.getAnimatedFraction();
+ for (SurfaceControl leash : auxiliarySurfaces) {
+ t.setAlpha(leash, shown ? progress : 1 - progress);
+ }
+ t.apply();
+ });
+ dockFadeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (shown) {
+ for (SurfaceControl leash : auxiliarySurfaces) {
+ t.setAlpha(leash, 0);
+ t.show(leash);
+ }
+ t.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (!shown) {
+ for (SurfaceControl leash : auxiliarySurfaces) {
+ t.hide(leash);
+ }
+ t.apply();
+ }
+ t.close();
+ }
+ });
+ dockFadeAnimator.setDuration(SPLIT_DIVIDER_ANIM_DURATION);
+ if (splitLaunchAnimation != null) {
+ // If split apps are launching, we want to delay showing the divider bar until the very
+ // end once the apps are mostly in place. This is because we aren't moving the divider
+ // leash in the relative position with the launching apps.
+ dockFadeAnimator.setStartDelay(
+ splitLaunchAnimation.getDuration() - SPLIT_DIVIDER_ANIM_DURATION);
+ splitLaunchAnimation.add(dockFadeAnimator);
+ } else {
+ dockFadeAnimator.start();
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 3e30a941c2..ec4edc2f9b 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -26,6 +26,7 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
@@ -33,6 +34,7 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHE
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
import android.annotation.TargetApi;
@@ -41,7 +43,6 @@ import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.Service;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
@@ -68,7 +69,6 @@ import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
@@ -91,18 +91,19 @@ import com.android.quickstep.inputconsumers.AssistantInputConsumer;
import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
-import com.android.quickstep.inputconsumers.OverscrollInputConsumer;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
+import com.android.quickstep.inputconsumers.ProgressDelegateInputConsumer;
import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
+import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistantUtilities;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.ProtoTracer;
+import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.SplitScreenBounds;
-import com.android.systemui.plugins.OverscrollPlugin;
-import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -114,6 +115,7 @@ import com.android.systemui.shared.system.smartspace.ISmartspaceTransitionContro
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
+import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.startingsurface.IStartingWindow;
import com.android.wm.shell.transition.IShellTransitions;
@@ -122,13 +124,14 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.LinkedList;
+import java.util.function.Function;
/**
* Service connected by system-UI for handling touch interaction.
*/
@TargetApi(Build.VERSION_CODES.R)
-public class TouchInteractionService extends Service implements PluginListener,
- ProtoTraceable {
+public class TouchInteractionService extends Service
+ implements ProtoTraceable {
private static final String TAG = "TouchInteractionService";
@@ -147,8 +150,8 @@ public class TouchInteractionService extends Service implements PluginListener {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
- splitscreen, onehanded, shellTransitions, startingWindow,
+ splitscreen, onehanded, shellTransitions, startingWindow, recentTasks,
smartspaceTransitionController);
TouchInteractionService.this.initInputMonitor();
preloadOverview(true /* fromInit */);
- mDeviceState.runOnUserUnlocked(() -> {
- final BaseActivityInterface ai =
- mOverviewComponentObserver.getActivityInterface();
- if (ai == null) return;
- ai.onOverviewServiceBound();
- });
});
sIsInitialized = true;
}
@@ -263,16 +262,47 @@ public class TouchInteractionService extends Service implements PluginListener SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
}
+ @BinderThread
@Override
- public void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
- int backDisposition, boolean showImeSwitcher) {
- MAIN_EXECUTOR.execute(() -> mTaskbarManager.updateImeStatus(
- displayId, vis, backDisposition, showImeSwitcher));
+ public void onScreenTurnedOn() {
+ MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn);
+ }
+
+ @Override
+ public void onRotationProposal(int rotation, boolean isValid) {
+ executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid));
+ }
+
+ @Override
+ public void disable(int displayId, int state1, int state2, boolean animate) {
+ executeForTaskbarManager(() -> mTaskbarManager
+ .disableNavBarElements(displayId, state1, state2, animate));
+ }
+
+ @Override
+ public void onSystemBarAttributesChanged(int displayId, int behavior) {
+ executeForTaskbarManager(() -> mTaskbarManager
+ .onSystemBarAttributesChanged(displayId, behavior));
+ }
+
+ @Override
+ public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
+ executeForTaskbarManager(() -> mTaskbarManager
+ .onNavButtonsDarkIntensityChanged(darkIntensity));
+ }
+
+ private void executeForTaskbarManager(final Runnable r) {
+ MAIN_EXECUTOR.execute(() -> {
+ if (mTaskbarManager == null) {
+ return;
+ }
+ r.run();
+ });
}
public TaskbarManager getTaskbarManager() {
@@ -282,6 +312,13 @@ public class TouchInteractionService extends Service implements PluginListener proxy) {
+ mSwipeUpProxyProvider = proxy != null ? proxy : (i -> null);
+ }
}
private static boolean sConnected = false;
@@ -292,7 +329,6 @@ public class TouchInteractionService extends Service implements PluginListener mSwipeUpProxyProvider = i -> null;
@Override
public void onCreate() {
@@ -332,13 +369,16 @@ public class TouchInteractionService extends Service implements PluginListener mAM.getRunningTask(false /* filterOnlyVisibleRecents */)));
+ gestureState.updateRunningTasks(TraceHelper.allowIpcs("getRunningTask.0",
+ () -> mAM.getRunningTasks(false /* filterOnlyVisibleRecents */)));
}
return gestureState;
}
private InputConsumer newConsumer(GestureState previousGestureState,
GestureState newGestureState, MotionEvent event) {
+ AnimatedFloat progressProxy = mSwipeUpProxyProvider.apply(mGestureState);
+ if (progressProxy != null) {
+ return new ProgressDelegateInputConsumer(this, mTaskAnimationManager,
+ mGestureState, mInputMonitorCompat, progressProxy);
+ }
+
boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
if (!mDeviceState.isUserUnlocked()) {
@@ -668,24 +726,12 @@ public class TouchInteractionService extends Service implements PluginListener newList = new ArrayList<>(tasks.size() + 1);
- newList.addAll(tasks);
- newList.add(Task.from(new TaskKey(mHomeTaskInfo), mHomeTaskInfo, false));
- tasks = newList;
+ ArrayList