Merge "Revert "Temporarily disabling events checking in git_master""
diff --git a/Android.mk b/Android.mk
index 9cfcf17..7805b32 100644
--- a/Android.mk
+++ b/Android.mk
@@ -67,7 +67,10 @@
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, src_shortcuts_overrides) \
- $(call all-java-files-under, src_ui_overrides)
+ $(call all-java-files-under, src_ui_overrides) \
+ $(call all-java-files-under, ext_tests/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/ext_tests/res
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
# Proguard is disable for testing. Derivarive prjects to keep proguard enabled
@@ -129,6 +132,7 @@
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := \
+ SystemUI-statsd \
SystemUISharedLib \
launcherprotosnano \
launcher_log_protos_lite
@@ -201,6 +205,7 @@
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := \
+ SystemUI-statsd \
SystemUISharedLib \
launcherprotosnano \
launcher_log_protos_lite
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index d7191b4..84dd06a 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -86,6 +86,7 @@
<receiver
android:name="com.android.launcher3.InstallShortcutReceiver"
android:permission="com.android.launcher.permission.INSTALL_SHORTCUT"
+ android:exported="true"
android:enabled="@bool/enable_install_shortcut_api" >
<intent-filter>
<action android:name="com.android.launcher.action.INSTALL_SHORTCUT" />
@@ -94,14 +95,16 @@
<!-- Intent received when a session is committed -->
<receiver
- android:name="com.android.launcher3.SessionCommitReceiver" >
+ android:name="com.android.launcher3.SessionCommitReceiver"
+ android:exported="true">
<intent-filter>
<action android:name="android.content.pm.action.SESSION_COMMITTED" />
</intent-filter>
</receiver>
<!-- Intent received used to initialize a restored widget -->
- <receiver android:name="com.android.launcher3.AppWidgetsRestoredReceiver" >
+ <receiver android:name="com.android.launcher3.AppWidgetsRestoredReceiver"
+ android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED"/>
</intent-filter>
@@ -117,6 +120,7 @@
android:name="com.android.launcher3.notification.NotificationListener"
android:label="@string/notification_dots_service_title"
android:enabled="@bool/notification_dots_enabled"
+ android:exported="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
@@ -130,6 +134,7 @@
android:theme="@style/AppItemActivityTheme"
android:excludeFromRecents="true"
android:autoRemoveFromRecents="true"
+ android:exported="true"
android:label="@string/action_add_to_workspace" >
<intent-filter>
<action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
@@ -156,7 +161,6 @@
<provider
android:name="com.android.launcher3.graphics.GridOptionsProvider"
android:authorities="${packageName}.grid_control"
- android:enabled="false"
android:exported="true" />
<!--
@@ -166,6 +170,7 @@
android:name="com.android.launcher3.settings.SettingsActivity"
android:label="@string/settings_button_text"
android:theme="@style/HomeSettingsTheme"
+ android:exported="true"
android:autoRemoveFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
@@ -188,6 +193,7 @@
android:name="com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher"
android:theme="@style/AppTheme"
android:launchMode="singleTop"
+ android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b031ffb..8e01f82 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -53,6 +53,7 @@
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
+ android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/ext_tests/res/values/overrides.xml b/ext_tests/res/values/overrides.xml
new file mode 100644
index 0000000..3f071d4
--- /dev/null
+++ b/ext_tests/res/values/overrides.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="test_information_handler_class" translatable="false">com.android.launcher3.testing.DebugTestInformationHandler</string>
+</resources>
+
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
new file mode 100644
index 0000000..e649ce1
--- /dev/null
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -0,0 +1,191 @@
+/*
+ * 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.testing;
+
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Debug;
+import android.system.Os;
+import android.view.View;
+
+import androidx.annotation.Keep;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class to handle requests from tests, including debug ones.
+ */
+public class DebugTestInformationHandler extends TestInformationHandler {
+ private static LinkedList sLeaks;
+ private static Collection<String> sEvents;
+
+ public DebugTestInformationHandler(Context context) {
+ init(context);
+ }
+
+ private static void runGcAndFinalizersSync() {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+
+ final CountDownLatch fence = new CountDownLatch(1);
+ createFinalizationObserver(fence);
+ try {
+ do {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ } while (!fence.await(100, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ // Create the observer in the scope of a method to minimize the chance that
+ // it remains live in a DEX/machine register at the point of the fence guard.
+ // This must be kept to avoid R8 inlining it.
+ @Keep
+ private static void createFinalizationObserver(CountDownLatch fence) {
+ new Object() {
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ fence.countDown();
+ } finally {
+ super.finalize();
+ }
+ }
+ };
+ }
+
+ @Override
+ public Bundle call(String method) {
+ final Bundle response = new Bundle();
+ switch (method) {
+ case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
+ return getLauncherUIProperty(Bundle::putInt,
+ l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
+ }
+
+ case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
+ TestProtocol.sDebugTracing = true;
+ return response;
+
+ case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
+ TestProtocol.sDebugTracing = false;
+ return response;
+
+ case TestProtocol.REQUEST_PID: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
+ return response;
+ }
+
+ case TestProtocol.REQUEST_TOTAL_PSS_KB: {
+ runGcAndFinalizersSync();
+ Debug.MemoryInfo mem = new Debug.MemoryInfo();
+ Debug.getMemoryInfo(mem);
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss());
+ return response;
+ }
+
+ case TestProtocol.REQUEST_JAVA_LEAK: {
+ if (sLeaks == null) sLeaks = new LinkedList();
+
+ // Allocate and dirty the memory.
+ final int leakSize = 1024 * 1024;
+ final byte[] bytes = new byte[leakSize];
+ for (int i = 0; i < leakSize; i += 239) {
+ bytes[i] = (byte) (i % 256);
+ }
+ sLeaks.add(bytes);
+ return response;
+ }
+
+ case TestProtocol.REQUEST_NATIVE_LEAK: {
+ if (sLeaks == null) sLeaks = new LinkedList();
+
+ // Allocate and dirty a bitmap.
+ final Bitmap bitmap = Bitmap.createBitmap(512, 512, ARGB_8888);
+ bitmap.eraseColor(Color.RED);
+ sLeaks.add(bitmap);
+ return response;
+ }
+
+ case TestProtocol.REQUEST_VIEW_LEAK: {
+ if (sLeaks == null) sLeaks = new LinkedList();
+ sLeaks.add(new View(mContext));
+ return response;
+ }
+
+ case TestProtocol.REQUEST_START_EVENT_LOGGING: {
+ sEvents = new ArrayList<>();
+ TestLogging.setEventConsumer(
+ (sequence, event) -> {
+ final Collection<String> events = sEvents;
+ if (events != null) {
+ synchronized (events) {
+ events.add(sequence + '/' + event);
+ }
+ }
+ });
+ return response;
+ }
+
+ case TestProtocol.REQUEST_STOP_EVENT_LOGGING: {
+ TestLogging.setEventConsumer(null);
+ sEvents = null;
+ return response;
+ }
+
+ case TestProtocol.REQUEST_GET_TEST_EVENTS: {
+ synchronized (sEvents) {
+ response.putStringArrayList(
+ TestProtocol.TEST_INFO_RESPONSE_FIELD, new ArrayList<>(sEvents));
+ }
+ return response;
+ }
+
+ case TestProtocol.REQUEST_CLEAR_DATA: {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ LauncherSettings.Settings.call(mContext.getContentResolver(),
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+ MAIN_EXECUTOR.submit(() ->
+ LauncherAppState.getInstance(mContext).getModel().forceReload());
+ return response;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ default:
+ return super.call(method);
+ }
+ }
+}
diff --git a/go/res/values-v26/bools.xml b/go/res/values-v26/bools.xml
index cc4a7ba..1584734 100644
--- a/go/res/values-v26/bools.xml
+++ b/go/res/values-v26/bools.xml
@@ -17,5 +17,6 @@
-->
<resources>
- <bool name="notification_badging_enabled">false</bool>
-</resources>
\ No newline at end of file
+ <bool name="notification_dots_enabled">false</bool>
+</resources>
+
diff --git a/go/res/xml/device_profiles.xml b/go/res/xml/device_profiles.xml
index 0fd0eeb..0c7eba3 100644
--- a/go/res/xml/device_profiles.xml
+++ b/go/res/xml/device_profiles.xml
@@ -24,6 +24,7 @@
launcher:numFolderRows="4"
launcher:numFolderColumns="4"
launcher:numHotseatIcons="4"
+ launcher:dbFile="launcher.db"
launcher:defaultLayoutId="@xml/default_workspace_4x4" >
<display-option
@@ -36,4 +37,4 @@
</grid-option>
-</profiles>
\ No newline at end of file
+</profiles>
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index a89fe5c..5611969 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -23,9 +23,10 @@
message ItemInfo {
oneof Item {
Application application = 1;
- Task task= 2;
+ Task task = 2;
Shortcut shortcut = 3;
Widget widget = 4;
+ FolderIcon folder_icon = 9;
}
// When used for launch event, stores the global predictive rank
optional int32 rank = 5;
@@ -34,16 +35,67 @@
optional bool is_work = 6;
// Item can be child node to parent container or parent containers (nested)
- oneof Container {
- WorkspaceContainer workspace = 7;
- HotseatContainer hotseat = 8;
- FolderContainer folder = 9;
- }
+ optional ContainerInfo container_info = 7;
+
// Stores the origin of the Item
- optional Origin source = 10;
+ optional Attribute attribute = 8;
}
-enum Origin {
+// Represents various launcher surface where items are placed.
+message ContainerInfo {
+ oneof Container {
+ WorkspaceContainer workspace = 1;
+ HotseatContainer hotseat = 2;
+ FolderContainer folder = 3;
+ AllAppsContainer all_apps_container = 4;
+ WidgetsContainer widgets_container = 5;
+ PredictionContainer prediction_container = 6;
+ SearchResultContainer search_result_container = 7;
+ ShortcutsContainer shortcuts_container = 8;
+ SettingsContainer settings_container = 9;
+ PredictedHotseatContainer predicted_hotseat_container = 10;
+ TaskSwitcherContainer task_switcher_container = 11;
+ }
+}
+
+// Represents the apps list sorted alphabetically inside the all-apps view.
+message AllAppsContainer {
+}
+
+message WidgetsContainer {
+}
+
+// Represents the predicted apps row(top row) in the all-apps view.
+message PredictionContainer {
+}
+
+// Represents the apps container within search results.
+message SearchResultContainer {
+
+ // Length of search term.
+ optional int32 query_length = 1;
+
+ // Container from where search was invoked.
+ oneof ParentContainer {
+ WorkspaceContainer workspace = 2;
+ AllAppsContainer all_apps_container = 3;
+ }
+}
+
+// Container for package specific shortcuts to deep links and notifications.
+// Typically shown as popup window by longpressing on an icon.
+message ShortcutsContainer {
+}
+
+// Container for generic system shortcuts for launcher specific settings.
+// Typically shown up as popup window by longpressing on empty space on workspace.
+message SettingsContainer {
+}
+
+message TaskSwitcherContainer {
+}
+
+enum Attribute {
UNKNOWN = 0;
DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat
BACKUP_RESTORE = 2; // icon layout restored from backup
@@ -53,6 +105,21 @@
ADD_TO_HOMESCREEN = 6; // play install + launcher home setting
ALLAPPS_PREDICTION = 7; // from prediction bar in all apps container
HOTSEAT_PREDICTION = 8; // from prediction bar in hotseat container
+
+ // Folder's label is one of the non-empty suggested values.
+ SUGGESTED_LABEL = 9;
+
+ // Folder's label is non-empty, manually entered by the user
+ // and different from any of suggested values.
+ MANUAL_LABEL = 10;
+
+ // Folder's label is not yet assigned( i.e., title == null).
+ // Eligible for auto-labeling.
+ UNLABELED = 11;
+
+ // Folder's label is empty(i.e., title == "").
+ // Not eligible for auto-labeling.
+ EMPTY_LABEL = 12;
}
// Main app icons
@@ -68,8 +135,8 @@
// AppWidgets handled by AppWidgetManager
message Widget {
- optional int32 span_x = 1;
- optional int32 span_y = 2;
+ optional int32 span_x = 1 [default = 1];
+ optional int32 span_y = 2 [default = 1];
optional int32 app_widget_id = 3;
optional string package_name = 4; // only populated during snapshot if from workspace
optional string component_name = 5; // only populated during snapshot if from workspace
@@ -82,27 +149,124 @@
optional int32 index = 3;
}
+// Represents folder in a closed state.
+message FolderIcon {
+ // Number of items inside folder.
+ optional int32 cardinality = 1;
+
+ // State of the folder label before the event.
+ optional FromState from_label_state = 2;
+
+ // State of the folder label after the event.
+ optional ToState to_label_state = 3;
+
+ // Details about actual folder label.
+ // Populated when folder label is not a PII.
+ optional string label_info = 4;
+}
+
//////////////////////////////////////////////
// Containers
message WorkspaceContainer {
- optional int32 page_index = 1; // range [-1, l], 0 is the index of the main homescreen
- optional int32 grid_x = 2; // [0, m], m varies based on the display density and resolution
- optional int32 grid_y = 3; // [0, n], n varies based on the display density and resolution
+ optional int32 page_index = 1 [default = -2]; // range [-1, l], 0 is the index of the main homescreen
+ optional int32 grid_x = 2 [default = -1]; // [0, m], m varies based on the display density and resolution
+ optional int32 grid_y = 3 [default = -1]; // [0, n], n varies based on the display density and resolution
}
message HotseatContainer {
optional int32 index = 1;
}
+// Represents hotseat container with prediction feature enabled.
+message PredictedHotseatContainer {
+ optional int32 index = 1;
+
+ // No of hotseat positions filled with predicted items.
+ optional int32 cardinality = 2;
+}
+
message FolderContainer {
- optional int32 page_index = 1;
- optional int32 grid_x = 2;
- optional int32 grid_y = 3;
- oneof Container {
+ optional int32 page_index = 1 [default = -1];
+ optional int32 grid_x = 2 [default = -1];
+ optional int32 grid_y = 3 [default = -1];
+ oneof ParentContainer {
WorkspaceContainer workspace = 4;
HotseatContainer hotseat = 5;
}
}
+// Represents state of EditText field before update.
+enum FromState {
+ // Default value.
+ // Used when a FromState is not applicable, for example, during folder creation.
+ FROM_STATE_UNSPECIFIED = 0;
+ // EditText was empty.
+ // Eg: When a folder label is updated from empty string.
+ FROM_EMPTY = 1;
+
+ // EditText was non-empty and manually entered by the user.
+ // Eg: When a folder label is updated from a user-entered value.
+ FROM_CUSTOM = 2;
+
+ // EditText was non-empty and one of the suggestions.
+ // Eg: When a folder label is updated from a suggested value.
+ FROM_SUGGESTED = 3;
+}
+
+// Represents state of EditText field after update.
+enum ToState {
+ // Default value.
+ // Used when ToState is not applicable, for example, when folder label is updated to a different
+ // value when folder label suggestion feature is disabled.
+ TO_STATE_UNSPECIFIED = 0;
+
+ // User attempted to change the EditText, but was not changed.
+ UNCHANGED = 1;
+
+ // New label matches with primary(aka top) suggestion.
+ TO_SUGGESTION0 = 2;
+
+ // New value matches with second top suggestion even though the top suggestion was non-empty.
+ TO_SUGGESTION1_WITH_VALID_PRIMARY = 3;
+
+ // New value matches with second top suggestion given that top suggestion was empty.
+ TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 4;
+
+ // New value matches with third top suggestion even though the top suggestion was non-empty.
+ TO_SUGGESTION2_WITH_VALID_PRIMARY = 5;
+
+ // New value matches with third top suggestion given that top suggestion was empty.
+ TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 6;
+
+ // New value matches with 4th top suggestion even though the top suggestion was non-empty.
+ TO_SUGGESTION3_WITH_VALID_PRIMARY = 7;
+
+ // New value matches with 4th top suggestion given that top suggestion was empty.
+ TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 8;
+
+ // New value is empty even though the top suggestion was non-empty.
+ TO_EMPTY_WITH_VALID_PRIMARY = 9;
+
+ // New value is empty given that top suggestion was empty.
+ TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 10;
+
+ // New value is empty given that no suggestions were provided.
+ TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 11;
+
+ // New value is empty given that suggestions feature was disabled.
+ TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 12;
+
+ // New value is non-empty and does not match with any of the suggestions even though the top suggestion was non-empty.
+ TO_CUSTOM_WITH_VALID_PRIMARY = 13;
+
+ // New value is non-empty and not match with any suggestions given that top suggestion was empty.
+ TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 14;
+
+ // New value is non-empty and also no suggestions were provided.
+ TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 15;
+
+ // New value is non-empty and also suggestions feature was disable.
+ TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 16;
+}
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 0fe5310..9423cb2 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -63,14 +63,14 @@
// Note: proto does not support duplicate enum values, even if they belong to different enum type.
// Hence "FROM" and "TO" prefix added.
- enum FromFolderLabelState{
+ enum FromFolderLabelState {
FROM_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
FROM_EMPTY = 1;
FROM_CUSTOM = 2;
FROM_SUGGESTED = 3;
}
- enum ToFolderLabelState{
+ enum ToFolderLabelState {
TO_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
TO_SUGGESTION0_WITH_VALID_PRIMARY = 1;
TO_SUGGESTION1_WITH_VALID_PRIMARY = 2;
@@ -79,10 +79,14 @@
TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 5;
TO_SUGGESTION3_WITH_VALID_PRIMARY = 6;
TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 7;
- TO_EMPTY_WITH_VALID_SUGGESTIONS = 8;
+ TO_EMPTY_WITH_VALID_SUGGESTIONS = 8 [deprecated = true];
+ TO_EMPTY_WITH_VALID_PRIMARY = 15;
+ TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 16;
TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 9;
TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 10;
- TO_CUSTOM_WITH_VALID_SUGGESTIONS = 11;
+ TO_CUSTOM_WITH_VALID_SUGGESTIONS = 11 [deprecated = true];
+ TO_CUSTOM_WITH_VALID_PRIMARY = 17;
+ TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 18;
TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 12;
TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 13;
UNCHANGED = 14;
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index 527bfc3..1a634ce 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -52,8 +52,9 @@
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
- android:taskAffinity="${packageName}.launcher"
- android:enabled="true">
+ android:taskAffinity=""
+ android:enabled="true"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 04506b5..868c659 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -17,98 +17,104 @@
** limitations under the License.
*/
-->
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.android.launcher3" >
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.launcher3">
+
+ <permission
+ android:name="${packageName}.permission.HOTSEAT_EDU"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signatureOrSystem" />
<uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="${packageName}.permission.HOTSEAT_EDU" />
-
- <application
- android:backupAgent="com.android.launcher3.LauncherBackupAgent"
- android:fullBackupOnly="true"
- android:fullBackupContent="@xml/backupscheme"
- android:hardwareAccelerated="true"
- android:icon="@drawable/ic_launcher_home"
- android:label="@string/derived_app_name"
- android:theme="@style/AppTheme"
- android:largeHeap="@bool/config_largeHeap"
- android:restoreAnyVersion="true"
- android:supportsRtl="true" >
+ <application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
+ android:fullBackupOnly="true"
+ android:fullBackupContent="@xml/backupscheme"
+ android:hardwareAccelerated="true"
+ android:icon="@drawable/ic_launcher_home"
+ android:label="@string/derived_app_name"
+ android:theme="@style/AppTheme"
+ android:largeHeap="@bool/config_largeHeap"
+ android:restoreAnyVersion="true"
+ android:supportsRtl="true">
- <service
- android:name="com.android.quickstep.TouchInteractionService"
- android:permission="android.permission.STATUS_BAR_SERVICE"
- android:directBootAware="true" >
+ <service android:name="com.android.quickstep.TouchInteractionService"
+ android:permission="android.permission.STATUS_BAR_SERVICE"
+ android:directBootAware="true"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.QUICKSTEP_SERVICE" />
+ <action android:name="android.intent.action.QUICKSTEP_SERVICE"/>
</intent-filter>
</service>
<activity android:name="com.android.quickstep.RecentsActivity"
- android:excludeFromRecents="true"
- android:launchMode="singleTask"
- android:clearTaskOnLaunch="true"
- android:stateNotNeeded="true"
- android:theme="@style/LauncherTheme"
- android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
- android:resizeableActivity="true"
- android:resumeWhilePausing="true"
- android:taskAffinity="" />
+ android:excludeFromRecents="true"
+ android:launchMode="singleTask"
+ android:clearTaskOnLaunch="true"
+ android:stateNotNeeded="true"
+ android:theme="@style/LauncherTheme"
+ android:screenOrientation="unspecified"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:resizeableActivity="true"
+ android:resumeWhilePausing="true"
+ android:taskAffinity=""/>
<!-- Content provider to settings search. The autority should be same as the packageName -->
- <provider
- android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
- android:authorities="${packageName}"
- android:grantUriPermissions="true"
- android:multiprocess="true"
- android:permission="android.permission.READ_SEARCH_INDEXABLES"
- android:exported="true">
+ <provider android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
+ android:authorities="${packageName}"
+ android:grantUriPermissions="true"
+ android:multiprocess="true"
+ android:permission="android.permission.READ_SEARCH_INDEXABLES"
+ android:exported="true">
<intent-filter>
- <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
+ <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER"/>
</intent-filter>
</provider>
<!-- FileProvider used for sharing images. -->
- <provider
- android:name="androidx.core.content.FileProvider"
- android:authorities="${packageName}.overview.fileprovider"
- android:exported="false"
- android:grantUriPermissions="true">
- <meta-data
- android:name="android.support.FILE_PROVIDER_PATHS"
- android:resource="@xml/overview_file_provider_paths" />
+ <provider android:name="androidx.core.content.FileProvider"
+ android:authorities="${packageName}.overview.fileprovider"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/overview_file_provider_paths"/>
</provider>
- <service
- android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
- tools:node="remove" />
+ <service android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
+ tools:node="remove"/>
- <activity
- android:name="com.android.launcher3.proxy.ProxyActivityStarter"
- android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
- android:launchMode="singleTask"
- android:clearTaskOnLaunch="true"
- android:exported="false" />
-
- <activity android:name="com.android.quickstep.LockScreenRecentsActivity"
- android:theme="@android:style/Theme.NoDisplay"
- android:showOnLockScreen="true"
- android:taskAffinity="${packageName}.locktask"
- android:directBootAware="true" />
+ <activity android:name="com.android.launcher3.proxy.ProxyActivityStarter"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
+ android:launchMode="singleTask"
+ android:clearTaskOnLaunch="true"
+ android:exported="false"/>
<activity
android:name="com.android.quickstep.interaction.GestureSandboxActivity"
android:autoRemoveFromRecents="true"
android:excludeFromRecents="true"
- android:taskAffinity="${packageName}.launcher"
- android:screenOrientation="portrait">
+ android:screenOrientation="portrait"
+ android:exported="true">
<intent-filter>
- <action android:name="com.android.quickstep.action.GESTURE_SANDBOX" />
+ <action android:name="com.android.quickstep.action.GESTURE_SANDBOX"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".hybridhotseat.HotseatEduActivity"
+ android:theme="@android:style/Theme.NoDisplay"
+ android:noHistory="true"
+ android:launchMode="singleTask"
+ android:clearTaskOnLaunch="true"
+ android:permission="${packageName}.permission.HOTSEAT_EDU"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
diff --git a/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml b/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
new file mode 100644
index 0000000..df7cd8e
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid android:color="?android:colorAccent"/>
+ <size android:height="@dimen/swipe_edu_circle_size"
+ android:width="@dimen/swipe_edu_circle_size" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml b/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml
new file mode 100644
index 0000000..5a2dfb7
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <gradient
+ android:angle="90"
+ android:endColor="@android:color/transparent"
+ android:startColor="@color/chip_scrim_start_color"
+ android:type="linear" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml b/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml
new file mode 100644
index 0000000..e7ef6e6
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.quickstep.views.AllAppsEduView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="@dimen/swipe_edu_width"
+ android:layout_height="@dimen/swipe_edu_max_height"/>
diff --git a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
index ffe906c..cd64a94 100644
--- a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
+++ b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
@@ -13,24 +13,30 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.fallback.RecentsRootView
+<com.android.launcher3.LauncherRootView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
- <com.android.quickstep.fallback.FallbackRecentsView
- android:id="@id/overview_panel"
+ <com.android.quickstep.fallback.RecentsDragLayer
+ android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:outlineProvider="none"
- android:theme="@style/HomeScreenElementTheme" />
+ android:clipChildren="false">
- <include
- android:id="@+id/overview_actions_view"
- layout="@layout/overview_actions_container" />
+ <com.android.quickstep.fallback.FallbackRecentsView
+ android:id="@id/overview_panel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:outlineProvider="none"
+ android:theme="@style/HomeScreenElementTheme" />
-</com.android.quickstep.fallback.RecentsRootView>
+ <include
+ android:id="@+id/overview_actions_view"
+ layout="@layout/overview_actions_container" />
+
+ </com.android.quickstep.fallback.RecentsDragLayer>
+</com.android.launcher3.LauncherRootView>
diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
index eac0bfa..fe57e9b 100644
--- a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
+++ b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
@@ -13,12 +13,20 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.LauncherRecentsView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:accessibilityPaneTitle="@string/accessibility_recent_apps"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:theme="@style/HomeScreenElementTheme"
- android:visibility="invisible" />
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <com.android.quickstep.views.LauncherRecentsView
+ android:id="@+id/overview_panel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:accessibilityPaneTitle="@string/accessibility_recent_apps"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:theme="@style/HomeScreenElementTheme"
+ android:visibility="invisible" />
+
+ <include
+ android:id="@+id/overview_actions_view"
+ layout="@layout/overview_actions_container" />
+
+</merge>
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
index c93cad6..1dab482 100644
--- a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -24,13 +24,13 @@
<View
android:layout_width="match_parent"
android:layout_height="32dp"
- android:backgroundTint="?android:attr/colorAccent"
+ android:backgroundTint="?attr/eduHalfSheetBGColor"
android:background="@drawable/bottom_sheet_top_border" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?android:attr/colorAccent"
+ android:background="?attr/eduHalfSheetBGColor"
android:orientation="vertical">
<TextView
@@ -72,33 +72,43 @@
android:layout_height="0dp"
launcher:containerType="hotseat" />
- <FrameLayout
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/bottom_sheet_edu_padding"
android:paddingTop="8dp"
android:paddingRight="@dimen/bottom_sheet_edu_padding">
- <Button
- android:id="@+id/turn_predictions_on"
- android:layout_width="wrap_content"
+ <FrameLayout
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_gravity="end"
- android:background="?android:attr/selectableItemBackground"
- android:text="@string/hotseat_edu_accept"
- android:textAlignment="textEnd"
- android:textColor="@android:color/white" />
-
- <Button
- android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight=".4">
+ <Button
+ android:id="@+id/no_thanks"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/hotseat_edu_dismiss"
+ android:layout_gravity="start|center_vertical"
+ android:textColor="@android:color/white"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:id="@+id/no_thanks"
- android:text="@string/hotseat_edu_dismiss"
- android:layout_gravity="start"
- android:background="?android:attr/selectableItemBackground"
- android:textColor="@android:color/white" />
+ android:layout_gravity="center_vertical"
+ android:layout_weight=".6">
+ <Button
+ android:id="@+id/turn_predictions_on"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:layout_gravity="end|center_vertical"
+ android:text="@string/hotseat_edu_accept"
+ android:textColor="@android:color/white"/>
+ </FrameLayout>
- </FrameLayout>
+ </LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
index 361f5f7..f03f118 100644
--- a/quickstep/recents_ui_overrides/res/values/colors.xml
+++ b/quickstep/recents_ui_overrides/res/values/colors.xml
@@ -15,6 +15,7 @@
-->
<resources>
<color name="chip_hint_foreground_color">#fff</color>
+ <color name="chip_scrim_start_color">#39000000</color>
<color name="all_apps_label_text">#61000000</color>
<color name="all_apps_label_text_dark">#61FFFFFF</color>
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index 363840a..9266b06 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -20,6 +20,7 @@
<dimen name="chip_hint_start_padding">10dp</dimen>
<dimen name="chip_hint_end_padding">12dp</dimen>
<dimen name="chip_hint_horizontal_margin">20dp</dimen>
+ <dimen name="chip_hint_vertical_offset">16dp</dimen>
<dimen name="chip_hint_elevation">2dp</dimen>
<dimen name="chip_icon_size">16dp</dimen>
<dimen name="chip_text_height">26dp</dimen>
diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml
index ed3ba92..6aa9619 100644
--- a/quickstep/recents_ui_overrides/res/values/override.xml
+++ b/quickstep/recents_ui_overrides/res/values/override.xml
@@ -26,5 +26,7 @@
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension</string>
+
+ <string name="prediction_model_class" translatable="false">com.android.launcher3.hybridhotseat.HotseatPredictionModel</string>
</resources>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 0019ecb..38adf39 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,41 +16,25 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
-import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
-import android.util.FloatProperty;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.SpringAnimationBuilder;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -61,13 +45,6 @@
*/
public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
- public static final int INDEX_SHELF_ANIM = 0;
- public static final int INDEX_RECENTS_FADE_ANIM = 1;
- public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2;
- public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3;
-
- public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
-
public LauncherAppTransitionManagerImpl(Context context) {
super(context);
}
@@ -87,19 +64,17 @@
boolean skipLauncherChanges = !launcherClosing;
TaskView taskView = findTaskViewToLaunch(mLauncher, v, appTargets);
-
- AppWindowAnimationHelper helper =
- new AppWindowAnimationHelper(recentsView.getPagedViewOrientedState(), mLauncher);
- Animator recentsAnimator = getRecentsWindowAnimator(taskView, skipLauncherChanges,
- appTargets, wallpaperTargets, mLauncher.getDepthController(), helper);
- anim.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
+ PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+ createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
+ mLauncher.getDepthController(), pa);
+ anim.play(pa.buildAnim());
Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
Animator launcherAnim;
final AnimatorListenerAdapter windowAnimEndListener;
if (launcherClosing) {
- launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView, helper);
+ launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
@@ -152,81 +127,8 @@
return () -> {
overview.setFreezeViewVisibility(false);
+ overview.setTranslationY(0);
mLauncher.getStateManager().reapplyState();
};
}
-
- @Override
- public int getStateElementAnimationsCount() {
- return 4;
- }
-
- @Override
- public Animator createStateElementAnimation(int index, float... values) {
- switch (index) {
- case INDEX_SHELF_ANIM: {
- AllAppsTransitionController aatc = mLauncher.getAllAppsController();
- Animator springAnim = aatc.createSpringAnimation(values);
-
- if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
- // Translate hotseat with the shelf until reaching overview.
- float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
- ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
- float shiftRange = aatc.getShiftRange();
- if (values.length == 1) {
- values = new float[] {aatc.getProgress(), values[0]};
- }
- ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
- hotseatAnim.addUpdateListener(anim -> {
- float progress = (Float) anim.getAnimatedValue();
- if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
- float hotseatShift = (progress - overviewProgress) * shiftRange;
- mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
- }
- });
- hotseatAnim.setInterpolator(LINEAR);
- hotseatAnim.setDuration(springAnim.getDuration());
-
- AnimatorSet anim = new AnimatorSet();
- anim.play(hotseatAnim);
- anim.play(springAnim);
- return anim;
- }
-
- return springAnim;
- }
- case INDEX_RECENTS_FADE_ANIM:
- return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
- RecentsView.CONTENT_ALPHA, values);
- case INDEX_RECENTS_TRANSLATE_X_ANIM:
- PagedOrientationHandler orientationHandler =
- ((RecentsView)mLauncher.getOverviewPanel()).getPagedViewOrientedState()
- .getOrientationHandler();
- FloatProperty<View> translate = orientationHandler.getPrimaryViewTranslate();
- return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), translate)
- .setDampingRatio(0.8f)
- .setStiffness(250)
- .setValues(values)
- .build(mLauncher);
- case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
- StateAnimationConfig config = new StateAnimationConfig();
- config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
-
- config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
- config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
- if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
- config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
- config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
- }
-
-
- LauncherStateManager stateManager = mLauncher.getStateManager();
- return stateManager.createAtomicAnimation(
- stateManager.getCurrentStableState(), OVERVIEW, config);
- }
-
- default:
- return super.createStateElementAnimation(index, values);
- }
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
index 079a738..8477b10 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
@@ -26,10 +26,10 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderView;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.views.ArrowTipView;
import com.android.systemui.shared.system.LauncherEventUtil;
@@ -71,21 +71,16 @@
public static void scheduleShowIfNeeded(Launcher launcher) {
if (!hasSeenAllAppsTip(launcher)) {
- launcher.getStateManager().addStateListener(
- new LauncherStateManager.StateListener() {
- @Override
- public void onStateTransitionStart(LauncherState toState) {
+ launcher.getStateManager().addStateListener(new StateListener<LauncherState>() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState == ALL_APPS) {
+ if (showAllAppsTipIfNecessary(launcher)) {
+ launcher.getStateManager().removeStateListener(this);
}
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- if (finalState == ALL_APPS) {
- if (showAllAppsTipIfNecessary(launcher)) {
- launcher.getStateManager().removeStateListener(this);
- }
- }
- }
- });
+ }
+ }
+ });
}
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
index ec46418..914d9e9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -38,18 +38,18 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.Themes;
/**
* A view which shows a horizontal divider
*/
@TargetApi(Build.VERSION_CODES.O)
-public class AppsDividerView extends View implements LauncherStateManager.StateListener,
+public class AppsDividerView extends View implements StateListener<LauncherState>,
FloatingHeaderRow {
private static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
@@ -251,9 +251,6 @@
}
@Override
- public void onStateTransitionStart(LauncherState toState) { }
-
- @Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState == ALL_APPS) {
setAllAppsVisitedCount(getAllAppsVisitedCount() + 1);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
index 0712285..d200868 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
@@ -18,10 +18,9 @@
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.ComponentKey;
public class ComponentKeyMapper {
@@ -57,9 +56,8 @@
return item;
} else if (getComponentClass().equals(COMPONENT_CLASS_MARKER)) {
return mCache.getInstantApp(componentKey.componentName.getPackageName());
- } else if (componentKey instanceof ShortcutKey) {
- return mCache.getShortcutInfo((ShortcutKey) componentKey);
+ } else {
+ return mCache.getShortcutInfo(componentKey);
}
- return null;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 54f58e2..ab96b1340 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -38,13 +38,14 @@
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import java.util.ArrayList;
@@ -76,7 +77,7 @@
private final Runnable mOnUpdateCallback;
private final IconCache mIconCache;
- private final Map<ShortcutKey, WorkspaceItemInfo> mShortcuts;
+ private final Map<ComponentKey, WorkspaceItemInfo> mShortcuts;
private final Map<String, InstantAppItemInfo> mInstantApps;
public DynamicItemCache(Context context, Runnable onUpdateCallback) {
@@ -230,7 +231,7 @@
}
@MainThread
- public WorkspaceItemInfo getShortcutInfo(ShortcutKey key) {
+ public WorkspaceItemInfo getShortcutInfo(ComponentKey key) {
return mShortcuts.get(key);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
index 6e5f461..6c4c601 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
@@ -21,9 +21,9 @@
import android.content.ComponentName;
import android.content.Intent;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
public class InstantAppItemInfo extends AppInfo {
@@ -44,7 +44,7 @@
workspaceItemInfo.status = WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON
| WorkspaceItemInfo.FLAG_RESTORE_STARTED
| WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI;
- workspaceItemInfo.intent.setPackage(componentName.getPackageName());
+ workspaceItemInfo.getIntent().setPackage(componentName.getPackageName());
return workspaceItemInfo;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index 8faec46..44691d3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -31,6 +31,7 @@
import android.util.AttributeSet;
import android.util.IntProperty;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
@@ -38,18 +39,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
@@ -60,6 +57,10 @@
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -241,8 +242,9 @@
while (getChildCount() > mNumPredictedAppsPerRow) {
removeViewAt(0);
}
+ LayoutInflater inflater = mLauncher.getAppsView().getLayoutInflater();
while (getChildCount() < mNumPredictedAppsPerRow) {
- BubbleTextView icon = (BubbleTextView) mLauncher.getLayoutInflater().inflate(
+ BubbleTextView icon = (BubbleTextView) inflater.inflate(
R.layout.all_apps_icon, this, false);
icon.setOnClickListener(PREDICTION_CLICK_LISTENER);
icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 632b9b5..a0f6b04 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -16,6 +16,9 @@
package com.android.launcher3.appprediction;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -25,22 +28,23 @@
import android.content.Context;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.MainThreadInitializedObject;
@@ -48,6 +52,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.OptionalInt;
import java.util.stream.IntStream;
/**
@@ -63,8 +68,8 @@
* 4) Maintains the current active client id (for the predictions) and all updates are performed on
* that client id.
*/
-public class PredictionUiStateManager implements StateListener, ItemInfoUpdateReceiver,
- OnIDPChangeListener, OnUpdateListener {
+public class PredictionUiStateManager implements StateListener<LauncherState>,
+ ItemInfoUpdateReceiver, OnIDPChangeListener, OnUpdateListener {
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
@@ -158,9 +163,6 @@
public void reapplyItemInfo(ItemInfoWithIcon info) { }
@Override
- public void onStateTransitionStart(LauncherState toState) { }
-
- @Override
public void onStateTransitionComplete(LauncherState state) {
if (mAppsView == null) {
return;
@@ -214,8 +216,11 @@
}
private void dispatchOnChange(boolean changed) {
- PredictionState newState = changed ? parseLastState() :
- (mPendingState == null ? mCurrentState : mPendingState);
+ PredictionState newState = changed
+ ? parseLastState()
+ : mPendingState != null && canApplyPredictions(mPendingState)
+ ? mPendingState
+ : mCurrentState;
if (changed && mAppsView != null && !canApplyPredictions(newState)) {
scheduleApplyPredictedApps(newState);
} else {
@@ -302,6 +307,32 @@
}
/**
+ * Returns ranking info for the app within all apps prediction.
+ * Only applicable when {@link ItemInfo#itemType} is one of the followings:
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
+ */
+ public OptionalInt getAllAppsRank(@Nullable ItemInfo itemInfo) {
+ if (itemInfo == null || itemInfo.getTargetComponent() == null || itemInfo.user == null) {
+ return OptionalInt.empty();
+ }
+
+ if (itemInfo.itemType == ITEM_TYPE_APPLICATION
+ || itemInfo.itemType == ITEM_TYPE_SHORTCUT
+ || itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
+ ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(),
+ itemInfo.user);
+ final List<ComponentKeyMapper> apps = getCurrentState().apps;
+ return IntStream.range(0, apps.size())
+ .filter(index -> key.equals(apps.get(index).getComponentKey()))
+ .findFirst();
+ }
+
+ return OptionalInt.empty();
+ }
+
+ /**
* Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is one of the followings:
* {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
@@ -310,6 +341,7 @@
*/
public static void fillInPredictedRank(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+
final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
|| (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
new file mode 100644
index 0000000..c968de9
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
@@ -0,0 +1,59 @@
+/*
+ * 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.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);
+ new HotseatActivityTracker<>().addToIntent(homeIntent);
+ startActivity(homeIntent);
+ finish();
+ }
+
+ static class HotseatActivityTracker<T extends QuickstepLauncher> implements
+ ActivityTracker.SchedulerCallback {
+
+ @Override
+ public boolean init(BaseActivity activity, boolean alreadyOnHome) {
+ QuickstepLauncher launcher = (QuickstepLauncher) activity;
+ if (launcher != null && launcher.getHotseatPredictionController() != null) {
+ launcher.getHotseatPredictionController().showEdu();
+ }
+ return false;
+ }
+
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 773c6c8..4f95254 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -15,56 +15,46 @@
*/
package com.android.launcher3.hybridhotseat;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
+ .LAUNCHER_HOTSEAT_EDU_ONLY_TIP;
+
import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Build;
import android.view.View;
-import androidx.core.app.NotificationCompat;
-
import com.android.launcher3.CellLayout;
-import com.android.launcher3.FolderInfo;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ActivityTracker;
+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.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.Snackbar;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.IntStream;
/**
* Controller class for managing user onboaridng flow for hybrid hotseat
*/
public class HotseatEduController {
- public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen";
- private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding";
- private static final int ONBOARDING_NOTIFICATION_ID = 7641;
-
- private static final String SETTINGS_ACTION =
+ public static final String HOTSEAT_EDU_ACTION =
+ "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
+ public static final String SETTINGS_ACTION =
"android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
private final Launcher mLauncher;
private final Hotseat mHotseat;
- private final NotificationManager mNotificationManager;
- private final Notification mNotification;
+ private HotseatRestoreHelper mRestoreHelper;
private List<WorkspaceItemInfo> mPredictedApps;
private HotseatEduDialog mActiveDialog;
@@ -72,24 +62,28 @@
private IntArray mNewScreens = null;
private Runnable mOnOnboardingComplete;
- HotseatEduController(Launcher launcher, Runnable runnable) {
+ HotseatEduController(Launcher launcher, HotseatRestoreHelper restoreHelper, Runnable runnable) {
mLauncher = launcher;
mHotseat = launcher.getHotseat();
+ mRestoreHelper = restoreHelper;
mOnOnboardingComplete = runnable;
- mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
- createNotificationChannel();
- mNotification = createNotification();
}
/**
* Checks what type of migration should be used and migrates hotseat
*/
void migrate() {
+ if (mRestoreHelper != null) {
+ mRestoreHelper.createBackup();
+ }
if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
migrateToFolder();
} else {
migrateHotseatWhole();
}
+ Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled,
+ R.string.hotseat_prediction_settings, null,
+ () -> mLauncher.startActivity(getSettingsIntent()));
}
/**
@@ -100,7 +94,6 @@
*/
private int migrateToFolder() {
ArrayDeque<FolderInfo> folders = new ArrayDeque<>();
-
ArrayList<WorkspaceItemInfo> putIntoFolder = new ArrayList<>();
//separate folders and items that can get in folders
@@ -110,7 +103,8 @@
ItemInfo info = (ItemInfo) view.getTag();
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
folders.add((FolderInfo) info);
- } else if (info instanceof WorkspaceItemInfo) {
+ } else if (info instanceof WorkspaceItemInfo && info.container == LauncherSettings
+ .Favorites.CONTAINER_HOTSEAT) {
putIntoFolder.add((WorkspaceItemInfo) info);
}
}
@@ -119,9 +113,9 @@
if (!putIntoFolder.isEmpty()) {
ItemInfo firstItem = putIntoFolder.get(0);
FolderInfo folderInfo = new FolderInfo();
- folderInfo.title = "";
mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container,
firstItem.screenId, firstItem.cellX, firstItem.cellY);
+ folderInfo.setTitle("", mLauncher.getModelWriter());
folderInfo.contents.addAll(putIntoFolder);
for (int i = 0; i < folderInfo.contents.size(); i++) {
ItemInfo item = folderInfo.contents.get(i);
@@ -200,11 +194,13 @@
pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ mNewScreens = IntArray.wrap(pageId);
}
for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
View child = mHotseat.getChildAt(i, 0);
if (child == null || child.getTag() == null) continue;
ItemInfo tag = (ItemInfo) child.getTag();
+ if (tag.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) continue;
mLauncher.getModelWriter().moveItemInDatabase(tag,
LauncherSettings.Favorites.CONTAINER_DESKTOP, pageId, i, toRow);
mNewItems.add(tag);
@@ -212,11 +208,6 @@
return pageId;
}
-
- void removeNotification() {
- mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
- }
-
void moveHotseatItems() {
mHotseat.removeAllViewsInLayout();
if (!mNewItems.isEmpty()) {
@@ -237,15 +228,14 @@
void finishOnboarding() {
mOnOnboardingComplete.run();
- destroy();
- mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
}
void showDimissTip() {
if (mHotseat.getShortcutsAndWidgets().getChildCount()
< mLauncher.getDeviceProfile().inv.numHotseatIcons) {
- Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled, R.string.hotseat_turn_off,
- null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
+ Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+ 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());
@@ -254,58 +244,27 @@
void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
mPredictedApps = predictedApps;
- if (!mPredictedApps.isEmpty()
- && mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
- mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification);
- }
- else {
- removeNotification();
- }
- }
-
- private void createNotificationChannel() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
- CharSequence name = mLauncher.getString(R.string.hotseat_edu_prompt_title);
- int importance = NotificationManager.IMPORTANCE_LOW;
- NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name,
- importance);
- mNotificationManager.createNotificationChannel(channel);
- }
-
- private Notification createNotification() {
- Intent intent = new Intent(mLauncher.getApplicationContext(), mLauncher.getClass());
- intent = new NotificationHandler().addToIntent(intent);
-
- CharSequence name = mLauncher.getString(R.string.hotseat_edu_prompt_title);
- String description = mLauncher.getString(R.string.hotseat_edu_prompt_content);
- NotificationCompat.Builder builder = new NotificationCompat.Builder(mLauncher,
- NOTIFICATION_CHANNEL_ID)
- .setContentTitle(name)
- .setOngoing(true)
- .setColor(Themes.getColorAccent(mLauncher))
- .setContentIntent(PendingIntent.getActivity(mLauncher, 0, intent,
- PendingIntent.FLAG_CANCEL_CURRENT))
- .setSmallIcon(R.drawable.hotseat_edu_notification_icon)
- .setContentText(description);
- return builder.build();
-
- }
-
- void destroy() {
- removeNotification();
- if (mActiveDialog != null) {
- mActiveDialog.setHotseatEduController(null);
- }
}
void showEdu() {
+ int childCount = mHotseat.getShortcutsAndWidgets().getChildCount();
+ CellLayout cellLayout = mLauncher.getWorkspace().getScreenWithId(Workspace.FIRST_SCREEN_ID);
// hotseat is already empty and does not require migration. show edu tip
- if (mHotseat.getShortcutsAndWidgets().getChildCount() == 0) {
- new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_auto_enrolled),
- mHotseat.getTop());
- finishOnboarding();
- } else {
+ boolean requiresMigration = IntStream.range(0, childCount).anyMatch(i -> {
+ View v = mHotseat.getShortcutsAndWidgets().getChildAt(i);
+ return v != null && v.getTag() != null && ((ItemInfo) v.getTag()).container
+ != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ });
+ boolean canMigrateToFirstPage = cellLayout.makeSpaceForHotseatMigration(false);
+ if (requiresMigration && canMigrateToFirstPage) {
showDialog();
+ } else {
+ new ArrowTipView(mLauncher).show(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);
+ finishOnboarding();
}
}
@@ -321,13 +280,8 @@
mActiveDialog.show(mPredictedApps);
}
- static class NotificationHandler implements
- ActivityTracker.SchedulerCallback<QuickstepLauncher> {
- @Override
- public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) {
- activity.getHotseatPredictionController().showEdu();
- return true;
- }
+ static Intent getSettingsIntent() {
+ return new Intent(SETTINGS_ACTION).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index cb46c40..4b8e434 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -15,9 +15,10 @@
*/
package com.android.launcher3.hybridhotseat;
-import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType
- .HYBRID_HOTSEAT_CANCELED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
+ .LAUNCHER_HOTSEAT_EDU_ACCEPT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_DENY;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_SEEN;
import android.animation.PropertyValuesHolder;
import android.content.Context;
@@ -29,15 +30,15 @@
import android.widget.Button;
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.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.views.AbstractSlideInView;
@@ -110,15 +111,13 @@
mHotseatEduController.moveHotseatItems();
mHotseatEduController.finishOnboarding();
- //TODO: pass actual page index here.
- // Temporarily we're passing 1 for folder migration and 2 for page migration
- logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2);
+ mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ACCEPT);
}
private void onDismiss(View v) {
mHotseatEduController.showDimissTip();
mHotseatEduController.finishOnboarding();
- logUserAction(false, -1);
+ mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_DENY);
handleClose(true);
}
@@ -143,38 +142,23 @@
int rightInset = insets.right - mInsets.right;
int bottomInset = insets.bottom - mInsets.bottom;
mInsets.set(insets);
- setPadding(leftInset, getPaddingTop(), rightInset, 0);
- mHotseatWrapper.setPadding(mHotseatWrapper.getPaddingLeft(), getPaddingTop(),
- mHotseatWrapper.getPaddingRight(), bottomInset);
- mHotseatWrapper.getLayoutParams().height =
- mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
- }
+ if (mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
+ setPadding(leftInset, getPaddingTop(), rightInset, 0);
+ mHotseatWrapper.setPadding(mHotseatWrapper.getPaddingLeft(), getPaddingTop(),
+ mHotseatWrapper.getPaddingRight(), bottomInset);
+ mHotseatWrapper.getLayoutParams().height =
+ mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
- private void logUserAction(boolean migrated, int pageIndex) {
- LauncherLogProto.Action action = new LauncherLogProto.Action();
- LauncherLogProto.Target target = new LauncherLogProto.Target();
- action.type = LauncherLogProto.Action.Type.TOUCH;
- action.touch = LauncherLogProto.Action.Touch.TAP;
- target.containerType = LauncherLogProto.ContainerType.TIP;
- target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
- target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
- : HYBRID_HOTSEAT_CANCELED;
- target.rank = MIGRATION_EXPERIMENT_IDENTIFIER;
- // encoding migration type on pageIndex
- target.pageIndex = pageIndex;
- target.cardinality = HotseatPredictionController.MAX_ITEMS_FOR_MIGRATION;
- LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
- UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
- }
-
- private void logOnBoardingSeen() {
- LauncherLogProto.Action action = new LauncherLogProto.Action();
- LauncherLogProto.Target target = new LauncherLogProto.Target();
- action.type = LauncherLogProto.Action.Type.TIP;
- target.containerType = LauncherLogProto.ContainerType.TIP;
- target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
- LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
- UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
+ } else {
+ setPadding(0, getPaddingTop(), 0, 0);
+ mHotseatWrapper.setPadding(mHotseatWrapper.getPaddingLeft(), getPaddingTop(),
+ mHotseatWrapper.getPaddingRight(),
+ (int) getResources().getDimension(R.dimen.bottom_sheet_edu_padding));
+ ((TextView) findViewById(R.id.hotseat_edu_heading)).setText(
+ R.string.hotseat_edu_title_migrate_landscape);
+ ((TextView) findViewById(R.id.hotseat_edu_content)).setText(
+ R.string.hotseat_edu_message_migrate_landscape);
+ }
}
private void animateOpen() {
@@ -225,8 +209,9 @@
|| mHotseatEduController == null) {
return;
}
+ AbstractFloatingView.closeAllOpenViews(mLauncher);
attachToContainer();
- logOnBoardingSeen();
+ mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_SEEN);
animateOpen();
populatePreview(predictions);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
new file mode 100644
index 0000000..c15a596
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
@@ -0,0 +1,129 @@
+/*
+ * 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.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Helper class to allow hot seat file logging
+ */
+public class HotseatFileLog {
+
+ public static final int LOG_DAYS = 10;
+ private static final String FILE_NAME_PREFIX = "hotseat-log-";
+ private static final DateFormat DATE_FORMAT =
+ DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+ public static final MainThreadInitializedObject<HotseatFileLog> INSTANCE =
+ new MainThreadInitializedObject<>(HotseatFileLog::new);
+
+
+ private final Handler mHandler = new Handler(
+ Executors.createAndStartNewLooper("hotseat-logger"));
+ private final File mLogsDir;
+ private PrintWriter mCurrentWriter;
+ private String mFileName;
+
+ private HotseatFileLog(Context context) {
+ mLogsDir = context.getFilesDir();
+ }
+
+ /**
+ * Prints log values to disk
+ */
+ public void log(String tag, String msg) {
+ String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg);
+
+ mHandler.post(() -> {
+ synchronized (this) {
+ PrintWriter writer = getWriter();
+ if (writer != null) {
+ writer.println(out);
+ }
+ }
+ });
+ }
+
+ private PrintWriter getWriter() {
+ String fName = FILE_NAME_PREFIX + (LOG_DAYS % 10);
+ if (fName.equals(mFileName)) return mCurrentWriter;
+
+ Calendar cal = Calendar.getInstance();
+
+ boolean append = false;
+ File logFile = new File(mLogsDir, fName);
+ if (logFile.exists()) {
+ Calendar modifiedTime = Calendar.getInstance();
+ modifiedTime.setTimeInMillis(logFile.lastModified());
+
+ // If the file was modified more that 36 hours ago, purge the file.
+ // We use instead of 24 to account for day-365 followed by day-1
+ modifiedTime.add(Calendar.HOUR, 36);
+ append = cal.before(modifiedTime);
+ }
+
+
+ if (mCurrentWriter != null) {
+ mCurrentWriter.close();
+ }
+ try {
+ mCurrentWriter = new PrintWriter(new FileWriter(logFile, append));
+ mFileName = fName;
+ } catch (Exception ex) {
+ Log.e("HotseatLogs", "Error writing logs to file", ex);
+ closeWriter();
+ }
+ return mCurrentWriter;
+ }
+
+
+ private synchronized void closeWriter() {
+ mFileName = null;
+ if (mCurrentWriter != null) {
+ mCurrentWriter.close();
+ }
+ mCurrentWriter = null;
+ }
+
+
+ /**
+ * Returns a list of all log files
+ */
+ public synchronized File[] getLogFiles() {
+ File[] files = new File[LOG_DAYS + FileLog.LOG_DAYS];
+ //include file log files here
+ System.arraycopy(FileLog.getLogFiles(), 0, files, 0, FileLog.LOG_DAYS);
+
+ closeWriter();
+ for (int i = 0; i < LOG_DAYS; i++) {
+ files[FileLog.LOG_DAYS + i] = new File(mLogsDir, FILE_NAME_PREFIX + i);
+ }
+ return files;
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 9bc0975..b94e633 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -15,10 +15,11 @@
*/
package com.android.launcher3.hybridhotseat;
+import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.logging.LoggerUtils.newAction;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -28,33 +29,25 @@
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
-import android.app.prediction.AppTargetId;
import android.content.ComponentName;
-import android.os.Bundle;
-import android.provider.DeviceConfig;
+import android.os.Process;
import android.util.Log;
+import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
-import com.android.launcher3.FolderInfo;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.appprediction.ComponentKeyMapper;
@@ -62,17 +55,27 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.PredictedHotseatContainer;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.uioverrides.DeviceFlag;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.views.ArrowTipView;
+import com.android.launcher3.views.Snackbar;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -91,18 +94,6 @@
private static final String TAG = "PredictiveHotseat";
private static final boolean DEBUG = false;
- public static final int MAX_ITEMS_FOR_MIGRATION = DeviceConfig.getInt(
- DeviceFlag.NAMESPACE_LAUNCHER, "max_homepage_items_for_migration", 5);
-
- //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543)
- private static final int APPTARGET_ACTION_UNPIN = 4;
-
- private static final String APP_LOCATION_HOTSEAT = "hotseat";
- private static final String APP_LOCATION_WORKSPACE = "workspace";
-
- private static final String BUNDLE_KEY_HOTSEAT = "hotseat_apps";
- private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps";
-
private static final String PREDICTION_CLIENT = "hotseat";
private DropTarget.DragObject mDragObject;
private int mHotSeatItemsCount;
@@ -111,16 +102,18 @@
private Launcher mLauncher;
private final Hotseat mHotseat;
+ private final HotseatRestoreHelper mRestoreHelper;
+
private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
private DynamicItemCache mDynamicItemCache;
+ private final HotseatPredictionModel mPredictionModel;
private AppPredictor mAppPredictor;
private AllAppsStore mAllAppsStore;
private AnimatorSet mIconRemoveAnimators;
private boolean mUIUpdatePaused = false;
-
- private HotseatEduController mHotseatEduController;
+ private boolean mIsDestroyed = false;
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
@@ -128,40 +121,78 @@
private final View.OnLongClickListener mPredictionLongClickListener = v -> {
if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
if (mLauncher.getWorkspace().isSwitchingState()) return false;
+ if (!mLauncher.getOnboardingPrefs().getBoolean(
+ OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN)) {
+ Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+ R.string.hotseat_prediction_settings, null,
+ () -> mLauncher.startActivity(getSettingsIntent()));
+ mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN);
+ mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ return true;
+ }
// Start the drag
mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
- return false;
+ return true;
};
public HotseatPredictionController(Launcher launcher) {
mLauncher = launcher;
mHotseat = launcher.getHotseat();
mAllAppsStore = mLauncher.getAppsView().getAppsStore();
+ LauncherAppState appState = LauncherAppState.getInstance(launcher);
+ mPredictionModel = (HotseatPredictionModel) appState.getPredictionModel();
mAllAppsStore.addUpdateListener(this);
mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction);
mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
launcher.getDeviceProfile().inv.addOnChangeListener(this);
mHotseat.addOnAttachStateChangeListener(this);
+ mRestoreHelper = new HotseatRestoreHelper(mLauncher);
if (mHotseat.isAttachedToWindow()) {
onViewAttachedToWindow(mHotseat);
}
}
/**
- * Returns whether or not the prediction controller is ready to show predictions
+ * Shows appropriate hotseat education based on prediction enabled and migration states.
*/
- public boolean isReady() {
- return mLauncher.getSharedPrefs().getBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
- false);
+ public void showEdu() {
+ mLauncher.getStateManager().goToState(NORMAL, true, () -> {
+ if (mComponentKeyMappers.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,
+ mRestoreHelper,
+ this::createPredictor);
+ eduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
+ eduController.showEdu();
+ }
+ });
}
/**
- * Transitions to NORMAL workspace mode and shows edu
+ * Shows educational tip for hotseat if user does not go through Tips app.
*/
- public void showEdu() {
- if (mHotseatEduController == null) return;
- mLauncher.getStateManager().goToState(LauncherState.NORMAL, true,
- () -> mHotseatEduController.showEdu());
+ 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
+ */
+ public boolean hasPredictions() {
+ return !mComponentKeyMappers.isEmpty();
}
@Override
@@ -179,10 +210,15 @@
}
private void fillGapsWithPrediction(boolean animate, Runnable callback) {
- if (!isReady() || mUIUpdatePaused || mDragObject != null) {
+ if (mUIUpdatePaused || mDragObject != null) {
return;
}
List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
+ if (mComponentKeyMappers.isEmpty() != predictedApps.isEmpty()) {
+ // Safely ignore update as AppsList is not ready yet. This will called again once
+ // apps are ready (HotseatPredictionController#onAppsUpdated)
+ return;
+ }
int predictionIndex = 0;
ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
// make sure predicted icon removal and filling predictions don't step on each other
@@ -249,6 +285,7 @@
* Unregisters callbacks and frees resources
*/
public void destroy() {
+ mIsDestroyed = true;
mAllAppsStore.removeUpdateListener(this);
mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
mHotseat.removeOnAttachStateChangeListener(this);
@@ -277,49 +314,61 @@
}
if (mAppPredictor != null) {
mAppPredictor.destroy();
+ mAppPredictor = null;
}
- mAppPredictor = apm.createAppPredictionSession(
- new AppPredictionContext.Builder(mLauncher)
- .setUiSurface(PREDICTION_CLIENT)
- .setPredictedTargetCount(mHotSeatItemsCount)
- .setExtras(getAppPredictionContextExtra())
- .build());
- mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
- this::setPredictedApps);
+ WeakReference<HotseatPredictionController> controllerRef = new WeakReference<>(this);
+
+
+ mPredictionModel.createBundle(bundle -> {
+ if (mIsDestroyed) return;
+ mAppPredictor = apm.createAppPredictionSession(
+ new AppPredictionContext.Builder(mLauncher)
+ .setUiSurface(PREDICTION_CLIENT)
+ .setPredictedTargetCount(mHotSeatItemsCount)
+ .setExtras(bundle)
+ .build());
+ mAppPredictor.registerPredictionUpdates(
+ mLauncher.getApplicationContext().getMainExecutor(),
+ list -> {
+ if (controllerRef.get() != null) {
+ controllerRef.get().setPredictedApps(list);
+ }
+ });
+ mAppPredictor.requestPredictionUpdate();
+ });
setPauseUIUpdate(false);
- performBetaCheck();
- if (!isReady()) {
- mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor);
- }
- mAppPredictor.requestPredictionUpdate();
}
- private Bundle getAppPredictionContextExtra() {
- Bundle bundle = new Bundle();
- bundle.putParcelableArrayList(BUNDLE_KEY_HOTSEAT,
- getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets())));
- bundle.putParcelableArrayList(BUNDLE_KEY_WORKSPACE, getPinnedAppTargetsInViewGroup(
- mLauncher.getWorkspace().getScreenWithId(
- Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets()));
-
- return bundle;
- }
-
- private ArrayList<AppTarget> getPinnedAppTargetsInViewGroup(ViewGroup viewGroup) {
- ArrayList<AppTarget> pinnedApps = new ArrayList<>();
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- View child = viewGroup.getChildAt(i);
- if (isPinnedIcon(child)) {
- WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag();
- pinnedApps.add(getAppTargetFromItemInfo(itemInfo));
- }
+ /**
+ * Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items.
+ */
+ public void showCachedItems(List<AppInfo> apps, IntArray ranks) {
+ if (hasPredictions() && mAppPredictor != null) {
+ mAppPredictor.requestPredictionUpdate();
+ fillGapsWithPrediction();
+ return;
}
- return pinnedApps;
+ int count = Math.min(ranks.size(), apps.size());
+ List<WorkspaceItemInfo> items = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i));
+ ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user);
+ preparePredictionInfo(item, ranks.get(i));
+ items.add(item);
+
+ mComponentKeyMappers.add(new ComponentKeyMapper(componentKey, mDynamicItemCache));
+ }
+ updateDependencies();
+ bindItems(items, false, null);
}
private void setPredictedApps(List<AppTarget> appTargets) {
mComponentKeyMappers.clear();
+ if (appTargets.isEmpty()) {
+ mRestoreHelper.restoreBackup();
+ }
StringBuilder predictionLog = new StringBuilder("predictedApps: [\n");
+ ArrayList<ComponentKey> componentKeys = new ArrayList<>();
for (AppTarget appTarget : appTargets) {
ComponentKey key;
if (appTarget.getShortcutInfo() != null) {
@@ -328,6 +377,7 @@
key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
appTarget.getClassName()), appTarget.getUser());
}
+ componentKeys.add(key);
predictionLog.append(key.toString());
predictionLog.append(",rank:");
predictionLog.append(appTarget.getRank());
@@ -335,13 +385,12 @@
mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
predictionLog.append("]");
- if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString());
- updateDependencies();
- if (isReady()) {
- fillGapsWithPrediction();
- } else if (mHotseatEduController != null) {
- mHotseatEduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
+ if (Utilities.IS_DEBUG_DEVICE) {
+ HotseatFileLog.INSTANCE.get(mLauncher).log(TAG, predictionLog.toString());
}
+ updateDependencies();
+ fillGapsWithPrediction();
+ mPredictionModel.cachePredictionComponentKeys(componentKeys);
}
private void updateDependencies() {
@@ -365,8 +414,11 @@
workspaceItemInfo.cellX, workspaceItemInfo.cellY);
ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
icon.pin(workspaceItemInfo);
- AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo);
- notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
+ AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(workspaceItemInfo);
+ if (appTarget != null) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
+ AppTargetEvent.ACTION_PIN, workspaceItemInfo));
+ }
}
private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
@@ -445,10 +497,9 @@
mIconRemoveAnimators.start();
}
- private void notifyItemAction(AppTarget target, String location, int action) {
+ private void notifyItemAction(AppTargetEvent event) {
if (mAppPredictor != null) {
- mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target,
- action).setLaunchLocation(location).build());
+ mAppPredictor.notifyAppTargetEvent(event);
}
}
@@ -466,36 +517,35 @@
/**
* Unpins pinned app when it's converted into a folder
*/
- public void folderCreatedFromWorkspaceItem(ItemInfo info, FolderInfo folderInfo) {
- if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- return;
+ public void folderCreatedFromWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) {
+ AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo);
+ AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo);
+ if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget,
+ AppTargetEvent.ACTION_PIN, folderInfo));
}
- AppTarget target = getAppTargetFromItemInfo(info);
- ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets();
- ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId(
- Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets();
-
- if (isInHotseat(folderInfo) && !getPinnedAppTargetsInViewGroup(hotseatVG).contains(
- target)) {
- notifyItemAction(target, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
- } else if (isInFirstPage(folderInfo) && !getPinnedAppTargetsInViewGroup(
- firstScreenVG).contains(target)) {
- notifyItemAction(target, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
+ // using folder info with isTrackedForPrediction as itemInfo.container is already changed
+ // to folder by this point
+ if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget,
+ AppTargetEvent.ACTION_UNPIN, folderInfo
+ ));
}
}
/**
* Pins workspace item created when all folder items are removed but one
*/
- public void folderConvertedToWorkspaceItem(ItemInfo info, FolderInfo folderInfo) {
- if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- return;
+ public void folderConvertedToWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) {
+ AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo);
+ AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo);
+ if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget,
+ AppTargetEvent.ACTION_UNPIN, folderInfo));
}
- AppTarget target = getAppTargetFromItemInfo(info);
- if (isInHotseat(info)) {
- notifyItemAction(target, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
- } else if (isInFirstPage(info)) {
- notifyItemAction(target, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN);
+ if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(itemInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget,
+ AppTargetEvent.ACTION_PIN, itemInfo));
}
}
@@ -506,35 +556,25 @@
}
ItemInfo dragInfo = mDragObject.dragInfo;
- ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets();
- ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId(
- Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets();
-
- if (dragInfo instanceof WorkspaceItemInfo
- && dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
- && dragInfo.getTargetComponent() != null) {
- AppTarget appTarget = getAppTargetFromItemInfo(dragInfo);
- if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) {
- if (!getPinnedAppTargetsInViewGroup(hotseatVG).contains(appTarget)) {
- notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
- }
+ if (mDragObject.isMoved()) {
+ AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(dragInfo);
+ //always send pin event first to prevent AiAi from predicting an item moved within
+ // the same page
+ if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(dragInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
+ AppTargetEvent.ACTION_PIN, dragInfo));
}
- if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) {
- if (!getPinnedAppTargetsInViewGroup(firstScreenVG).contains(appTarget)) {
- notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
- }
- }
- if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) {
- notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
- }
- if (isInFirstPage(dragInfo) && !isInFirstPage(mDragObject.originalDragInfo)) {
- notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN);
+ if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(
+ mDragObject.originalDragInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
+ AppTargetEvent.ACTION_UNPIN, mDragObject.originalDragInfo));
}
}
mDragObject = null;
fillGapsWithPrediction(true, this::removeOutlineDrawings);
}
+
@Nullable
@Override
public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
@@ -564,8 +604,10 @@
@Override
public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
- this.mHotSeatItemsCount = profile.numHotseatIcons;
- createPredictor();
+ if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
+ this.mHotSeatItemsCount = profile.numHotseatIcons;
+ createPredictor();
+ }
}
@Override
@@ -588,6 +630,53 @@
mHotseat.fillInLogContainerData(childInfo, child, parents);
}
+ /**
+ * Logs rank info based on current list of predicted items
+ */
+ public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
+ if (Utilities.IS_DEBUG_DEVICE) {
+ final String pkg = itemInfo.getTargetComponent() != null
+ ? itemInfo.getTargetComponent().getPackageName() : "unknown";
+ HotseatFileLog.INSTANCE.get(mLauncher).log("UserEvent",
+ "appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null
+ && !Process.myUserHandle().equals(itemInfo.user))
+ + ",launchLocation:" + itemInfo.container);
+ }
+
+ if (itemInfo.getTargetComponent() == null || itemInfo.user == null) {
+ return;
+ }
+
+ final ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+
+ final List<ComponentKeyMapper> predictedApps = new ArrayList<>(mComponentKeyMappers);
+ OptionalInt rank = IntStream.range(0, predictedApps.size())
+ .filter(index -> key.equals(predictedApps.get(index).getComponentKey()))
+ .findFirst();
+ if (!rank.isPresent()) {
+ return;
+ }
+
+ int cardinality = 0;
+ for (PredictedAppIcon icon : getPredictedIcons()) {
+ ItemInfo info = (ItemInfo) icon.getTag();
+ cardinality |= 1 << info.screenId;
+ }
+
+ PredictedHotseatContainer.Builder containerBuilder = PredictedHotseatContainer.newBuilder();
+ containerBuilder.setCardinality(cardinality);
+ if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ containerBuilder.setIndex(rank.getAsInt());
+ }
+ mLauncher.getStatsLogManager().logger()
+ .withInstanceId(instanceId)
+ .withRank(rank.getAsInt())
+ .withContainerInfo(ContainerInfo.newBuilder()
+ .setPredictedHotseatContainer(containerBuilder)
+ .build())
+ .log(LAUNCHER_HOTSEAT_RANKED);
+ }
+
private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
@@ -602,39 +691,6 @@
}
}
- private void performBetaCheck() {
- if (isReady()) return;
- int hotseatItemsCount = mHotseat.getShortcutsAndWidgets().getChildCount();
-
- // -1 to exclude smart space
- int workspaceItemCount = mLauncher.getWorkspace().getScreenWithId(
- Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets().getChildCount() - 1;
-
- // opt user into the feature without onboarding tip or migration if they don't have any
- // open spots in their hotseat and have more than maxItems in their hotseat + workspace
-
- if (hotseatItemsCount == mHotSeatItemsCount && workspaceItemCount + hotseatItemsCount
- > MAX_ITEMS_FOR_MIGRATION) {
- mLauncher.getSharedPrefs().edit().putBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
- true).apply();
-
- LauncherLogProto.Action action = newAction(LauncherLogProto.Action.Type.TOUCH);
- LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.TIP);
- action.touch = LauncherLogProto.Action.Touch.TAP;
- target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
- target.controlType = LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
-
- // temporarily encode details in log target (go/hotseat_migration)
- target.rank = 2;
- target.cardinality = MAX_ITEMS_FOR_MIGRATION;
- target.pageIndex = (workspaceItemCount * 1000) + hotseatItemsCount;
- LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
- UserEventDispatcher.newInstance(mLauncher).dispatchUserEvent(event, null);
-
-
- }
- }
-
/**
* Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
@@ -664,29 +720,4 @@
&& ((WorkspaceItemInfo) view.getTag()).container
== LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
-
- private static boolean isPinnedIcon(View view) {
- if (!(view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo)) {
- return false;
- }
- ItemInfo info = (ItemInfo) view.getTag();
- return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && (
- info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION);
- }
-
- private static boolean isInHotseat(ItemInfo itemInfo) {
- return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
- }
-
- private static boolean isInFirstPage(ItemInfo itemInfo) {
- return itemInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
- && itemInfo.screenId == Workspace.FIRST_SCREEN_ID;
- }
-
- private static AppTarget getAppTargetFromItemInfo(ItemInfo info) {
- if (info.getTargetComponent() == null) return null;
- ComponentName cn = info.getTargetComponent();
- return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
- cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
new file mode 100644
index 0000000..5a038d2
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -0,0 +1,136 @@
+/*
+ * 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 static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+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.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.PredictionModel;
+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;
+import java.util.function.Consumer;
+
+/**
+ * Model helper for app predictions in workspace
+ */
+public class HotseatPredictionModel extends PredictionModel {
+ 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";
+
+
+ public HotseatPredictionModel(Context context) { }
+
+ /**
+ * Creates and returns bundle using workspace items and cached items
+ */
+ public void createBundle(Consumer<Bundle> cb) {
+ LauncherAppState appState = LauncherAppState.getInstance(mContext);
+ appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ Bundle bundle = new Bundle();
+ ArrayList<AppTargetEvent> events = new ArrayList<>();
+ ArrayList<ItemInfo> workspaceItems = new ArrayList<>(dataModel.workspaceItems);
+ workspaceItems.addAll(dataModel.appWidgets);
+ for (ItemInfo item : workspaceItems) {
+ AppTarget target = getAppTargetFromInfo(item);
+ if (target != null && !isTrackedForPrediction(item)) continue;
+ events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
+ }
+ ArrayList<AppTarget> currentTargets = new ArrayList<>();
+ for (ItemInfo itemInfo : dataModel.cachedPredictedItems) {
+ AppTarget target = getAppTargetFromInfo(itemInfo);
+ if (target != null) currentTargets.add(target);
+ }
+ bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events);
+ bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
+ MAIN_EXECUTOR.execute(() -> cb.accept(bundle));
+ }
+ });
+ }
+
+ /**
+ * Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null
+ * if item is not supported prediction
+ */
+ public AppTarget getAppTargetFromInfo(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),
+ mContext.getPackageName(), info.user).build();
+ }
+ return null;
+ }
+
+
+ /**
+ * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item
+ * location using {@link ItemInfo}
+ */
+ public 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/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
new file mode 100644
index 0000000..8c1db4e
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
@@ -0,0 +1,86 @@
+/*
+ * 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 static com.android.launcher3.LauncherSettings.Favorites.HYBRID_HOTSEAT_BACKUP_TABLE;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.GridBackupTable;
+import com.android.launcher3.provider.LauncherDbUtils;
+
+/**
+ * A helper class to manage migration revert restoration for hybrid hotseat
+ */
+public class HotseatRestoreHelper {
+ private final Launcher mLauncher;
+ private boolean mBackupRestored = false;
+
+ HotseatRestoreHelper(Launcher context) {
+ mLauncher = context;
+ }
+
+ /**
+ * Creates a snapshot backup of Favorite table for future restoration use.
+ */
+ public void createBackup() {
+ MODEL_EXECUTOR.execute(() -> {
+ try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
+ LauncherSettings.Settings.call(
+ mLauncher.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
+ .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+ InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
+ GridBackupTable backupTable = new GridBackupTable(mLauncher,
+ transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
+ idp.numRows);
+ backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
+ transaction.commit();
+ LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+ LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE);
+ }
+ });
+ }
+
+ /**
+ * Finds and restores a previously saved snapshow of Favorites table
+ */
+ public void restoreBackup() {
+ if (mBackupRestored) return;
+ MODEL_EXECUTOR.execute(() -> {
+ try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
+ LauncherSettings.Settings.call(
+ mLauncher.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
+ .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+ if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
+ return;
+ }
+ InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
+ GridBackupTable backupTable = new GridBackupTable(mLauncher,
+ transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
+ idp.numRows);
+ backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
+ transaction.commit();
+ mBackupRestored = true;
+ mLauncher.getModel().forceReload();
+ }
+ });
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 304c77f..597c17b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -19,11 +19,13 @@
import static com.android.launcher3.graphics.IconShape.getShape;
import android.content.Context;
+import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.DashPathEffect;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.Rect;
+import android.os.Process;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -33,16 +35,19 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.DoubleShadowBubbleTextView;
/**
@@ -51,13 +56,18 @@
public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
LauncherAccessibilityDelegate.AccessibilityActionHandler {
- private static final float RING_EFFECT_RATIO = 0.11f;
+ private static final int RING_SHADOW_COLOR = 0x99000000;
+ private static final float RING_EFFECT_RATIO = 0.095f;
boolean mIsDrawingDot = false;
private final DeviceProfile mDeviceProfile;
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Path mRingPath = new Path();
private boolean mIsPinned = false;
- private int mNormalizedIconRadius;
+ private final int mNormalizedIconRadius;
+ private final BlurMaskFilter mShadowFilter;
+ private int mPlateColor;
+ boolean mDrawForDrag = false;
public PredictedAppIcon(Context context) {
this(context, null, 0);
@@ -69,17 +79,20 @@
public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
+ mDeviceProfile = ActivityContext.lookupContext(context).getDeviceProfile();
mNormalizedIconRadius = IconNormalizer.getNormalizedCircleSize(getIconSize()) / 2;
- setOnClickListener(ItemClickHandler.INSTANCE);
- setOnFocusChangeListener(Launcher.getLauncher(context).getFocusHandler());
+ int shadowSize = context.getResources().getDimensionPixelSize(
+ R.dimen.blur_size_thin_outline);
+ mShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.OUTER);
}
@Override
public void onDraw(Canvas canvas) {
int count = canvas.save();
if (!mIsPinned) {
- drawEffect(canvas);
+ boolean isBadged = getTag() instanceof WorkspaceItemInfo
+ && !Process.myUserHandle().equals(((ItemInfo) getTag()).user);
+ drawEffect(canvas, isBadged);
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
}
@@ -102,7 +115,7 @@
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
super.applyFromWorkspaceItem(info);
int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
- mIconRingPaint.setColor(ColorUtils.setAlphaComponent(color, 200));
+ mPlateColor = ColorUtils.setAlphaComponent(color, 200);
if (mIsPinned) {
setContentDescription(info.contentDescription);
} else {
@@ -136,9 +149,11 @@
@Override
public void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo) {
- accessibilityNodeInfo.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(PIN_PREDICTION,
- getContext().getText(R.string.pin_prediction)));
+ if (!mIsPinned) {
+ accessibilityNodeInfo.addAction(
+ new AccessibilityNodeInfo.AccessibilityAction(PIN_PREDICTION,
+ getContext().getText(R.string.pin_prediction)));
+ }
}
@Override
@@ -172,9 +187,49 @@
return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
}
- private void drawEffect(Canvas canvas) {
- getShape().drawShape(canvas, getOutlineOffsetX(), getOutlineOffsetY(),
- mNormalizedIconRadius, mIconRingPaint);
+ private void drawEffect(Canvas canvas, boolean isBadged) {
+ // Don't draw ring effect if item is about to be dragged.
+ if (mDrawForDrag) {
+ return;
+ }
+ mRingPath.reset();
+ getShape().addToPath(mRingPath, getOutlineOffsetX(), getOutlineOffsetY(),
+ mNormalizedIconRadius);
+ if (isBadged) {
+ float outlineSize = mNormalizedIconRadius * RING_EFFECT_RATIO * 2;
+ float iconSize = getIconSize() * (1 - 2 * RING_EFFECT_RATIO);
+ float badgeSize = LauncherIcons.getBadgeSizeForIconSize((int) iconSize) + outlineSize;
+ float badgeInset = mNormalizedIconRadius * 2 - badgeSize;
+ getShape().addToPath(mRingPath, getOutlineOffsetX() + badgeInset,
+ getOutlineOffsetY() + badgeInset, badgeSize / 2);
+
+ }
+ mIconRingPaint.setColor(RING_SHADOW_COLOR);
+ mIconRingPaint.setMaskFilter(mShadowFilter);
+ canvas.drawPath(mRingPath, mIconRingPaint);
+ mIconRingPaint.setColor(mPlateColor);
+ mIconRingPaint.setMaskFilter(null);
+ canvas.drawPath(mRingPath, mIconRingPaint);
+ }
+
+ @Override
+ public void getSourceVisualDragBounds(Rect bounds) {
+ super.getSourceVisualDragBounds(bounds);
+ if (!mIsPinned) {
+ int internalSize = (int) (bounds.width() * RING_EFFECT_RATIO);
+ bounds.inset(internalSize, internalSize);
+ }
+ }
+
+ @Override
+ public SafeCloseable prepareDrawDragView() {
+ mDrawForDrag = true;
+ invalidate();
+ SafeCloseable r = super.prepareDrawDragView();
+ return () -> {
+ r.close();
+ mDrawForDrag = false;
+ };
}
/**
@@ -184,6 +239,8 @@
PredictedAppIcon icon = (PredictedAppIcon) LayoutInflater.from(parent.getContext())
.inflate(R.layout.predicted_app_icon, parent, false);
icon.applyFromWorkspaceItem(info);
+ icon.setOnClickListener(ItemClickHandler.INSTANCE);
+ icon.setOnFocusChangeListener(Launcher.getLauncher(parent.getContext()).getFocusHandler());
return icon;
}
@@ -203,10 +260,8 @@
mOffsetX = icon.getOutlineOffsetX();
mOffsetY = icon.getOutlineOffsetY();
mIconRadius = icon.mNormalizedIconRadius;
- mOutlinePaint.setStyle(Paint.Style.STROKE);
- mOutlinePaint.setStrokeWidth(5);
- mOutlinePaint.setPathEffect(new DashPathEffect(new float[]{15, 15}, 0));
- mOutlinePaint.setColor(Color.argb(100, 245, 245, 245));
+ mOutlinePaint.setStyle(Paint.Style.FILL);
+ mOutlinePaint.setColor(Color.argb(24, 245, 245, 245));
}
/**
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
new file mode 100644
index 0000000..8f1d319
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
@@ -0,0 +1,33 @@
+/*
+ * 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.uioverrides;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+
+/** A util class that inflates a predicted app icon */
+public class PredictedAppIconInflater {
+ public static View inflate(LayoutInflater inflater, ViewGroup parent, WorkspaceItemInfo info) {
+ PredictedAppIcon icon = (PredictedAppIcon) inflater.inflate(
+ R.layout.predicted_app_icon, parent, false);
+ icon.applyFromWorkspaceItem(info);
+ return icon;
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a02d9c3..9a03d92 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -15,28 +15,47 @@
*/
package com.android.launcher3.uioverrides;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
+import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.model.data.AppInfo;
+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.testing.TestProtocol;
+import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
@@ -48,16 +67,21 @@
import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
+import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
-import com.android.quickstep.RecentsModel;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.views.RecentsView;
+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.OptionalInt;
import java.util.stream.Stream;
public class QuickstepLauncher extends BaseQuickstepLauncher {
@@ -68,20 +92,40 @@
*/
public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
- private HotseatPredictionController mHotseatPredictionController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.createPredictor();
+ }
+ }
+
+ @Override
+ protected void setupViews() {
+ super.setupViews();
if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
mHotseatPredictionController = new HotseatPredictionController(this);
}
}
@Override
+ protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
+ StatsLogger logger = getStatsLogManager()
+ .logger().withItemInfo(info).withInstanceId(instanceId);
+ OptionalInt allAppsRank = PredictionUiStateManager.INSTANCE.get(this).getAllAppsRank(info);
+ allAppsRank.ifPresent(logger::withRank);
+ logger.log(LAUNCHER_APP_LAUNCH_TAP);
+
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
+ }
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- onStateOrResumeChanged();
+ onStateOrResumeChanging(false /* inTransition */);
}
@Override
@@ -96,11 +140,9 @@
@Override
protected void onActivityFlagsChanged(int changeBits) {
super.onActivityFlagsChanged(changeBits);
-
if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED
- | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0
- && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0) {
- onStateOrResumeChanged();
+ | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
+ onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
}
if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0
@@ -136,32 +178,28 @@
}
/**
- * Returns Prediction controller for hybrid hotseat
- */
- public HotseatPredictionController getHotseatPredictionController() {
- return mHotseatPredictionController;
- }
-
- /**
* Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
*/
- private void onStateOrResumeChanged() {
+ private void onStateOrResumeChanging(boolean inTransition) {
LauncherState state = getStateManager().getState();
DeviceProfile profile = getDeviceProfile();
- boolean visible = (state == NORMAL || state == OVERVIEW) && isUserActive()
- && !profile.isVerticalBarLayout();
+ boolean willUserBeActive = (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
+ boolean visible = (state == NORMAL || state == OVERVIEW)
+ && (willUserBeActive || isUserActive())
+ && !profile.isVerticalBarLayout()
+ && profile.isPhone && !profile.isLandscape;
UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
profile.hotseatBarSizePx);
- if (state == NORMAL) {
+ if (state == NORMAL && !inTransition) {
((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
}
}
@Override
- public void finishBindingItems(int pageBoundFirst) {
- super.finishBindingItems(pageBoundFirst);
+ public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) {
+ super.bindPredictedItems(appInfos, ranks);
if (mHotseatPredictionController != null) {
- mHotseatPredictionController.createPredictor();
+ mHotseatPredictionController.showCachedItems(appInfos, ranks);
}
}
@@ -170,11 +208,60 @@
super.onDestroy();
if (mHotseatPredictionController != null) {
mHotseatPredictionController.destroy();
+ mHotseatPredictionController = null;
+ }
+ }
+
+ @Override
+ public void onStateSetEnd(LauncherState state) {
+ super.onStateSetEnd(state);
+
+ switch (state.ordinal) {
+ case HINT_STATE_ORDINAL: {
+ Workspace workspace = getWorkspace();
+ boolean willMoveScreens = workspace.getNextPage() != Workspace.DEFAULT_PAGE;
+ getStateManager().goToState(NORMAL, true,
+ willMoveScreens ? null : getScrimView()::startDragHandleEducationAnim);
+ if (willMoveScreens) {
+ workspace.post(workspace::moveToDefaultScreen);
+ }
+ break;
+ }
+ case OVERVIEW_STATE_ORDINAL: {
+ RecentsView recentsView = getOverviewPanel();
+ DiscoveryBounce.showForOverviewIfNeeded(this,
+ recentsView.getPagedOrientationHandler());
+ RecentsView rv = getOverviewPanel();
+ sendCustomAccessibilityEvent(
+ rv.getPageAt(rv.getCurrentPage()), TYPE_VIEW_FOCUSED, null);
+ break;
+ }
+ case QUICK_SWITCH_STATE_ORDINAL: {
+ RecentsView rv = getOverviewPanel();
+ TaskView tasktolaunch = rv.getTaskViewAt(0);
+ if (tasktolaunch != null) {
+ tasktolaunch.launchTask(false, success -> {
+ if (!success) {
+ getStateManager().goToState(OVERVIEW);
+ tasktolaunch.notifyTaskLaunchFailed(TAG);
+ } else {
+ getStateManager().moveToRestState();
+ }
+ }, MAIN_EXECUTOR.getHandler());
+ } else {
+ getStateManager().goToState(NORMAL);
+ }
+ break;
+ }
+
}
}
@Override
public TouchController[] createTouchControllers() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.1");
+ }
Mode mode = SysUINavigationMode.getMode(this);
ArrayList<TouchController> list = new ArrayList<>();
@@ -182,7 +269,13 @@
if (mode == NO_BUTTON) {
list.add(new NoButtonQuickSwitchTouchController(this));
list.add(new NavBarToHomeTouchController(this));
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.2");
+ }
if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.3");
+ }
list.add(new NoButtonNavbarToOverviewTouchController(this));
} else {
list.add(new FlingAndHoldTouchController(this));
@@ -211,6 +304,11 @@
return list.toArray(new TouchController[list.size()]);
}
+ @Override
+ public AtomicAnimationFactory createAtomicAnimationFactory() {
+ return new QuickstepAtomicAnimationFactory(this);
+ }
+
private static final class LauncherTaskViewController extends
TaskViewTouchController<Launcher> {
@@ -220,7 +318,12 @@
@Override
protected boolean isRecentsInteractive() {
- return mActivity.isInState(OVERVIEW);
+ return mActivity.isInState(OVERVIEW) || mActivity.isInState(OVERVIEW_MODAL_TASK);
+ }
+
+ @Override
+ protected boolean isRecentsModal() {
+ return mActivity.isInState(OVERVIEW_MODAL_TASK);
}
@Override
@@ -228,4 +331,13 @@
mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
}
}
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ RecentsView recentsView = getOverviewPanel();
+ writer.println("\nQuickstepLauncher:");
+ writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
+ recentsView.getPagedViewOrientedState()));
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 3d6e519..085b9b3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,19 +15,15 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_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 android.annotation.TargetApi;
import android.os.Build;
import android.util.FloatProperty;
-import android.view.View;
-import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
@@ -37,6 +33,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
@@ -60,7 +57,7 @@
mRecentsView.updateEmptyMessage();
mRecentsView.resetTaskVisuals();
}
- setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state, LINEAR);
+ setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state);
mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
}
@@ -74,26 +71,26 @@
builder.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
mRecentsView.updateEmptyMessage();
} else {
- builder.getAnim().addListener(
+ builder.addListener(
AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
}
- setAlphas(builder, toState,
- config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
+ setAlphas(builder, toState);
builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
toState.getOverviewFullscreenProgress(), LINEAR);
}
- private void setAlphas(PropertySetter propertySetter, LauncherState state,
- Interpolator actionInterpolator) {
+ private void setAlphas(PropertySetter propertySetter, LauncherState state) {
float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0;
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
buttonAlpha, LINEAR);
+ propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
+ MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+ }
- View actionsView = mLauncher.getActionsView();
- if (actionsView != null) {
- propertySetter.setFloat(actionsView, VIEW_ALPHA, buttonAlpha, actionInterpolator);
- }
+ @Override
+ FloatProperty<RecentsView> getTaskModalnessProperty() {
+ return TASK_MODALNESS;
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index a87d6d1..8ff05f2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -17,22 +17,20 @@
import android.content.Context;
-import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
/**
* State indicating that the Launcher is behind an app
*/
public class BackgroundAppState extends OverviewState {
- private static final int STATE_FLAGS =
- FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY
- | FLAG_DISABLE_INTERACTION;
+ private static final int STATE_FLAGS = FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI
+ | FLAG_WORKSPACE_INACCESSIBLE | FLAG_NON_INTERACTIVE | FLAG_CLOSE_POPUPS;
public BackgroundAppState(int id) {
this(id, LauncherLogProto.ContainerType.TASKSWITCHER);
@@ -43,17 +41,14 @@
}
@Override
- public void onStateEnabled(Launcher launcher) {
- AbstractFloatingView.closeAllOpenViews(launcher, false);
- }
-
- @Override
public float getVerticalProgress(Launcher launcher) {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return super.getVerticalProgress(launcher);
}
+ RecentsView recentsView = launcher.getOverviewPanel();
int transitionLength = LayoutUtils.getShelfTrackingDistance(launcher,
- launcher.getDeviceProfile());
+ launcher.getDeviceProfile(),
+ recentsView.getPagedOrientationHandler());
AllAppsTransitionController controller = launcher.getAllAppsController();
float scrollRange = Math.max(controller.getShiftRange(), 1);
float progressDelta = (transitionLength / scrollRange);
@@ -61,25 +56,8 @@
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- // Initialize the recents view scale to what it would be when starting swipe up
- RecentsView recentsView = launcher.getOverviewPanel();
- int taskCount = recentsView.getTaskViewCount();
- if (taskCount == 0) {
- return super.getOverviewScaleAndTranslation(launcher);
- }
- TaskView dummyTask;
- if (recentsView.getCurrentPage() >= recentsView.getTaskViewStartIndex()) {
- if (recentsView.getCurrentPage() <= taskCount - 1) {
- dummyTask = recentsView.getCurrentPageTaskView();
- } else {
- dummyTask = recentsView.getTaskViewAt(taskCount - 1);
- }
- } else {
- dummyTask = recentsView.getTaskViewAt(0);
- }
- return recentsView.getTempAppWindowAnimationHelper().updateForFullscreenOverview(dummyTask)
- .getScaleAndTranslation();
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return getOverviewScaleAndOffsetForBackgroundState(launcher);
}
@Override
@@ -97,16 +75,25 @@
public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
// Translate hotseat offscreen if we show it in overview.
+ RecentsView recentsView = launcher.getOverviewPanel();
ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
- launcher.getDeviceProfile());
+ launcher.getDeviceProfile(),
+ recentsView.getPagedOrientationHandler());
return scaleAndTranslation;
}
return super.getHotseatScaleAndTranslation(launcher);
}
@Override
- public float getDepth(Context context) {
+ protected float getDepthUnchecked(Context context) {
return 1f;
}
+
+ public static float[] getOverviewScaleAndOffsetForBackgroundState(
+ BaseDraggingActivity activity) {
+ return new float[] {
+ ((RecentsView) activity.getOverviewPanel()).getMaxScaleForFullScreen(),
+ NO_OFFSET};
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
new file mode 100644
index 0000000..fc0dcd5
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -0,0 +1,82 @@
+/*
+ * 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.uioverrides.states;
+
+import android.content.Context;
+import android.graphics.Rect;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * An Overview state that shows the current task in a modal fashion. Modal state is where the
+ * current task is shown on its own without other tasks visible.
+ */
+public class OverviewModalTaskState extends OverviewState {
+
+ private static final int STATE_FLAGS =
+ FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_WORKSPACE_INACCESSIBLE;
+
+ public OverviewModalTaskState(int id) {
+ super(id, ContainerType.OVERVIEW, STATE_FLAGS);
+ }
+
+ @Override
+ public int getTransitionDuration(Context launcher) {
+ return 300;
+ }
+
+ @Override
+ public int getVisibleElements(Launcher launcher) {
+ return OVERVIEW_BUTTONS;
+ }
+
+ @Override
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return getOverviewScaleAndOffsetForModalState(launcher);
+ }
+
+ @Override
+ public float getOverviewModalness() {
+ return 1.0f;
+ }
+
+ @Override
+ public void onBackPressed(Launcher launcher) {
+ launcher.getStateManager().goToState(LauncherState.OVERVIEW);
+ RecentsView recentsView = launcher.<RecentsView>getOverviewPanel();
+ if (recentsView != null) {
+ recentsView.resetModalVisuals();
+ } else {
+ super.onBackPressed(launcher);
+ }
+ }
+
+ public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
+ Rect out = new Rect();
+ activity.<RecentsView>getOverviewPanel().getTaskSize(out);
+ int taskHeight = out.height();
+ activity.<RecentsView>getOverviewPanel().getModalTaskSize(out);
+ int newHeight = out.height();
+
+ float scale = (float) newHeight / taskHeight;
+
+ return new float[] {scale, NO_OFFSET};
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
index 1288e7b..fc9a11b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
@@ -15,42 +15,17 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
-
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.states.StateAnimationConfig;
public class OverviewPeekState extends OverviewState {
+ private static final float OVERVIEW_OFFSET = 0.7f;
+
public OverviewPeekState(int id) {
super(id);
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- ScaleAndTranslation result = super.getOverviewScaleAndTranslation(launcher);
- result.translationX = NORMAL.getOverviewScaleAndTranslation(launcher).translationX
- - launcher.getResources().getDimension(R.dimen.overview_peek_distance);
- if (Utilities.isRtl(launcher.getResources())) {
- result.translationX = -result.translationX;
- }
- return result;
- }
-
- @Override
- public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
- StateAnimationConfig config) {
- if (this == OVERVIEW_PEEK && fromState == NORMAL) {
- config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
- config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
- config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
- }
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return new float[] {NO_SCALE, OVERVIEW_OFFSET};
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index bcfb11c..d174bfd 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -15,41 +15,22 @@
*/
package com.android.launcher3.uioverrides.states;
-import static android.view.View.VISIBLE;
-
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_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.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
import android.content.Context;
import android.graphics.Rect;
import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.Interpolator;
-import com.android.launcher3.AbstractFloatingView;
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.allapps.DiscoveryBounce;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
@@ -62,13 +43,11 @@
*/
public class OverviewState extends LauncherState {
- // Scale recents takes before animating in
- private static final float RECENTS_PREPARE_SCALE = 1.33f;
-
protected static final Rect sTempRect = new Rect();
private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
- | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
+ | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_WORKSPACE_INACCESSIBLE
+ | FLAG_CLOSE_POPUPS;
public OverviewState(int id) {
this(id, STATE_FLAGS);
@@ -83,9 +62,9 @@
}
@Override
- public int getTransitionDuration(Launcher launcher) {
+ public int getTransitionDuration(Context context) {
// In no-button mode, overview comes in all the way from the left, so give it more time.
- boolean isNoButtonMode = SysUINavigationMode.INSTANCE.get(launcher).getMode() == NO_BUTTON;
+ boolean isNoButtonMode = SysUINavigationMode.INSTANCE.get(context).getMode() == NO_BUTTON;
return isNoButtonMode && ENABLE_OVERVIEW_ACTIONS.get() ? 380 : 250;
}
@@ -123,8 +102,8 @@
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(1f, 0f, 0f);
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return new float[] {NO_SCALE, NO_OFFSET};
}
@Override
@@ -138,21 +117,6 @@
}
@Override
- public void onStateEnabled(Launcher launcher) {
- AbstractFloatingView.closeAllOpenViews(launcher);
- }
-
- @Override
- public void onStateTransitionEnd(Launcher launcher) {
- launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
- DiscoveryBounce.showForOverviewIfNeeded(launcher);
- RecentsView recentsView = launcher.getOverviewPanel();
- AccessibilityManagerCompat.sendCustomAccessibilityEvent(
- recentsView.getPageAt(recentsView.getCurrentPage()),
- AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
- }
-
- @Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
return new PageAlphaProvider(DEACCEL_2) {
@Override
@@ -164,7 +128,9 @@
@Override
public int getVisibleElements(Launcher launcher) {
- if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher)) {
+ RecentsView recentsView = launcher.getOverviewPanel();
+ if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher) ||
+ hideShelfInTwoButtonLandscape(launcher, recentsView.getPagedOrientationHandler())) {
return OVERVIEW_BUTTONS;
} else if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return VERTICAL_SWIPE_INDICATOR | OVERVIEW_BUTTONS;
@@ -205,7 +171,7 @@
}
@Override
- public float getDepth(Context context) {
+ protected float getDepthUnchecked(Context context) {
return 1f;
}
@@ -221,35 +187,6 @@
}
}
- @Override
- public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
- StateAnimationConfig config) {
- if ((fromState == NORMAL || fromState == HINT_STATE) && this == OVERVIEW) {
- if (SysUINavigationMode.getMode(launcher) == NO_BUTTON) {
- config.setInterpolator(ANIM_WORKSPACE_SCALE,
- fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
- config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
- } else {
- config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
-
- // Scale up the recents, if it is not coming from the side
- RecentsView overview = launcher.getOverviewPanel();
- if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
- SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
- }
- }
- config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
- config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
- Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
- && removeShelfFromOverview(launcher)
- ? OVERSHOOT_1_2
- : OVERSHOOT_1_7;
- config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
- config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
- config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
- }
- }
-
public static OverviewState newBackgroundState(int id) {
return new BackgroundAppState(id);
}
@@ -261,4 +198,11 @@
public static OverviewState newSwitchState(int id) {
return new QuickSwitchState(id);
}
+
+ /**
+ * New Overview substate that represents the overview in modal mode (one task shown on its own)
+ */
+ public static OverviewState newModalTaskState(int id) {
+ return new OverviewModalTaskState(id);
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 7b4bb02..2c7373e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -15,24 +15,16 @@
*/
package com.android.launcher3.uioverrides.states;
-import android.os.Handler;
-import android.os.Looper;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.GestureState;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
/**
* State to indicate we are about to launch a recent task. Note that this state is only used when
* quick switching from launcher; quick switching from an app uses LauncherSwipeHandler.
- * @see GestureState.GestureEndTarget#NEW_TASK
+ * @see com.android.quickstep.GestureState.GestureEndTarget#NEW_TASK
*/
public class QuickSwitchState extends BackgroundAppState {
- private static final String TAG = "QuickSwitchState";
-
public QuickSwitchState(int id) {
super(id, LauncherLogProto.ContainerType.APP);
}
@@ -49,21 +41,4 @@
public int getVisibleElements(Launcher launcher) {
return NONE;
}
-
- @Override
- public void onStateTransitionEnd(Launcher launcher) {
- TaskView tasktolaunch = launcher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
- if (tasktolaunch != null) {
- tasktolaunch.launchTask(false, success -> {
- if (!success) {
- launcher.getStateManager().goToState(OVERVIEW);
- tasktolaunch.notifyTaskLaunchFailed(TAG);
- } else {
- launcher.getStateManager().moveToRestState();
- }
- }, new Handler(Looper.getMainLooper()));
- } else {
- launcher.getStateManager().goToState(NORMAL);
- }
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
new file mode 100644
index 0000000..a0af797
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -0,0 +1,239 @@
+/*
+ * 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.uioverrides.states;
+
+import static android.view.View.VISIBLE;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.HINT_STATE;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
+import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+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.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.RecentsAtomicAnimationFactory;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Animation factory for quickstep specific transitions
+ */
+public class QuickstepAtomicAnimationFactory extends
+ RecentsAtomicAnimationFactory<Launcher, LauncherState> {
+
+ // Scale recents takes before animating in
+ private static final float RECENTS_PREPARE_SCALE = 1.33f;
+
+ public static final int INDEX_SHELF_ANIM = RecentsAtomicAnimationFactory.NEXT_INDEX + 0;
+ public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM =
+ RecentsAtomicAnimationFactory.NEXT_INDEX + 1;
+
+ private static final int MY_ANIM_COUNT = 2;
+ protected static final int NEXT_INDEX = RecentsAtomicAnimationFactory.NEXT_INDEX
+ + MY_ANIM_COUNT;
+
+ // Due to use of physics, duration may differ between devices so we need to calculate and
+ // cache the value.
+ private int mHintToNormalDuration = -1;
+
+ public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
+
+ public QuickstepAtomicAnimationFactory(QuickstepLauncher activity) {
+ super(activity, MY_ANIM_COUNT);
+ }
+
+ @Override
+ public Animator createStateElementAnimation(int index, float... values) {
+ switch (index) {
+ case INDEX_SHELF_ANIM: {
+ AllAppsTransitionController aatc = mActivity.getAllAppsController();
+ Animator springAnim = aatc.createSpringAnimation(values);
+
+ if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
+ // Translate hotseat with the shelf until reaching overview.
+ float overviewProgress = OVERVIEW.getVerticalProgress(mActivity);
+ ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mActivity);
+ float shiftRange = aatc.getShiftRange();
+ if (values.length == 1) {
+ values = new float[] {aatc.getProgress(), values[0]};
+ }
+ ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
+ hotseatAnim.addUpdateListener(anim -> {
+ float progress = (Float) anim.getAnimatedValue();
+ if (progress >= overviewProgress || mActivity.isInState(BACKGROUND_APP)) {
+ float hotseatShift = (progress - overviewProgress) * shiftRange;
+ mActivity.getHotseat().setTranslationY(hotseatShift + sat.translationY);
+ }
+ });
+ hotseatAnim.setInterpolator(LINEAR);
+ hotseatAnim.setDuration(springAnim.getDuration());
+
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(hotseatAnim);
+ anim.play(springAnim);
+ return anim;
+ }
+
+ return springAnim;
+ }
+ case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
+ StateAnimationConfig config = new StateAnimationConfig();
+ config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
+
+ config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
+ if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
+ config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
+ }
+
+ StateManager<LauncherState> stateManager = mActivity.getStateManager();
+ return stateManager.createAtomicAnimation(
+ stateManager.getCurrentStableState(), OVERVIEW, config);
+ }
+ default:
+ return super.createStateElementAnimation(index, values);
+ }
+ }
+
+ @Override
+ public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
+ StateAnimationConfig config) {
+ if (toState == NORMAL && fromState == OVERVIEW) {
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
+ config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
+ config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
+ config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
+ config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+ Workspace workspace = mActivity.getWorkspace();
+
+ // Start from a higher workspace scale, but only if we're invisible so we don't jump.
+ boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
+ if (isWorkspaceVisible) {
+ CellLayout currentChild = (CellLayout) workspace.getChildAt(
+ workspace.getCurrentPage());
+ isWorkspaceVisible = currentChild.getVisibility() == VISIBLE
+ && currentChild.getShortcutsAndWidgets().getAlpha() > 0;
+ }
+ if (!isWorkspaceVisible) {
+ workspace.setScaleX(0.92f);
+ workspace.setScaleY(0.92f);
+ }
+ Hotseat hotseat = mActivity.getHotseat();
+ boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
+ if (!isHotseatVisible) {
+ hotseat.setScaleX(0.92f);
+ hotseat.setScaleY(0.92f);
+ if (ENABLE_OVERVIEW_ACTIONS.get()) {
+ AllAppsContainerView qsbContainer = mActivity.getAppsView();
+ View qsb = qsbContainer.getSearchView();
+ boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
+ if (!qsbVisible) {
+ qsbContainer.setScaleX(0.92f);
+ qsbContainer.setScaleY(0.92f);
+ }
+ }
+ }
+ } else if (toState == NORMAL && fromState == OVERVIEW_PEEK) {
+ // Keep fully visible until the very end (when overview is offscreen) to make invisible.
+ config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
+ } else if (toState == OVERVIEW_PEEK && fromState == NORMAL) {
+ config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+ config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
+ } else if ((fromState == NORMAL || fromState == HINT_STATE) && toState == OVERVIEW) {
+ if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
+ config.setInterpolator(ANIM_WORKSPACE_SCALE,
+ fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+ } else {
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
+
+ // Scale up the recents, if it is not coming from the side
+ RecentsView overview = mActivity.getOverviewPanel();
+ if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
+ SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
+ }
+ }
+ config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_ALL_APPS_FADE, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
+ Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
+ && removeShelfFromOverview(mActivity)
+ ? OVERSHOOT_1_2
+ : OVERSHOOT_1_7;
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
+ config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
+ } else if (fromState == HINT_STATE && toState == NORMAL) {
+ config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
+ if (mHintToNormalDuration == -1) {
+ ValueAnimator va = getSpringScaleAnimator(mActivity, mActivity.getWorkspace(),
+ toState.getWorkspaceScaleAndTranslation(mActivity).scale);
+ mHintToNormalDuration = (int) va.getDuration();
+ }
+ config.duration = Math.max(config.duration, mHintToNormalDuration);
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 8af2747..fac478e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -16,7 +16,6 @@
package com.android.launcher3.uioverrides.touchcontrollers;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -31,23 +30,26 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppTransitionManagerImpl;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
@@ -82,7 +84,7 @@
@Override
protected long getAtomicDuration() {
- return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
+ return QuickstepAtomicAnimationFactory.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
}
@Override
@@ -178,6 +180,9 @@
@Override
public boolean onDrag(float displacement, MotionEvent event) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "FlingAndHoldTouchController");
+ }
float upDisplacement = -displacement;
mMotionPauseDetector.setDisallowPause(!handlingOverviewAnim()
|| upDisplacement < mMotionPauseMinDisplacement
@@ -206,8 +211,8 @@
mPeekAnim.cancel();
}
- Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
- INDEX_PAUSE_TO_OVERVIEW_ANIM);
+ Animator overviewAnim = mLauncher.createAtomicAnimationFactory()
+ .createStateElementAnimation(INDEX_PAUSE_TO_OVERVIEW_ANIM);
mAtomicAnim = new AnimatorSet();
mAtomicAnim.addListener(new AnimationSuccessListener() {
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 77118d5..c1a585e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -15,17 +15,21 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.animation.ValueAnimator;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
@@ -41,7 +45,9 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -95,18 +101,37 @@
}
private boolean canInterceptTouch(MotionEvent ev) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NavBarToHomeTouchController.canInterceptTouch "
+ + ev);
+ }
boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
if (!cameFromNavBar) {
return false;
}
- if (mStartState == OVERVIEW || mStartState == ALL_APPS) {
+ if (mStartState.overviewUi || mStartState == ALL_APPS) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED,
+ "NavBarToHomeTouchController.canInterceptTouch true 1 "
+ + mStartState.overviewUi + " " + (mStartState == ALL_APPS));
+ }
return true;
}
- if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+ int typeToClose = ENABLE_ALL_APPS_EDU.get() ? TYPE_ALL & ~TYPE_ALL_APPS_EDU : TYPE_ALL;
+ if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED,
+ "NavBarToHomeTouchController.canInterceptTouch true 2 "
+ + AbstractFloatingView.getTopOpenView(mLauncher), new Exception());
+ }
return true;
}
if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
&& AssistantUtilities.isExcludedAssistantRunning()) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED,
+ "NavBarToHomeTouchController.canInterceptTouch true 3");
+ }
return true;
}
return false;
@@ -129,14 +154,10 @@
private void initCurrentAnimation() {
long accuracy = (long) (getShiftRange() * 2);
final PendingAnimation builder = new PendingAnimation(accuracy);
- if (mStartState == OVERVIEW) {
+ if (mStartState.overviewUi) {
RecentsView recentsView = mLauncher.getOverviewPanel();
- float pullbackDist = mPullbackDistance;
- if (!recentsView.isRtl()) {
- pullbackDist = -pullbackDist;
- }
-
- builder.setFloat(recentsView, VIEW_TRANSLATE_X, pullbackDist, PULLBACK_INTERPOLATOR);
+ builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET,
+ -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
builder.addOnFrameCallback(
() -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
@@ -227,5 +248,9 @@
startContainerType,
mEndState.containerType,
mLauncher.getWorkspace().getCurrentPage());
+ mLauncher.getStatsLogManager().logger()
+ .withSrcState(StatsLogManager.containerTypeToAtomState(mStartState.containerType))
+ .withDstState(StatsLogManager.containerTypeToAtomState(mEndState.containerType))
+ .log(LAUNCHER_HOME_GESTURE);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 71aa2e8..9316938 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -24,17 +24,22 @@
import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import android.animation.Animator;
import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.PointF;
+import android.util.Log;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
@@ -57,12 +62,20 @@
private boolean mDidTouchStartInNavBar;
private boolean mReachedOverview;
+ private boolean mIsOverviewRehidden;
+ private boolean mIsHomeStaggeredAnimFinished;
// The last recorded displacement before we reached overview.
private PointF mStartDisplacement = new PointF();
+ // Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
+ private ObjectAnimator mNormalToHintOverviewScrimAnimator;
+
public NoButtonNavbarToOverviewTouchController(Launcher l) {
super(l);
mRecentsView = l.getOverviewPanel();
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController.ctor");
+ }
}
@Override
@@ -102,11 +115,31 @@
@Override
public void onDragStart(boolean start, float startDisplacement) {
super.onDragStart(start, startDisplacement);
-
+ if (mFromState == NORMAL && mToState == HINT_STATE) {
+ mNormalToHintOverviewScrimAnimator = ObjectAnimator.ofFloat(
+ mLauncher.getDragLayer().getOverviewScrim(),
+ OverviewScrim.SCRIM_PROGRESS,
+ mFromState.getOverviewScrimAlpha(mLauncher),
+ mToState.getOverviewScrimAlpha(mLauncher));
+ }
mReachedOverview = false;
}
@Override
+ protected void updateProgress(float fraction) {
+ super.updateProgress(fraction);
+ if (mNormalToHintOverviewScrimAnimator != null) {
+ mNormalToHintOverviewScrimAnimator.setCurrentFraction(fraction);
+ }
+ }
+
+ @Override
+ public void onDragEnd(float velocity) {
+ super.onDragEnd(velocity);
+ mNormalToHintOverviewScrimAnimator = null;
+ }
+
+ @Override
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
LauncherState targetState, float velocity, boolean isFling) {
super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, velocity,
@@ -124,6 +157,7 @@
if (mCurrentAnimation == null) {
return;
}
+ mNormalToHintOverviewScrimAnimator = null;
mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
mReachedOverview = true;
@@ -139,6 +173,13 @@
}
}
+ // Used if flinging back to home after reaching overview
+ private void maybeSwipeInteractionToHomeComplete() {
+ if (mIsHomeStaggeredAnimFinished && mIsOverviewRehidden) {
+ onSwipeInteractionCompleted(NORMAL, Touch.FLING);
+ }
+ }
+
@Override
protected boolean handlingOverviewAnim() {
return mDidTouchStartInNavBar && super.handlingOverviewAnim();
@@ -146,6 +187,9 @@
@Override
public boolean onDrag(float yDisplacement, float xDisplacement, MotionEvent event) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController");
+ }
if (mMotionPauseDetector.isPaused()) {
if (!mReachedOverview) {
mStartDisplacement.set(xDisplacement, yDisplacement);
@@ -165,16 +209,24 @@
protected void goToOverviewOnDragEnd(float velocity) {
float velocityDp = dpiFromPx(velocity);
boolean isFling = Math.abs(velocityDp) > 1;
- LauncherStateManager stateManager = mLauncher.getStateManager();
- if (isFling) {
- // When flinging, go back to home instead of overview.
+ StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+ boolean goToHomeInsteadOfOverview = isFling;
+ if (goToHomeInsteadOfOverview) {
if (velocity > 0) {
stateManager.goToState(NORMAL, true,
() -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
} else {
+ mIsHomeStaggeredAnimFinished = mIsOverviewRehidden = false;
+
StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
mLauncher, velocity, false /* animateOverviewScrim */);
- staggeredWorkspaceAnim.start();
+ staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mIsHomeStaggeredAnimFinished = true;
+ maybeSwipeInteractionToHomeComplete();
+ }
+ }).start();
// StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
stateManager.cancelAnimation();
@@ -183,24 +235,27 @@
config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
AnimatorSet anim = stateManager.createAtomicAnimation(
stateManager.getState(), NORMAL, config);
- anim.addListener(AnimationSuccessListener.forRunnable(
- () -> onSwipeInteractionCompleted(NORMAL, Touch.SWIPE)));
+ anim.addListener(AnimationSuccessListener.forRunnable(() -> {
+ mIsOverviewRehidden = true;
+ maybeSwipeInteractionToHomeComplete();
+ }));
anim.start();
}
- } else {
- if (mReachedOverview) {
- float distanceDp = dpiFromPx(Math.max(
- Math.abs(mRecentsView.getTranslationX()),
- Math.abs(mRecentsView.getTranslationY())));
- long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
- distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
- mRecentsView.animate()
- .translationX(0)
- .translationY(0)
- .setInterpolator(ACCEL_DEACCEL)
- .setDuration(duration)
- .withEndAction(this::maybeSwipeInteractionToOverviewComplete);
- }
+ }
+ if (mReachedOverview) {
+ float distanceDp = dpiFromPx(Math.max(
+ Math.abs(mRecentsView.getTranslationX()),
+ Math.abs(mRecentsView.getTranslationY())));
+ long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
+ distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
+ mRecentsView.animate()
+ .translationX(0)
+ .translationY(0)
+ .setInterpolator(ACCEL_DEACCEL)
+ .setDuration(duration)
+ .withEndAction(goToHomeInsteadOfOverview
+ ? null
+ : this::maybeSwipeInteractionToOverviewComplete);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index c92a872..1b439d1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -16,10 +16,10 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
import static com.android.launcher3.LauncherState.QUICK_SWITCH;
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
@@ -28,6 +28,10 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
+import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP;
+import static com.android.launcher3.logging.StatsLogManager.getLauncherAtomEvent;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
@@ -35,22 +39,21 @@
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.PointF;
import android.view.MotionEvent;
-import android.view.View;
import android.view.animation.Interpolator;
import com.android.launcher3.BaseQuickstepLauncher;
@@ -59,8 +62,10 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
@@ -117,7 +122,7 @@
mRecentsView = mLauncher.getOverviewPanel();
mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
mYRange = LayoutUtils.getShelfTrackingDistance(
- mLauncher, mLauncher.getDeviceProfile());
+ mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
mMotionPauseDetector = new MotionPauseDetector(mLauncher);
mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
R.dimen.motion_pause_detector_min_displacement_from_app);
@@ -237,58 +242,34 @@
private void setupOverviewAnimators() {
final LauncherState fromState = QUICK_SWITCH;
final LauncherState toState = OVERVIEW;
- LauncherState.ScaleAndTranslation fromScaleAndTranslation = fromState
- .getOverviewScaleAndTranslation(mLauncher);
- LauncherState.ScaleAndTranslation toScaleAndTranslation = toState
- .getOverviewScaleAndTranslation(mLauncher);
- // Update RecentView's translationX to have it start offscreen.
- float startScale = Utilities.mapRange(
- SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS),
- fromScaleAndTranslation.scale,
- toScaleAndTranslation.scale);
- fromScaleAndTranslation.translationX = mRecentsView.getOffscreenTranslationX(startScale);
// Set RecentView's initial properties.
- mRecentsView.setScaleX(fromScaleAndTranslation.scale);
- mRecentsView.setScaleY(fromScaleAndTranslation.scale);
- mRecentsView.setTranslationX(fromScaleAndTranslation.translationX);
- mRecentsView.setTranslationY(fromScaleAndTranslation.translationY);
+ SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
+ ADJACENT_PAGE_OFFSET.set(mRecentsView, 1f);
mRecentsView.setContentAlpha(1);
mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
+ mLauncher.getActionsView().getVisibilityAlpha().setValue(
+ (fromState.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1f : 0f);
+ float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
// As we drag right, animate the following properties:
// - RecentsView translationX
// - OverviewScrim
- AnimatorSet xOverviewAnim = new AnimatorSet();
- xOverviewAnim.play(ObjectAnimator.ofFloat(mRecentsView, View.TRANSLATION_X,
- toScaleAndTranslation.translationX));
- xOverviewAnim.play(ObjectAnimator.ofFloat(
- mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
- toState.getOverviewScrimAlpha(mLauncher)));
- long xAccuracy = (long) (mXRange * 2);
- xOverviewAnim.setDuration(xAccuracy);
- mXOverviewAnim = AnimatorPlaybackController.wrap(xOverviewAnim, xAccuracy);
+ PendingAnimation xAnim = new PendingAnimation((long) (mXRange * 2));
+ xAnim.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1], LINEAR);
+ xAnim.setFloat(mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
+ toState.getOverviewScrimAlpha(mLauncher), LINEAR);
+ mXOverviewAnim = xAnim.createPlaybackController();
mXOverviewAnim.dispatchOnStart();
// As we drag up, animate the following properties:
- // - RecentsView translationY
// - RecentsView scale
// - RecentsView fullscreenProgress
- AnimatorSet yAnimation = new AnimatorSet();
- Animator translateYAnim = ObjectAnimator.ofFloat(mRecentsView, View.TRANSLATION_Y,
- toScaleAndTranslation.translationY);
- Animator scaleAnim = ObjectAnimator.ofFloat(mRecentsView, SCALE_PROPERTY,
- toScaleAndTranslation.scale);
- Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(mRecentsView, FULLSCREEN_PROGRESS,
- fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress());
- scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
- fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
- yAnimation.play(translateYAnim);
- yAnimation.play(scaleAnim);
- yAnimation.play(fullscreenProgressAnim);
- long yAccuracy = (long) (mYRange * 2);
- yAnimation.setDuration(yAccuracy);
- mYOverviewAnim = AnimatorPlaybackController.wrap(yAnimation, yAccuracy);
+ PendingAnimation yAnim = new PendingAnimation((long) (mYRange * 2));
+ yAnim.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0], SCALE_DOWN_INTERPOLATOR);
+ yAnim.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
+ toState.getOverviewFullscreenProgress(), SCALE_DOWN_INTERPOLATOR);
+ mYOverviewAnim = yAnim.createPlaybackController();
mYOverviewAnim.dispatchOnStart();
}
@@ -339,8 +320,8 @@
if (mMotionPauseDetector.isPaused() && noFling) {
cancelAnimations();
- Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
- INDEX_PAUSE_TO_OVERVIEW_ANIM);
+ Animator overviewAnim = mLauncher.createAtomicAnimationFactory()
+ .createStateElementAnimation(INDEX_PAUSE_TO_OVERVIEW_ANIM);
overviewAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -453,6 +434,13 @@
mStartState.containerType,
targetState.containerType,
mLauncher.getWorkspace().getCurrentPage());
+ mLauncher.getStatsLogManager().logger()
+ .withSrcState(LAUNCHER_STATE_HOME)
+ .withDstState(StatsLogManager.containerTypeToAtomState(targetState.containerType))
+ .log(getLauncherAtomEvent(mStartState.containerType, targetState.containerType,
+ targetState.ordinal > mStartState.ordinal
+ ? LAUNCHER_UNKNOWN_SWIPEUP
+ : LAUNCHER_UNKNOWN_SWIPEDOWN));
mLauncher.getStateManager().goToState(targetState, false, this::clearState);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 9e53959..c643858 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -35,8 +35,6 @@
import android.view.MotionEvent;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
@@ -57,7 +55,7 @@
*/
public class QuickSwitchTouchController extends AbstractStateChangeTouchController {
- private @Nullable TaskView mTaskToLaunch;
+ protected final RecentsView mOverviewPanel;
public QuickSwitchTouchController(Launcher launcher) {
this(launcher, SingleAxisSwipeDetector.HORIZONTAL);
@@ -65,6 +63,7 @@
protected QuickSwitchTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
super(l, dir);
+ mOverviewPanel = l.getOverviewPanel();
}
@Override
@@ -94,7 +93,6 @@
public void onDragStart(boolean start, float startDisplacement) {
super.onDragStart(start, startDisplacement);
mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
- mTaskToLaunch = mLauncher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
ActivityManagerWrapper.getInstance()
.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
}
@@ -102,7 +100,6 @@
@Override
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
- mTaskToLaunch = null;
}
@Override
@@ -141,13 +138,15 @@
}
private void updateFullscreenProgress(float progress) {
- if (mTaskToLaunch != null) {
- mTaskToLaunch.setFullscreenProgress(progress);
- int sysuiFlags = progress > UPDATE_SYSUI_FLAGS_THRESHOLD
- ? mTaskToLaunch.getThumbnail().getSysUiStatusNavFlags()
- : 0;
- mLauncher.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
+ mOverviewPanel.setFullscreenProgress(progress);
+ int sysuiFlags = 0;
+ if (progress > UPDATE_SYSUI_FLAGS_THRESHOLD) {
+ TaskView tv = mOverviewPanel.getTaskViewAt(0);
+ if (tv != null) {
+ sysuiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
+ }
}
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 1b3610a..0ee5d04 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -24,6 +24,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.view.MotionEvent;
+import android.view.View;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
@@ -57,6 +58,7 @@
private final SingleAxisSwipeDetector mDetector;
private final RecentsView mRecentsView;
private final int[] mTempCords = new int[2];
+ private final boolean mIsRtl;
private PendingAnimation mPendingAnimation;
private AnimatorPlaybackController mCurrentAnimation;
@@ -74,6 +76,7 @@
public TaskViewTouchController(T activity) {
mActivity = activity;
mRecentsView = activity.getOverviewPanel();
+ mIsRtl = Utilities.isRtl(activity.getResources());
SingleAxisSwipeDetector.Direction dir =
mRecentsView.getPagedOrientationHandler().getOppositeSwipeDirection();
mDetector = new SingleAxisSwipeDetector(activity, this, dir);
@@ -95,6 +98,9 @@
protected abstract boolean isRecentsInteractive();
+ /** Is recents view showing a single task in a modal way. */
+ protected abstract boolean isRecentsModal();
+
protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
}
@@ -133,7 +139,7 @@
if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
.isEventOverView(view, ev)) {
// Disable swiping up and down if the task overlay is modal.
- if (view.isTaskOverlayModal()) {
+ if (isRecentsModal()) {
mTaskBeingDragged = null;
break;
}
@@ -197,8 +203,8 @@
mCurrentAnimationIsGoingUp = goingUp;
BaseDragLayer dl = mActivity.getDragLayer();
final int secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl);
- long maxDuration = (long) (2 * secondaryLayerDimension);
- int verticalFactor = -orientationHandler.getTaskDismissDirectionFactor();
+ long maxDuration = 2 * secondaryLayerDimension;
+ int verticalFactor = orientationHandler.getTaskDragDisplacementFactor(mIsRtl);
int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged);
if (goingUp) {
mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
@@ -209,9 +215,11 @@
mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN);
- mTempCords[1] = mTaskBeingDragged.getHeight();
- dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
- mEndDisplacement = dl.getHeight() - mTempCords[1];
+ // Since the thumbnail is what is filling the screen, based the end displacement on it.
+ View thumbnailView = mTaskBeingDragged.getThumbnail();
+ mTempCords[1] = orientationHandler.getSecondaryDimension(thumbnailView);
+ dl.getDescendantCoordRelativeToSelf(thumbnailView, mTempCords);
+ mEndDisplacement = secondaryLayerDimension - mTempCords[1];
}
mEndDisplacement *= verticalFactor;
@@ -230,7 +238,7 @@
public void onDragStart(boolean start, float startDisplacement) {
PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
if (mCurrentAnimation == null) {
- reInitAnimationController(orientationHandler.isGoingUp(startDisplacement));
+ reInitAnimationController(orientationHandler.isGoingUp(startDisplacement, mIsRtl));
mDisplacementShift = 0;
} else {
mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
@@ -244,7 +252,7 @@
PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
float totalDisplacement = displacement + mDisplacementShift;
boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp :
- orientationHandler.isGoingUp(totalDisplacement);
+ orientationHandler.isGoingUp(totalDisplacement, mIsRtl);
if (isGoingUp != mCurrentAnimationIsGoingUp) {
reInitAnimationController(isGoingUp);
mFlingBlockCheck.blockFling();
@@ -276,7 +284,7 @@
float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
if (fling) {
logAction = Touch.FLING;
- boolean goingUp = orientationHandler.isGoingUp(velocity);
+ boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
} else {
logAction = Touch.SWIPE;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index 9dce984..de83caf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -17,53 +17,53 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.Animator;
import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
import android.util.Log;
-import android.view.View;
+import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statehandlers.DepthController;
-import com.android.quickstep.util.AppWindowAnimationHelper;
-import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.TransactionCompat;
/**
* Provider for the atomic (for 3-button mode) remote window animation from the app to the overview.
*
* @param <T> activity that contains the overview
*/
-final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> extends
+final class AppToOverviewAnimationProvider<T extends StatefulActivity<?>> extends
RemoteAnimationProvider {
private static final long RECENTS_LAUNCH_DURATION = 250;
private static final String TAG = "AppToOverviewAnimationProvider";
- private final BaseActivityInterface<T> mActivityInterface;
+ private final BaseActivityInterface<?, T> mActivityInterface;
// The id of the currently running task that is transitioning to overview.
private final int mTargetTaskId;
+ private final RecentsAnimationDeviceState mDeviceState;
private T mActivity;
private RecentsView mRecentsView;
- AppToOverviewAnimationProvider(BaseActivityInterface<T> activityInterface, int targetTaskId) {
+ AppToOverviewAnimationProvider(
+ BaseActivityInterface<?, T> activityInterface, int targetTaskId,
+ RecentsAnimationDeviceState deviceState) {
mActivityInterface = activityInterface;
mTargetTaskId = targetTaskId;
+ mDeviceState = deviceState;
}
/**
@@ -75,16 +75,12 @@
boolean onActivityReady(T activity, Boolean wasVisible) {
activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
- BaseActivityInterface.AnimationFactory factory =
- mActivityInterface.prepareRecentsUI(wasVisible,
- false /* animate activity */, (controller) -> {
+ BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI(
+ mDeviceState,
+ wasVisible, (controller) -> {
controller.dispatchOnStart();
- ValueAnimator anim = controller.getAnimationPlayer()
- .setDuration(RECENTS_LAUNCH_DURATION);
- anim.setInterpolator(FAST_OUT_SLOW_IN);
- anim.start();
+ controller.getAnimationPlayer().end();
});
- factory.onRemoteAnimationReceived(null);
factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
factory.setRecentsAttachedToAppWindow(true, false);
mActivity = activity;
@@ -101,31 +97,25 @@
@Override
public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets) {
- if (mRecentsView != null) {
- mRecentsView.setRunningTaskIconScaledDown(true);
+ PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+ if (mActivity == null) {
+ Log.e(TAG, "Animation created, before activity");
+ return pa.buildAnim();
}
- AnimatorSet anim = new AnimatorSet();
- anim.addListener(new AnimationSuccessListener() {
+ mRecentsView.setRunningTaskIconScaledDown(true);
+ pa.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
mActivityInterface.onSwipeUpToRecentsComplete();
- if (mRecentsView != null) {
- mRecentsView.animateUpRunningTaskIconScale();
- }
+ mRecentsView.animateUpRunningTaskIconScale();
}
});
- if (mActivity == null) {
- Log.e(TAG, "Animation created, before activity");
- return anim;
- }
DepthController depthController = mActivityInterface.getDepthController();
if (depthController != null) {
- anim.play(ObjectAnimator.ofFloat(depthController, DEPTH,
- BACKGROUND_APP.getDepth(mActivity),
- OVERVIEW.getDepth(mActivity))
- .setDuration(RECENTS_LAUNCH_DURATION));
+ pa.addFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(mActivity),
+ OVERVIEW.getDepth(mActivity), TOUCH_RESPONSE_INTERPOLATOR);
}
RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
@@ -135,53 +125,41 @@
RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId);
if (runningTaskTarget == null) {
Log.e(TAG, "No closing app");
- return anim;
+ return pa.buildAnim();
}
- final AppWindowAnimationHelper clipHelper = new AppWindowAnimationHelper(
- mRecentsView.getPagedViewOrientedState(), mActivity);
-
- // At this point, the activity is already started and laid-out. Get the home-bounds
- // relative to the screen using the rootView of the activity.
- int loc[] = new int[2];
- View rootView = mActivity.getRootView();
- rootView.getLocationOnScreen(loc);
- Rect homeBounds = new Rect(loc[0], loc[1],
- loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight());
- clipHelper.updateSource(homeBounds, runningTaskTarget);
-
- Rect targetRect = new Rect();
- mActivityInterface.getSwipeUpDestinationAndLength(mActivity.getDeviceProfile(), mActivity,
- targetRect);
- clipHelper.updateTargetRect(targetRect);
- clipHelper.prepareAnimation(mActivity.getDeviceProfile(), false /* isOpening */);
+ TaskViewSimulator tsv = new TaskViewSimulator(mActivity, mRecentsView.getSizeStrategy());
+ tsv.setDp(mActivity.getDeviceProfile());
+ tsv.setPreview(runningTaskTarget);
+ tsv.setLayoutRotation(mRecentsView.getPagedViewOrientedState().getTouchRotation(),
+ mRecentsView.getPagedViewOrientedState().getDisplayRotation());
TransformParams params = new TransformParams()
- .setSyncTransactionApplier(new SyncRtSurfaceTransactionApplierCompat(rootView));
- ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
- valueAnimator.setDuration(RECENTS_LAUNCH_DURATION);
- valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
- valueAnimator.addUpdateListener((v) -> {
- params.setProgress((float) v.getAnimatedValue())
- .setTargetSet(targets)
- .setLauncherOnTop(true);
- clipHelper.applyTransform(params);
- });
+ .setTargetSet(targets)
+ .setSyncTransactionApplier(new SurfaceTransactionApplier(mActivity.getRootView()));
+ AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { });
+ params.setBaseBuilderProxy((builder, app, p)
+ -> builder.withAlpha(recentsAlpha.value));
+
+ Interpolator taskInterpolator;
if (targets.isAnimatingHome()) {
- // If we are animating home, fade in the opening targets
- RemoteAnimationTargets openingSet = new RemoteAnimationTargets(appTargets,
- wallpaperTargets, MODE_OPENING);
+ params.setHomeBuilderProxy((builder, app, p) -> builder.withAlpha(1 - p.getProgress()));
- TransactionCompat transaction = new TransactionCompat();
- valueAnimator.addUpdateListener((v) -> {
- for (RemoteAnimationTargetCompat app : openingSet.apps) {
- transaction.setAlpha(app.leash, (float) v.getAnimatedValue());
- }
- transaction.apply();
- });
+ taskInterpolator = TOUCH_RESPONSE_INTERPOLATOR;
+ pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, TOUCH_RESPONSE_INTERPOLATOR);
+ } else {
+ // When animation from app to recents, the recents layer is drawn on top of the app. To
+ // prevent the overlap, we animate the task first and then quickly fade in the recents.
+ taskInterpolator = clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0, 0.8f);
+ pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1,
+ clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0.8f, 1));
}
- return anim;
+
+ pa.addFloat(params, TransformParams.PROGRESS, 0, 1, taskInterpolator);
+ tsv.addAppToOverviewAnim(pa, taskInterpolator);
+ pa.addOnFrameCallback(() -> tsv.apply(params));
+ return pa.buildAnim();
}
/**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index f81c56f..a63f3a8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -15,57 +15,38 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
-import android.animation.Animator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Build;
import android.util.Log;
-import android.util.Pair;
import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.Interpolator;
+import androidx.annotation.CallSuper;
import androidx.annotation.UiThread;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.model.PagedViewOrientedState;
-import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
import com.android.launcher3.util.VibratorWrapper;
-import com.android.launcher3.views.FloatingIconView;
-import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
+import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.AppWindowAnimationHelper;
-import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TransformParams;
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.InputConsumerController;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -74,39 +55,14 @@
* Base class for swipe up handler with some utility methods
*/
@TargetApi(Build.VERSION_CODES.Q)
-public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q extends RecentsView>
- implements RecentsAnimationListener {
+public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
+ extends SwipeUpAnimationLogic implements RecentsAnimationListener {
private static final String TAG = "BaseSwipeUpHandler";
- protected static final Rect TEMP_RECT = new Rect();
- public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
- private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
-
- // The distance needed to drag to reach the task size in recents.
- protected int mTransitionDragLength;
- // How much further we can drag past recents, as a factor of mTransitionDragLength.
- protected float mDragLengthFactor = 1;
- // Start resisting when swiping past this factor of mTransitionDragLength.
- private float mDragLengthFactorStartPullback = 1f;
- // This is how far down we can scale down, where 0f is full screen and 1f is recents.
- private float mDragLengthFactorMaxPullback = 1f;
-
- protected final Context mContext;
- protected final RecentsAnimationDeviceState mDeviceState;
- protected final GestureState mGestureState;
- protected final BaseActivityInterface<T> mActivityInterface;
+ protected final BaseActivityInterface<?, T> mActivityInterface;
protected final InputConsumerController mInputConsumer;
- protected AppWindowAnimationHelper mAppWindowAnimationHelper;
- protected final TransformParams mTransformParams = new TransformParams();
-
- // Shift in the range of [0, 1].
- // 0 => preview snapShot is completely visible, and hotseat is completely translated down
- // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
- // visible.
- protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
-
protected final ActivityInitListener mActivityInitListener;
protected RecentsAnimationController mRecentsAnimationController;
@@ -117,31 +73,30 @@
protected T mActivity;
protected Q mRecentsView;
- protected DeviceProfile mDp;
- private final int mPageSpacing;
protected Runnable mGestureEndCallback;
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
- protected int mFinishingRecentsAnimationForNewTaskId = -1;
- private PagedViewOrientedState mOrientedState;
+ private boolean mRecentsViewScrollLinked = false;
protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
GestureState gestureState, InputConsumerController inputConsumer) {
- mContext = context;
- mDeviceState = deviceState;
- mGestureState = gestureState;
+ super(context, deviceState, gestureState, new TransformParams());
mActivityInterface = gestureState.getActivityInterface();
- mActivityInitListener =
- mActivityInterface.createActivityInitListener(this::onActivityInit);
+ mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
mInputConsumer = inputConsumer;
- mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
- mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
- initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
- .getDeviceProfile(mContext));
+ }
+
+ /**
+ * To be called at the end of constructor of subclasses. This calls various methods which can
+ * depend on proper class initialization.
+ */
+ protected void initAfterSubclassConstructor() {
+ initTransitionEndpoints(
+ mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
}
protected void performHapticFeedback() {
@@ -152,28 +107,6 @@
return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
}
- @UiThread
- public void updateDisplacement(float displacement) {
- // We are moving in the negative x/y direction
- displacement = -displacement;
- float shift;
- if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
- shift = mDragLengthFactor;
- } else {
- float translation = Math.max(displacement, 0);
- shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
- if (shift > mDragLengthFactorStartPullback) {
- float pullbackProgress = Utilities.getProgress(shift,
- mDragLengthFactorStartPullback, mDragLengthFactor);
- pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
- shift = mDragLengthFactorStartPullback + pullbackProgress
- * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback);
- }
- }
-
- mCurrentShift.updateValue(shift);
- }
-
public void setGestureEndCallback(Runnable gestureEndCallback) {
mGestureEndCallback = gestureEndCallback;
}
@@ -181,10 +114,10 @@
public abstract Intent getLaunchIntent();
protected void linkRecentsViewScroll() {
- SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
+ SurfaceTransactionApplier.create(mRecentsView, applier -> {
mTransformParams.setSyncTransactionApplier(applier);
runOnRecentsAnimationStart(() ->
- mRecentsAnimationTargets.addDependentTransactionApplier(applier));
+ mRecentsAnimationTargets.addReleaseCheck(applier));
});
mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
@@ -192,45 +125,58 @@
updateFinalShift();
}
});
- mRecentsView.setAppWindowAnimationHelper(mAppWindowAnimationHelper);
runOnRecentsAnimationStart(() ->
mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
mRecentsAnimationTargets));
+ mRecentsViewScrollLinked = true;
}
- protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_START_FROM_RECENTS, "startNewTask1");
- }
+ protected void startNewTask(Consumer<Boolean> resultCallback) {
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// We finish recents animation inside launchTask() when live tile is enabled.
mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
true /* freezeTaskList */);
} else {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_START_FROM_RECENTS, "startNewTask2");
- }
int taskId = mRecentsView.getNextPageTaskView().getTask().key.id;
if (!mCanceled) {
TaskView nextTask = mRecentsView.getTaskView(taskId);
if (nextTask != null) {
+ mGestureState.updateLastStartedTaskId(taskId);
+ boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
+ .contains(taskId);
nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
success -> {
resultCallback.accept(success);
- if (!success) {
+ if (success) {
+ if (hasTaskPreviouslyAppeared) {
+ onRestartPreviouslyAppearedTask();
+ }
+ } else {
mActivityInterface.onLaunchTaskFailed();
nextTask.notifyTaskLaunchFailed(TAG);
- } else {
- mActivityInterface.onLaunchTaskSuccess();
+ mRecentsAnimationController.finish(true /* toRecents */, null);
}
}, MAIN_EXECUTOR.getHandler());
}
- mStateCallback.setStateOnUiThread(successStateFlag);
}
mCanceled = false;
}
- ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
+ }
+
+ /**
+ * Called when we successfully startNewTask() on the task that was previously running. Normally
+ * we call resumeLastTask() when returning to the previously running task, but this handles a
+ * specific edge case: if we switch from A to B, and back to A before B appears, we need to
+ * start A again to ensure it stays on top.
+ */
+ @CallSuper
+ protected void onRestartPreviouslyAppearedTask() {
+ // Finish the controller here, since we won't get onTaskAppeared() for a task that already
+ // appeared.
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.finish(false, null);
+ }
}
/**
@@ -246,6 +192,7 @@
}
/**
+ * TODO can we remove this now that we don't finish the controller until onTaskAppeared()?
* @return whether the recents animation has started and there are valid app targets.
*/
protected boolean hasTargets() {
@@ -257,29 +204,31 @@
RecentsAnimationTargets targets) {
mRecentsAnimationController = recentsAnimationController;
mRecentsAnimationTargets = targets;
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
- final Rect overviewStackBounds;
+ mTransformParams.setTargetSet(mRecentsAnimationTargets);
RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
mGestureState.getRunningTaskId());
- if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
- overviewStackBounds = mActivityInterface
- .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
- dp = dp.getMultiWindowProfile(mContext, new Point(
- overviewStackBounds.width(), overviewStackBounds.height()));
- } else {
- // If we are not in multi-window mode, home insets should be same as system insets.
- dp = dp.copy(mContext);
- overviewStackBounds = getStackBounds(dp);
- }
- dp.updateInsets(targets.homeContentInsets);
- dp.updateIsSeascape(mContext);
if (runningTaskTarget != null) {
- mAppWindowAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+ mTaskViewSimulator.setPreview(runningTaskTarget);
}
- mAppWindowAnimationHelper.prepareAnimation(dp, false /* isOpening */);
- initTransitionEndpoints(dp);
+ // 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) {
+ Rect overviewStackBounds = mActivityInterface
+ .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+ dp = dp.getMultiWindowProfile(mContext,
+ new WindowBounds(overviewStackBounds, targets.homeContentInsets));
+ } else {
+ // If we are not in multi-window mode, home insets should be same as system insets.
+ dp = dp.copy(mContext);
+ }
+ dp.updateInsets(targets.homeContentInsets);
+ dp.updateIsSeascape(mContext);
+ initTransitionEndpoints(dp);
+ }
// Notify when the animation starts
if (!mRecentsAnimationStartCallbacks.isEmpty()) {
@@ -308,50 +257,37 @@
}
}
- private Rect getStackBounds(DeviceProfile dp) {
- if (mActivity != null) {
- int loc[] = new int[2];
- View rootView = mActivity.getRootView();
- rootView.getLocationOnScreen(loc);
- return new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(),
- loc[1] + rootView.getHeight());
- } else {
- return new Rect(0, 0, dp.widthPx, dp.heightPx);
+ @Override
+ public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ if (mRecentsAnimationController != null) {
+ if (handleTaskAppeared(appearedTaskTarget)) {
+ mRecentsAnimationController.finish(false /* toRecents */,
+ null /* onFinishComplete */);
+ mActivityInterface.onLaunchTaskSuccess();
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+ }
}
}
- protected void initTransitionEndpoints(DeviceProfile dp) {
- mDp = dp;
+ /** @return Whether this was the task we were waiting to appear, and thus handled it. */
+ protected abstract boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget);
- mTransitionDragLength = mActivityInterface.getSwipeUpDestinationAndLength(
- dp, mContext, TEMP_RECT);
- if (!dp.isMultiWindowMode) {
- // When updating the target rect, also update the home bounds since the location on
- // screen of the launcher window may be stale (position is not updated until first
- // traversal after the window is resized). We only do this for non-multiwindow because
- // we otherwise use the minimized home bounds provided by the system.
- mAppWindowAnimationHelper.updateHomeBounds(getStackBounds(dp));
- }
- int displayRotation = 0;
- if (mOrientedState != null) {
- // TODO(b/150300347): The first recents animation after launcher is started with the
- // foreground app not in landscape will look funky until that bug is fixed
- displayRotation = mOrientedState.getDisplayRotation();
- }
- RotationHelper.getTargetRectForRotation(TEMP_RECT, dp.widthPx, dp.heightPx,
- displayRotation);
- mAppWindowAnimationHelper.updateTargetRect(TEMP_RECT);
- if (mDeviceState.isFullyGesturalNavMode()) {
- // We can drag all the way to the top of the screen.
- // TODO(b/149609070): Landscape apps are currently limited in
- // their ability to scale past the target rect.
- float dragFactor = (float) dp.heightPx / mTransitionDragLength;
- mDragLengthFactor = displayRotation == 0 ? dragFactor : Math.min(1.0f, dragFactor);
- Pair<Float, Float> dragFactorStartAndMaxProgress =
- mActivityInterface.getSwipeUpPullbackStartAndMaxProgress();
- mDragLengthFactorStartPullback = dragFactorStartAndMaxProgress.first;
- mDragLengthFactorMaxPullback = dragFactorStartAndMaxProgress.second;
- }
+ /**
+ * @return The index of the TaskView in RecentsView whose taskId matches the task that will
+ * resume if we finish the controller.
+ */
+ protected int getLastAppearedTaskIndex() {
+ return mGestureState.getLastAppearedTaskId() != -1
+ ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
+ : mRecentsView.getRunningTaskIndex();
+ }
+
+ /**
+ * @return Whether we are continuing a gesture that already landed on a new task,
+ * but before that task appeared.
+ */
+ protected boolean hasStartedNewTask() {
+ return mGestureState.getLastStartedTaskId() != -1;
}
/**
@@ -361,12 +297,14 @@
protected boolean onActivityInit(Boolean alreadyOnHome) {
T createdActivity = mActivityInterface.getCreatedActivity();
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
+ }
if (createdActivity != null) {
- mOrientedState = ((RecentsView) createdActivity.getOverviewPanel())
- .getPagedViewOrientedState();
- mAppWindowAnimationHelper = new AppWindowAnimationHelper(mOrientedState, mContext);
- initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
- .getDeviceProfile(mContext));
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
+ }
+ initTransitionEndpoints(createdActivity.getDeviceProfile());
}
return true;
}
@@ -389,7 +327,7 @@
public abstract void onMotionPauseChanged(boolean isPaused);
@UiThread
- public void onGestureStarted() { }
+ public void onGestureStarted(boolean isLikelyToStartNewTask) { }
@UiThread
public abstract void onGestureCancelled();
@@ -401,186 +339,47 @@
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
- public void initWhenReady() {
+ /**
+ * Registers a callback to run when the activity is ready.
+ * @param intent The intent that will be used to start the activity if it doesn't exist already.
+ */
+ public void initWhenReady(Intent intent) {
// Preload the plan
RecentsModel.INSTANCE.get(mContext).getTasks(null);
- mActivityInitListener.register();
+ mActivityInitListener.register(intent);
}
/**
- * Applies the transform on the recents animation without any additional null checks
+ * Applies the transform on the recents animation
*/
- protected void applyTransformUnchecked() {
- float shift = mCurrentShift.value;
- float offset = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
- float taskSize = getOrientationHandler()
- .getPrimarySize(mAppWindowAnimationHelper.getTargetRect());
- float offsetScale = getTaskCurveScaleForOffset(offset, taskSize);
- mTransformParams.setProgress(shift)
- .setOffset(offset)
- .setOffsetScale(offsetScale)
- .setTargetSet(mRecentsAnimationTargets)
- .setLauncherOnTop(true);
- mAppWindowAnimationHelper.applyTransform(mTransformParams);
- }
-
- private float getTaskCurveScaleForOffset(float offset, float taskSize) {
- int dpPixel = getOrientationHandler().getShortEdgeLength(mDp);
- float distanceToReachEdge = dpPixel / 2 + taskSize / 2 + mPageSpacing;
- float interpolation = Math.min(1, offset / distanceToReachEdge);
- return TaskView.getCurveScaleForInterpolation(interpolation);
- }
-
- protected PagedOrientationHandler getOrientationHandler() {
- if (mOrientedState == null) {
- return new PortraitPagedViewHandler();
+ protected void applyWindowTransform() {
+ if (mWindowTransitionController != null) {
+ float progress = mCurrentShift.value / mDragLengthFactor;
+ mWindowTransitionController.setPlayFraction(progress);
}
- return mOrientedState.getOrientationHandler();
+ if (mRecentsAnimationTargets != null) {
+ if (mRecentsViewScrollLinked) {
+ mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+ }
+ mTaskViewSimulator.apply(mTransformParams);
+ }
}
- /**
- * Creates an animation that transforms the current app window into the home app.
- * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
- * @param homeAnimationFactory The home animation factory.
- */
+ @Override
protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
- final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
- final View floatingView = homeAnimationFactory.getFloatingView();
- final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
- final RectF startRect = new RectF(
- mAppWindowAnimationHelper.applyTransform(
- mTransformParams.setProgress(startProgress)
- .setTargetSet(mRecentsAnimationTargets)
- .setLauncherOnTop(false)));
- if (isFloatingIconView) {
- RotationHelper.mapInverseRectFromNormalOrientation(startRect,
- mDp.widthPx, mDp.heightPx, mOrientedState.getDisplayRotation());
+ RectFSpringAnim anim =
+ super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
+ if (mRecentsAnimationTargets != null) {
+ mRecentsAnimationTargets.addReleaseCheck(anim);
}
- RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
- if (isFloatingIconView) {
- FloatingIconView fiv = (FloatingIconView) floatingView;
- anim.addAnimatorListener(fiv);
- fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
- }
-
- AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
-
- // 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.
- float startRadius = mAppWindowAnimationHelper.getCurrentCornerRadius();
- float endRadius = startRect.width() / 6f;
-
- float startTransformProgress = mTransformParams.getProgress();
- float endTransformProgress = 1;
-
- // 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 = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
- final RectF rotatedRect = new RectF();
- anim.addOnUpdateListener(new RectFSpringAnim.OnUpdateListener() {
-
- @Override
- public void onUpdate(RectF currentRect, float progress) {
- homeAnim.setPlayFraction(progress);
-
- rotatedRect.set(currentRect);
- if (isFloatingIconView) {
- RotationHelper.mapRectFromNormalOrientation(rotatedRect,
- mDp.widthPx, mDp.heightPx, mOrientedState.getDisplayRotation());
- mTransformParams.setCornerRadius(endRadius * progress + startRadius
- * (1f - progress));
- }
- mTransformParams.setProgress(
- Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
- .setCurrentRect(rotatedRect)
- .setTargetAlpha(getWindowAlpha(progress));
- mAppWindowAnimationHelper.applyTransform(mTransformParams);
-
- if (isFloatingIconView) {
- ((FloatingIconView) floatingView).update(currentRect, 1f, progress,
- windowAlphaThreshold, mAppWindowAnimationHelper.getCurrentCornerRadius(),
- false);
- }
- }
-
- @Override
- public void onCancel() {
- if (isFloatingIconView) {
- ((FloatingIconView) floatingView).fastFinish();
- }
- }
- });
- anim.addAnimatorListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- homeAnim.dispatchOnStart();
- }
-
- @Override
- public void onAnimationSuccess(Animator animator) {
- homeAnim.getAnimationPlayer().end();
- }
- });
return anim;
}
- /**
- * @param progress The progress of the animation to the home screen.
- * @return The current alpha to set on the animating app window.
- */
- 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);
- }
-
public interface Factory {
- BaseSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs,
- boolean continuingLastGesture, boolean isLikelyToStartNewTask);
- }
-
- protected interface RunningWindowAnim {
- void end();
-
- void cancel();
-
- static RunningWindowAnim wrap(Animator animator) {
- return new RunningWindowAnim() {
- @Override
- public void end() {
- animator.end();
- }
-
- @Override
- public void cancel() {
- animator.cancel();
- }
- };
- }
-
- static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
- return new RunningWindowAnim() {
- @Override
- public void end() {
- rectFSpringAnim.end();
- }
-
- @Override
- public void cancel() {
- rectFSpringAnim.cancel();
- }
- };
- }
+ BaseSwipeUpHandler newHandler(
+ GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
similarity index 85%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
index 4cfa6f1..37aa0da 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -22,6 +22,12 @@
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
+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.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
@@ -29,8 +35,8 @@
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
+import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
-import static com.android.quickstep.GestureState.STATE_TASK_APPEARED_DURING_SWITCH;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
@@ -38,46 +44,41 @@
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.animation.Animator;
-import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
-import android.graphics.RectF;
import android.os.Build;
import android.os.SystemClock;
-import android.util.Log;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.ViewTreeObserver.OnDrawListener;
import android.view.WindowInsets;
import android.view.animation.Interpolator;
-import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
-import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.AppWindowAnimationHelper.TargetAlphaProvider;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
@@ -85,17 +86,20 @@
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.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TaskStackChangeListener;
/**
* Handles the navigation gestures when Launcher is the default home activity.
+ * TODO: Merge this with BaseSwipeUpHandler
*/
@TargetApi(Build.VERSION_CODES.O)
-public class LauncherSwipeHandler<T extends BaseDraggingActivity>
- extends BaseSwipeUpHandler<T, RecentsView> implements OnApplyWindowInsetsListener {
- private static final String TAG = LauncherSwipeHandler.class.getSimpleName();
+public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q extends RecentsView>
+ extends BaseSwipeUpHandler<T, Q> implements OnApplyWindowInsetsListener {
+ private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName();
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
@@ -107,9 +111,11 @@
}
// Launcher UI related states
- private static final int STATE_LAUNCHER_PRESENT = getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
- private static final int STATE_LAUNCHER_STARTED = getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
- private static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
+ protected static final int STATE_LAUNCHER_PRESENT =
+ 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");
// Internal initialization states
private static final int STATE_APP_CONTROLLER_RECEIVED =
@@ -121,7 +127,7 @@
private static final int STATE_SCALED_CONTROLLER_RECENTS =
getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
- private static final int STATE_HANDLER_INVALIDATED =
+ protected static final int STATE_HANDLER_INVALIDATED =
getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
private static final int STATE_GESTURE_STARTED =
getFlagForIndex(7, "STATE_GESTURE_STARTED");
@@ -132,7 +138,7 @@
private static final int STATE_CAPTURE_SCREENSHOT =
getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
- private static final int STATE_SCREENSHOT_CAPTURED =
+ protected static final int STATE_SCREENSHOT_CAPTURED =
getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
private static final int STATE_SCREENSHOT_VIEW_SHOWN =
getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
@@ -148,7 +154,6 @@
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
public static final long MAX_SWIPE_DURATION = 350;
- public static final long MIN_SWIPE_DURATION = 80;
public static final long MIN_OVERSHOOT_DURATION = 120;
public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
@@ -163,7 +168,7 @@
*/
private static final int LOG_NO_OP_PAGE_INDEX = -1;
- private final TaskAnimationManager mTaskAnimationManager;
+ protected final TaskAnimationManager mTaskAnimationManager;
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
private RunningWindowAnim mRunningWindowAnim;
@@ -193,7 +198,7 @@
private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
- public LauncherSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
+ public BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture,
InputConsumerController inputConsumer) {
@@ -201,6 +206,8 @@
mTaskAnimationManager = taskAnimationManager;
mTouchTimeMs = touchTimeMs;
mContinuingLastGesture = continuingLastGesture;
+
+ initAfterSubclassConstructor();
initStateCallbacks();
}
@@ -220,9 +227,6 @@
| STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
- mStateCallback.runOnceAtState(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
- this::sendRemoteAnimationsToAnimationFactory);
-
mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::resumeLastTask);
mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
@@ -254,14 +258,16 @@
| STATE_RECENTS_SCROLLING_FINISHED,
this::onSettledOnEndTarget);
- mGestureState.runOnceAtState(STATE_TASK_APPEARED_DURING_SWITCH, this::onTaskAppeared);
-
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
this::notifyTransitionCancelled);
+ mGestureState.runOnceAtState(STATE_END_TARGET_SET,
+ () -> mDeviceState.onEndTargetCalculated(mGestureState.getEndTarget(),
+ mActivityInterface));
+
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
| STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
@@ -276,6 +282,7 @@
if (mActivity == activity) {
return true;
}
+
if (mActivity != null) {
// The launcher may have been recreated as a result of device rotation.
int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
@@ -294,7 +301,6 @@
mRecentsView = activity.getOverviewPanel();
mRecentsView.setOnPageTransitionEndCallback(null);
- linkRecentsViewScroll();
addLiveTileOverlay();
mStateCallback.setState(STATE_LAUNCHER_PRESENT);
@@ -311,6 +317,8 @@
// so we need to kick off switching to the overview predictions as soon as possible
mActivityInterface.updateOverviewPredictionState();
}
+ linkRecentsViewScroll();
+
return true;
}
@@ -327,14 +335,14 @@
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
return;
}
+ mTaskViewSimulator.setRecentsConfiguration(mActivity.getResources().getConfiguration());
// 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.
if (mGestureState.getEndTarget() != HOME) {
Runnable initAnimFactory = () -> {
- mAnimationFactory = mActivityInterface.prepareRecentsUI(
- mWasLauncherAlreadyVisible, true,
- this::onAnimatorPlaybackControllerCreated);
+ mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
+ mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
maybeUpdateRecentsAttachedState(false /* animate */);
};
if (mWasLauncherAlreadyVisible) {
@@ -409,6 +417,10 @@
updateSysUiFlags(mCurrentShift.value);
return;
}
+ notifyGestureAnimationStartToRecents();
+ }
+
+ protected void notifyGestureAnimationStartToRecents() {
mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
}
@@ -416,10 +428,6 @@
mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
}
- private void sendRemoteAnimationsToAnimationFactory() {
- mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationTargets);
- }
-
private void initializeLauncherAnimationController() {
buildAnimationController();
@@ -473,32 +481,24 @@
} else if (mContinuingLastGesture
&& mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
recentsAttachedToAppWindow = true;
- animate = false;
} else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
// The window is going away so make sure recents is always visible in this case.
recentsAttachedToAppWindow = true;
- animate = false;
} else {
recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
- if (animate) {
- // Only animate if an adjacent task view is visible on screen.
- TaskView adjacentTask1 = mRecentsView.getNextTaskView();
- TaskView adjacentTask2 = mRecentsView.getPreviousTaskView();
- float prevTranslationX = mRecentsView.getTranslationX();
- mRecentsView.setTranslationX(0);
- animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT))
- || (adjacentTask2 != null && adjacentTask2.getGlobalVisibleRect(TEMP_RECT));
- mRecentsView.setTranslationX(prevTranslationX);
- }
}
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
}
@Override
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
+ setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
+ }
+
+ private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
mIsLikelyToStartNewTask = isLikelyToStartNewTask;
- maybeUpdateRecentsAttachedState();
+ maybeUpdateRecentsAttachedState(animate);
}
}
@@ -542,7 +542,6 @@
private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
mLauncherTransitionController = anim;
mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
- mAnimationFactory.adjustActivityControllerInterpolators();
mLauncherTransitionController.dispatchOnStart();
updateLauncherTransitionProgress();
}
@@ -554,16 +553,11 @@
@Override
public void updateFinalShift() {
- if (mRecentsAnimationTargets != null) {
- applyTransformUnchecked();
- updateSysUiFlags(mCurrentShift.value);
- }
-
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (mRecentsAnimationTargets != null) {
LiveTileOverlay.INSTANCE.update(
- mAppWindowAnimationHelper.getCurrentRectWithInsets(),
- mAppWindowAnimationHelper.getCurrentCornerRadius());
+ mTaskViewSimulator.getCurrentCropRect(),
+ mTaskViewSimulator.getCurrentCornerRadius());
}
}
@@ -575,6 +569,8 @@
}
}
+ updateSysUiFlags(mCurrentShift.value);
+ applyWindowTransform();
updateLauncherTransitionProgress();
}
@@ -593,17 +589,20 @@
* @param windowProgress 0 == app, 1 == overview
*/
private void updateSysUiFlags(float windowProgress) {
- if (mRecentsView != null) {
+ if (mRecentsAnimationController != null && mRecentsView != null) {
+ TaskView runningTask = mRecentsView.getRunningTaskView();
TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
int centermostTaskFlags = centermostTask == null ? 0
: centermostTask.getThumbnail().getSysUiStatusNavFlags();
- boolean useHomeScreenFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
+ boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
+ boolean quickswitchThresholdPassed = centermostTask != runningTask;
+
// We will handle the sysui flags based on the centermost task view.
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.setWindowThresholdCrossed(centermostTaskFlags != 0
- && useHomeScreenFlags);
- }
- int sysuiFlags = useHomeScreenFlags ? 0 : centermostTaskFlags;
+ mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
+ || (quickswitchThresholdPassed && centermostTaskFlags != 0));
+ mRecentsAnimationController.setSplitScreenMinimized(swipeUpThresholdPassed);
+
+ int sysuiFlags = swipeUpThresholdPassed ? 0 : centermostTaskFlags;
mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
}
}
@@ -633,8 +632,9 @@
}
@Override
- public void onGestureStarted() {
+ public void onGestureStarted(boolean isLikelyToStartNewTask) {
notifyGestureStartedAsync();
+ setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
mGestureStarted = true;
}
@@ -690,12 +690,8 @@
protected InputConsumer createNewInputProxyHandler() {
endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
endLauncherTransitionController();
- if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- // Hide the task view, if not already hidden
- setTargetAlphaProvider(LauncherSwipeHandler::getHiddenTargetAlpha);
- }
- BaseDraggingActivity activity = mActivityInterface.getCreatedActivity();
+ StatefulActivity activity = mActivityInterface.getCreatedActivity();
return activity == null ? InputConsumer.NO_OP
: new OverviewInputConsumer(mGestureState, activity, null, true);
}
@@ -728,22 +724,20 @@
mStateCallback.setState(STATE_RESUME_LAST_TASK);
break;
}
+ ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
}
- private void onTaskAppeared() {
- RemoteAnimationTargetCompat app = mGestureState.getAnimationTarget();
- if (mRecentsAnimationController != null && app != null) {
-
- // TODO(b/152480470): Update Task target animation after onTaskAppeared holistically.
- /* android.util.Log.d("LauncherSwipeHandler", "onTaskAppeared");
-
- final boolean result = mRecentsAnimationController.removeTaskTarget(app);
- mGestureState.setAnimationTarget(null);
- android.util.Log.d("LauncherSwipeHandler", "removeTask, result=" + result); */
-
- mRecentsAnimationController.finish(false /* toRecents */,
- null /* onFinishComplete */);
+ @Override
+ protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
+ return false;
}
+ if (mStateCallback.hasStates(STATE_START_NEW_TASK)
+ && appearedTaskTarget.taskId == mGestureState.getLastStartedTaskId()) {
+ reset();
+ return true;
+ }
+ return false;
}
private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
@@ -801,12 +795,6 @@
}
}
- if (endTarget == RECENTS || endTarget == HOME) {
- // Since we're now done quickStepping, we want to only listen for touch events
- // for the main orientation's nav bar, instead of multiple
- mDeviceState.enableMultipleRegions(false);
- }
-
if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
return LAST_TASK;
}
@@ -915,6 +903,27 @@
ContainerType.NAVBAR, ContainerType.APP,
endTarget.containerType,
pageIndex);
+ StatsLogManager.EventEnum event;
+ switch (endTarget) {
+ case HOME:
+ event = LAUNCHER_HOME_GESTURE;
+ break;
+ case RECENTS:
+ event = LAUNCHER_OVERVIEW_GESTURE;
+ break;
+ case LAST_TASK:
+ case NEW_TASK:
+ event = (mLogDirection == Direction.LEFT)
+ ? LAUNCHER_QUICKSWITCH_LEFT
+ : LAUNCHER_QUICKSWITCH_RIGHT;
+ break;
+ default:
+ event = IGNORE;
+ }
+ StatsLogManager.newInstance(mContext).logger()
+ .withSrcState(LAUNCHER_STATE_BACKGROUND)
+ .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType))
+ .log(event);
}
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
@@ -925,37 +934,39 @@
interpolator, target, velocityPxPerMs));
}
+ protected abstract HomeAnimationFactory createHomeAnimationFactory(long duration);
+
+ private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ if (task.taskId == mGestureState.getRunningTaskId()) {
+ // Since this is an edge case, just cancel and relaunch with default activity
+ // options (since we don't know if there's an associated app icon to launch from)
+ endRunningWindowAnim(true /* cancel */);
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
+ mActivityRestartListener);
+ ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId, null);
+ }
+ }
+ };
+
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
// Set the state, but don't notify until the animation completes
mGestureState.setEndTarget(target, false /* isAtomic */);
-
maybeUpdateRecentsAttachedState();
- if (mGestureState.getEndTarget() == HOME) {
- HomeAnimationFactory homeAnimFactory;
- if (mActivity != null) {
- homeAnimFactory = mActivityInterface.prepareHomeUI();
- } else {
- homeAnimFactory = new HomeAnimationFactory() {
- @NonNull
- @Override
- public RectF getWindowTargetRect() {
- RectF fallbackTarget = new RectF(mAppWindowAnimationHelper.getTargetRect());
- Utilities.scaleRectFAboutCenter(fallbackTarget, 0.25f);
- return fallbackTarget;
- }
+ // If we are transitioning to launcher, then listen for the activity to be restarted while
+ // the transition is in progress
+ if (mGestureState.getEndTarget().isLauncher) {
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(
+ mActivityRestartListener);
+ }
- @NonNull
- @Override
- public AnimatorPlaybackController createActivityAnimationToHome() {
- return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
- }
- };
- mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
- isPresent -> mRecentsView.startHome());
- }
+ if (mGestureState.getEndTarget() == HOME) {
+ HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
windowAnim.addAnimatorListener(new AnimationSuccessListener() {
@Override
@@ -984,21 +995,30 @@
windowAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_START_FROM_RECENTS, "onAnimationSuccess");
- }
if (mRecentsAnimationController == null) {
// If the recents animation is interrupted, we still end the running
// animation (not canceled) so this is still called. In that case, we can
// skip doing any future work here for the current gesture.
return;
}
- if (target == NEW_TASK && mRecentsView != null
- && mRecentsView.getNextPage() == mRecentsView.getRunningTaskIndex()) {
- // We are about to launch the current running task, so use LAST_TASK state
- // instead of NEW_TASK. This could happen, for example, if our scroll is
- // aborted after we determined the target to be NEW_TASK.
- mGestureState.setEndTarget(LAST_TASK);
+ if (mRecentsView != null) {
+ int taskToLaunch = mRecentsView.getNextPage();
+ int runningTask = getLastAppearedTaskIndex();
+ boolean hasStartedNewTask = hasStartedNewTask();
+ if (target == NEW_TASK && taskToLaunch == runningTask
+ && !hasStartedNewTask) {
+ // We are about to launch the current running task, so use LAST_TASK
+ // state instead of NEW_TASK. This could happen, for example, if our
+ // scroll is aborted after we determined the target to be NEW_TASK.
+ mGestureState.setEndTarget(LAST_TASK);
+ } else if (target == LAST_TASK && hasStartedNewTask) {
+ // We are about to re-launch the previously running task, but we can't
+ // just finish the controller like we normally would because that would
+ // instead resume the last task that appeared, and not ensure that this
+ // task is restored to the top. To address this, re-launch the task as
+ // if it were a new task.
+ mGestureState.setEndTarget(NEW_TASK);
+ }
}
mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
@@ -1023,7 +1043,6 @@
mLauncherTransitionController.dispatchSetInterpolator(t -> end);
} else {
mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator);
- mAnimationFactory.adjustActivityControllerInterpolators();
}
mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
@@ -1043,9 +1062,11 @@
}
private void continueComputingRecentsScrollIfNecessary() {
- if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)) {
+ if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
+ && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)
+ && !mCanceled) {
computeRecentsScrollIfInvisible();
- mRecentsView.post(this::continueComputingRecentsScrollIfNecessary);
+ mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary);
}
}
@@ -1077,7 +1098,7 @@
}
// Make sure recents is in its final state
maybeUpdateRecentsAttachedState(false);
- mActivityInterface.onSwipeUpToHomeComplete();
+ mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
}
});
return anim;
@@ -1121,8 +1142,9 @@
@UiThread
private void startNewTaskInternal() {
- startNewTask(STATE_HANDLER_INVALIDATED, success -> {
+ startNewTask(success -> {
if (!success) {
+ reset();
// We couldn't launch the task, so take user to overview so they can
// decide what to do instead of staying in this broken state.
endLauncherTransitionController();
@@ -1132,6 +1154,12 @@
});
}
+ @Override
+ protected void onRestartPreviouslyAppearedTask() {
+ super.onRestartPreviouslyAppearedTask();
+ reset();
+ }
+
private void reset() {
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
@@ -1147,19 +1175,6 @@
.getAnimationPlayer().isStarted()) {
mLauncherTransitionController.getAnimationPlayer().cancel();
}
-
- if (mFinishingRecentsAnimationForNewTaskId != -1) {
- // If we are canceling mid-starting a new task, switch to the screenshot since the
- // recents animation has finished
- switchToScreenshot();
- TaskView newRunningTaskView = mRecentsView.getTaskView(
- mFinishingRecentsAnimationForNewTaskId);
- int newRunningTaskId = newRunningTaskView != null
- ? newRunningTaskView.getTask().key.id
- : -1;
- mRecentsView.setCurrentTask(newRunningTaskId);
- mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId);
- }
}
private void invalidateHandler() {
@@ -1170,6 +1185,7 @@
}
mActivityInitListener.unregister();
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mActivityRestartListener);
mTaskSnapshot = null;
}
@@ -1209,10 +1225,7 @@
mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
}
- private void switchToScreenshot() {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_START_FROM_RECENTS, "switchToScreenshot");
- }
+ protected void switchToScreenshot() {
final int runningTaskId = mGestureState.getRunningTaskId();
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (mRecentsAnimationController != null) {
@@ -1278,14 +1291,15 @@
// If there are no targets or the animation not started, then there is nothing to finish
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
- mRecentsAnimationController.finish(true /* toRecents */,
- () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
- true /* sendUserLeaveHint */);
+ finishRecentsControllerToHome(
+ () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
doLogGesture(HOME);
}
+ protected abstract void finishRecentsControllerToHome(Runnable callback);
+
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
endLauncherTransitionController();
mActivityInterface.onSwipeUpToRecentsComplete();
@@ -1300,11 +1314,6 @@
reset();
}
- private void setTargetAlphaProvider(TargetAlphaProvider provider) {
- mAppWindowAnimationHelper.setTaskAlphaCallback(provider);
- updateFinalShift();
- }
-
private void addLiveTileOverlay() {
if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
mRecentsView.setLiveTileOverlayAttached(true);
@@ -1316,13 +1325,6 @@
mRecentsView.setLiveTileOverlayAttached(false);
}
- public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha) {
- if (!isNotInRecents(app)) {
- return 0;
- }
- return expectedAlpha;
- }
-
private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
return app.isNotInRecents
|| app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index 4abb86a..33b9cde 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -15,30 +15,23 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
-import static com.android.quickstep.fallback.FallbackRecentsView.ZOOM_PROGRESS;
-import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
+import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
+import static com.android.quickstep.fallback.RecentsState.DEFAULT;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Rect;
-import android.graphics.RectF;
+import android.view.MotionEvent;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -50,19 +43,20 @@
* currently running one and apps should interact with the {@link RecentsActivity} as opposed
* to the in-launcher one.
*/
-public final class FallbackActivityInterface implements
- BaseActivityInterface<RecentsActivity> {
+public final class FallbackActivityInterface extends
+ BaseActivityInterface<RecentsState, RecentsActivity> {
- public FallbackActivityInterface() { }
+ public static final FallbackActivityInterface INSTANCE = new FallbackActivityInterface();
- @Override
- public void onTransitionCancelled(boolean activityVisible) {
- // TODO:
+ private FallbackActivityInterface() {
+ super(false, DEFAULT, BACKGROUND_APP);
}
+ /** 2 */
@Override
- public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
- LayoutUtils.calculateFallbackTaskSize(context, dp, outRect);
+ public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
+ PagedOrientationHandler orientationHandler) {
+ calculateTaskSize(context, dp, outRect, orientationHandler);
if (dp.isVerticalBarLayout()
&& SysUINavigationMode.INSTANCE.get(context).getMode() != NO_BUTTON) {
Rect targetInsets = dp.getInsets();
@@ -73,17 +67,13 @@
}
}
+ /** 4 */
@Override
- public void onSwipeUpToRecentsComplete() {
- RecentsActivity activity = getCreatedActivity();
- if (activity == null) {
- return;
- }
- RecentsView recentsView = activity.getOverviewPanel();
- recentsView.getClearAllButton().setVisibilityAlpha(1);
- recentsView.setDisallowScrollToClearAll(false);
+ public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {
+ onSwipeUpToRecentsComplete();
}
+ /** 5 */
@Override
public void onAssistantVisibilityChanged(float visibility) {
// This class becomes active when the screen is locked.
@@ -91,95 +81,13 @@
// set to zero prior to this class becoming active.
}
- @NonNull
+ /** 6 */
@Override
- public HomeAnimationFactory prepareHomeUI() {
- RecentsActivity activity = getCreatedActivity();
- RecentsView recentsView = activity.getOverviewPanel();
-
- return new HomeAnimationFactory() {
- @NonNull
- @Override
- public RectF getWindowTargetRect() {
- float centerX = recentsView.getPivotX();
- float centerY = recentsView.getPivotY();
- return new RectF(centerX, centerY, centerX, centerY);
- }
-
- @NonNull
- @Override
- public AnimatorPlaybackController createActivityAnimationToHome() {
- Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0);
- anim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- recentsView.startHome();
- }
- });
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.play(anim);
- long accuracy = 2 * Math.max(recentsView.getWidth(), recentsView.getHeight());
- return AnimatorPlaybackController.wrap(animatorSet, accuracy);
- }
- };
- }
-
- @Override
- public AnimationFactory prepareRecentsUI(boolean activityVisible,
- boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
- RecentsActivity activity = getCreatedActivity();
- if (activityVisible) {
- return (transitionLength) -> { };
- }
-
- FallbackRecentsView rv = activity.getOverviewPanel();
- rv.setContentAlpha(0);
- rv.getClearAllButton().setVisibilityAlpha(0);
- rv.setDisallowScrollToClearAll(true);
-
- boolean fromState = !animateActivity;
- rv.setInOverviewState(fromState);
-
- return new AnimationFactory() {
-
- boolean isAnimatingToRecents = false;
-
- @Override
- public void onRemoteAnimationReceived(RemoteAnimationTargets targets) {
- isAnimatingToRecents = targets != null && targets.isAnimatingHome();
- if (!isAnimatingToRecents) {
- rv.setContentAlpha(1);
- }
- createActivityInterface(getSwipeUpDestinationAndLength(
- activity.getDeviceProfile(), activity, new Rect()));
- }
-
- @Override
- public void createActivityInterface(long transitionLength) {
- AnimatorSet animatorSet = new AnimatorSet();
- if (isAnimatingToRecents) {
- ObjectAnimator anim = ObjectAnimator.ofFloat(rv, CONTENT_ALPHA, 0, 1);
- anim.setDuration(transitionLength).setInterpolator(LINEAR);
- animatorSet.play(anim);
- }
-
- ObjectAnimator anim = ObjectAnimator.ofFloat(rv, ZOOM_PROGRESS, 1, 0);
- anim.setDuration(transitionLength).setInterpolator(LINEAR);
- animatorSet.play(anim);
-
- AnimatorPlaybackController controller =
- AnimatorPlaybackController.wrap(animatorSet, transitionLength);
-
- // Since we are changing the start position of the UI, reapply the state, at the end
- controller.setEndAction(() -> {
- boolean endState = true;
- rv.setInOverviewState(controller.getInterpolatedProgress() > 0.5 ?
- endState : fromState);
- });
-
- callback.accept(controller);
- }
- };
+ public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
+ boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
+ DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
+ factory.initUI();
+ return factory;
}
@Override
@@ -192,7 +100,7 @@
@Nullable
@Override
public RecentsActivity getCreatedActivity() {
- return BaseRecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
+ return RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
}
@Nullable
@@ -217,12 +125,26 @@
}
@Override
- public boolean shouldMinimizeSplitScreen() {
+ public boolean allowMinimizeSplitScreen() {
// TODO: Remove this once b/77875376 is fixed
return false;
}
@Override
+ public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+ // In non-gesture mode, user might be clicking on the home button which would directly
+ // start the home activity instead of going through recents. In that case, defer starting
+ // recents until we are sure it is a gesture.
+ return !deviceState.isFullyGesturalNavMode()
+ || super.deferStartingActivity(deviceState, ev);
+ }
+
+ @Override
+ public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) {
+ // no-op, fake landscape not supported for 3P
+ }
+
+ @Override
public int getContainerType() {
RecentsActivity activity = getCreatedActivity();
boolean visible = activity != null && activity.isStarted() && activity.hasWindowFocus();
@@ -247,11 +169,10 @@
}
@Override
- public void onLaunchTaskSuccess() {
- RecentsActivity activity = getCreatedActivity();
- if (activity == null) {
- return;
- }
- activity.onTaskLaunched();
+ protected float getExtraSpace(Context context, DeviceProfile dp,
+ PagedOrientationHandler orientationHandler) {
+ return showOverviewActions(context)
+ ? context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height)
+ : 0;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index da73bc0..fc7a119 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -15,515 +15,208 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-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;
-import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
-import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
-import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
-import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.os.Bundle;
-import android.util.ArrayMap;
-import android.view.MotionEvent;
+import android.graphics.Matrix;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimationSuccessListener;
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.touch.LandscapePagedViewHandler;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
-import com.android.launcher3.util.ObjectWrapper;
-import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
-import com.android.quickstep.GestureState.GestureEndTarget;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.quickstep.fallback.FallbackRecentsView;
-import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.TransformParams.BuilderProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
/**
* Handles the navigation gestures when a 3rd party launcher is the default home activity.
*/
-public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
+public class FallbackSwipeHandler extends
+ BaseSwipeUpHandlerV2<RecentsActivity, FallbackRecentsView> {
- private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
-
- private static int getFlagForIndex(int index, String name) {
- if (DEBUG_STATES) {
- STATE_NAMES[index] = name;
- }
- return 1 << index;
- }
-
- private static final int STATE_RECENTS_PRESENT =
- getFlagForIndex(0, "STATE_RECENTS_PRESENT");
- private static final int STATE_HANDLER_INVALIDATED =
- getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
-
- private static final int STATE_GESTURE_CANCELLED =
- getFlagForIndex(2, "STATE_GESTURE_CANCELLED");
- private static final int STATE_GESTURE_COMPLETED =
- getFlagForIndex(3, "STATE_GESTURE_COMPLETED");
- private static final int STATE_APP_CONTROLLER_RECEIVED =
- getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
-
- public static class EndTargetAnimationParams {
- private final float mEndProgress;
- private final long mDurationMultiplier;
- private final float mLauncherAlpha;
-
- EndTargetAnimationParams(float endProgress, long durationMultiplier, float launcherAlpha) {
- mEndProgress = endProgress;
- mDurationMultiplier = durationMultiplier;
- mLauncherAlpha = launcherAlpha;
- }
- }
- private final ArrayMap<GestureEndTarget, EndTargetAnimationParams>
- mEndTargetAnimationParams = new ArrayMap();
-
- private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
-
- private boolean mOverviewThresholdPassed = false;
-
- private final boolean mInQuickSwitchMode;
- private final boolean mContinuingLastGesture;
+ private FallbackHomeAnimationFactory mActiveAnimationFactory;
private final boolean mRunningOverHome;
- private final boolean mSwipeUpOverHome;
- private boolean mTouchedHomeDuringTransition;
- private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
- private RunningWindowAnim mFinishAnimation;
+ private final Matrix mTmpMatrix = new Matrix();
+ private float mMaxLauncherScale = 1;
public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
- GestureState gestureState, InputConsumerController inputConsumer,
- boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
- super(context, deviceState, gestureState, inputConsumer);
+ TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
+ boolean continuingLastGesture, InputConsumerController inputConsumer) {
+ super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
+ continuingLastGesture, inputConsumer);
- mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
- mContinuingLastGesture = continuingLastGesture;
mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
- mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
-
- // Keep the home launcher invisible until we decide to land there.
- mLauncherAlpha.value = mRunningOverHome ? 1 : 0;
- if (mSwipeUpOverHome) {
- mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
- } else {
- mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
- }
-
- // Going home has an extra long progress to ensure that it animates into the screen
- mEndTargetAnimationParams.put(HOME, new EndTargetAnimationParams(3, 100, 1));
- mEndTargetAnimationParams.put(RECENTS, new EndTargetAnimationParams(1, 300, 0));
- mEndTargetAnimationParams.put(LAST_TASK, new EndTargetAnimationParams(0, 150, 1));
- mEndTargetAnimationParams.put(NEW_TASK, new EndTargetAnimationParams(0, 150, 1));
-
- initStateCallbacks();
- }
-
- private void initStateCallbacks() {
- mStateCallback = new MultiStateCallback(STATE_NAMES);
-
- mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
- this::onHandlerInvalidated);
- mStateCallback.runOnceAtState(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
- this::onHandlerInvalidatedWithRecents);
-
- mStateCallback.runOnceAtState(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
- this::finishAnimationTargetSetAnimationComplete);
-
- if (mInQuickSwitchMode) {
- mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
- | STATE_RECENTS_PRESENT,
- this::finishAnimationTargetSet);
- } else {
- mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
- this::finishAnimationTargetSet);
- }
- }
-
- private void onLauncherAlphaChanged() {
- if (mRecentsAnimationTargets != null && mGestureState.getEndTarget() == null) {
- applyTransformUnchecked();
- }
- }
-
- @Override
- protected boolean onActivityInit(Boolean alreadyOnHome) {
- super.onActivityInit(alreadyOnHome);
- mActivity = mActivityInterface.getCreatedActivity();
- mRecentsView = mActivity.getOverviewPanel();
- mRecentsView.setOnPageTransitionEndCallback(null);
- linkRecentsViewScroll();
- mRecentsView.setDisallowScrollToClearAll(true);
- mRecentsView.getClearAllButton().setVisibilityAlpha(0);
- mRecentsView.setZoomProgress(1);
-
- if (!mContinuingLastGesture) {
- if (mRunningOverHome) {
- mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
- } else {
- mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
- }
- }
- mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT);
- mDeviceState.enableMultipleRegions(false);
- return true;
- }
-
- @Override
- protected boolean moveWindowWithRecentsScroll() {
- return mInQuickSwitchMode;
- }
-
- @Override
- public void initWhenReady() {
- if (mInQuickSwitchMode) {
- // Only init if we are in quickswitch mode
- super.initWhenReady();
- }
- }
-
- @Override
- public void updateDisplacement(float displacement) {
- if (!mInQuickSwitchMode) {
- super.updateDisplacement(displacement);
- }
- }
-
- @Override
- protected InputConsumer createNewInputProxyHandler() {
- // Just consume all input on the active task
- return new InputConsumer() {
- @Override
- public int getType() {
- return InputConsumer.TYPE_NO_OP;
- }
-
- @Override
- public void onMotionEvent(MotionEvent ev) {
- mTouchedHomeDuringTransition = true;
- }
- };
- }
-
- @Override
- public void onMotionPauseChanged(boolean isPaused) {
- if (!mInQuickSwitchMode && mDeviceState.isFullyGesturalNavMode()) {
- updateOverviewThresholdPassed(isPaused);
- }
- }
-
- private void updateOverviewThresholdPassed(boolean passed) {
- if (passed != mOverviewThresholdPassed) {
- mOverviewThresholdPassed = passed;
- if (mSwipeUpOverHome) {
- mLauncherAlpha.animateToValue(mLauncherAlpha.value, passed ? 0 : 1)
- .setDuration(150).start();
- }
- performHapticFeedback();
- }
- }
-
- @Override
- public Intent getLaunchIntent() {
- if (mInQuickSwitchMode || mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
- return mGestureState.getOverviewIntent();
- } else {
- return mGestureState.getHomeIntent();
- }
- }
-
- @Override
- public void updateFinalShift() {
- mTransformParams.setProgress(mCurrentShift.value);
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.setWindowThresholdCrossed(!mInQuickSwitchMode
- && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
- }
-
- if (!mInQuickSwitchMode && !mDeviceState.isFullyGesturalNavMode()) {
- updateOverviewThresholdPassed(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW);
- }
-
- if (mRecentsAnimationTargets != null) {
- applyTransformUnchecked();
- }
- }
-
- @Override
- public void onGestureCancelled() {
- updateDisplacement(0);
- mGestureState.setEndTarget(LAST_TASK);
- mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED);
- }
-
- @Override
- public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
- mEndVelocityPxPerMs.set(0, velocity.y / 1000);
- if (mInQuickSwitchMode) {
- // For now set it to non-null, it will be reset before starting the animation
- mGestureState.setEndTarget(LAST_TASK);
- } else {
- float flingThreshold = mContext.getResources()
- .getDimension(R.dimen.quickstep_fling_threshold_velocity);
- boolean isFling = Math.abs(endVelocity) > flingThreshold;
-
- if (mDeviceState.isFullyGesturalNavMode()) {
- if (isFling) {
- mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK);
- } else if (mOverviewThresholdPassed) {
- mGestureState.setEndTarget(RECENTS);
- } else {
- mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
- ? HOME
- : LAST_TASK);
- }
- } else {
- GestureEndTarget startState = mSwipeUpOverHome ? HOME : LAST_TASK;
- if (isFling) {
- mGestureState.setEndTarget(endVelocity < 0 ? RECENTS : startState);
- } else {
- mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
- ? RECENTS
- : startState);
- }
- }
- }
- mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
- }
-
- @Override
- public void onConsumerAboutToBeSwitched() {
- if (mInQuickSwitchMode && mGestureState.getEndTarget() != null) {
- mGestureState.setEndTarget(NEW_TASK);
-
- mCanceled = true;
- mCurrentShift.cancelAnimation();
- if (mFinishAnimation != null) {
- mFinishAnimation.cancel();
- }
-
- if (mRecentsView != null) {
- if (mFinishingRecentsAnimationForNewTaskId != -1) {
- TaskView newRunningTaskView = mRecentsView.getTaskView(
- mFinishingRecentsAnimationForNewTaskId);
- int newRunningTaskId = newRunningTaskView != null
- ? newRunningTaskView.getTask().key.id
- : -1;
- mRecentsView.setCurrentTask(newRunningTaskId);
- mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId);
- }
- mRecentsView.setOnScrollChangeListener(null);
- }
- } else {
- mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
- }
- }
-
- private void onHandlerInvalidated() {
- mActivityInitListener.unregister();
- if (mGestureEndCallback != null) {
- mGestureEndCallback.run();
- }
- if (mFinishAnimation != null) {
- mFinishAnimation.end();
- }
- }
-
- private void onHandlerInvalidatedWithRecents() {
- mRecentsView.onGestureAnimationEnd();
- mRecentsView.setDisallowScrollToClearAll(false);
- mRecentsView.getClearAllButton().setVisibilityAlpha(1);
- }
-
- private void finishAnimationTargetSetAnimationComplete() {
- switch (mGestureState.getEndTarget()) {
- case HOME: {
- if (mSwipeUpOverHome) {
- mRecentsAnimationController.finish(false, null, false);
- // Send a home intent to clear the task stack
- mContext.startActivity(mGestureState.getHomeIntent());
- } else {
- // Notify swipe-to-home (recents animation) is finished
- SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
- mRecentsAnimationController.finish(true, () -> {
- if (!mTouchedHomeDuringTransition) {
- // If the user hasn't interacted with the screen during the transition,
- // send a home intent so launcher can go to the default home screen.
- // (If they are trying to touch something, we don't want to interfere.)
- mContext.startActivity(mGestureState.getHomeIntent());
- }
- }, true);
- }
- break;
- }
- case LAST_TASK:
- mRecentsAnimationController.finish(false, null, false);
- break;
- case RECENTS: {
- if (mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
- mRecentsAnimationController.finish(true, null, true);
- break;
- }
-
- final int runningTaskId = mGestureState.getRunningTaskId();
- ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(runningTaskId);
- mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
- false /* screenshot */);
-
- ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
- ActivityOptionsCompat.setFreezeRecentTasksList(options);
-
- Bundle extras = new Bundle();
- extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
- extras.putInt(EXTRA_TASK_ID, runningTaskId);
-
- Intent intent = new Intent(mGestureState.getOverviewIntent())
- .putExtras(extras);
- mContext.startActivity(intent, options.toBundle());
- mRecentsAnimationController.cleanupScreenshot();
- break;
- }
- case NEW_TASK: {
- startNewTask(STATE_HANDLER_INVALIDATED, b -> {});
- break;
- }
- }
-
- mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
- }
-
- private void finishAnimationTargetSet() {
- if (mInQuickSwitchMode) {
- // Recalculate the end target, some views might have been initialized after
- // gesture has ended.
- if (mRecentsView == null || !hasTargets()) {
- mGestureState.setEndTarget(LAST_TASK);
- } else {
- final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
- final int taskToLaunch = mRecentsView.getNextPage();
- mGestureState.setEndTarget(
- (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
- ? NEW_TASK
- : LAST_TASK);
- }
- }
-
- EndTargetAnimationParams params = mEndTargetAnimationParams.get(mGestureState.getEndTarget());
- float endProgress = params.mEndProgress;
- long duration = (long) (params.mDurationMultiplier *
- Math.abs(endProgress - mCurrentShift.value));
- if (mRecentsView != null) {
- duration = Math.max(duration, mRecentsView.getScroller().getDuration());
- }
- if (mCurrentShift.value != endProgress || mInQuickSwitchMode) {
- AnimationSuccessListener endListener = new AnimationSuccessListener() {
-
- @Override
- public void onAnimationSuccess(Animator animator) {
- if (mRecentsView != null) {
- mRecentsView.setOnPageTransitionEndCallback(FallbackSwipeHandler.this
- ::finishAnimationTargetSetAnimationComplete);
- } else {
- finishAnimationTargetSetAnimationComplete();
- }
- mFinishAnimation = null;
- }
- };
-
- if (mGestureState.getEndTarget() == HOME && !mRunningOverHome) {
- mRecentsAnimationController.enableInputProxy(mInputConsumer,
- this::createNewInputProxyHandler);
- RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
- anim.addAnimatorListener(endListener);
- anim.start(mContext, mEndVelocityPxPerMs);
- mFinishAnimation = RunningWindowAnim.wrap(anim);
- } else {
-
- AnimatorSet anim = new AnimatorSet();
- anim.play(mLauncherAlpha.animateToValue(
- mLauncherAlpha.value, params.mLauncherAlpha));
- anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
-
- anim.setDuration(duration);
- anim.addListener(endListener);
- anim.start();
- mFinishAnimation = RunningWindowAnim.wrap(anim);
- }
-
- } else {
- finishAnimationTargetSetAnimationComplete();
- }
- }
-
- @Override
- public void onRecentsAnimationStart(RecentsAnimationController controller,
- RecentsAnimationTargets targets) {
- super.onRecentsAnimationStart(controller, targets);
- mRecentsAnimationController.enableInputConsumer();
-
if (mRunningOverHome) {
- mAppWindowAnimationHelper.prepareAnimation(mDp, true);
+ mTransformParams.setHomeBuilderProxy(this::updateHomeActivityTransformDuringSwipeUp);
}
- applyTransformUnchecked();
-
- mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
}
@Override
- public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
- mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
-
- // Defer clearing the controller and the targets until after we've updated the state
- super.onRecentsAnimationCanceled(thumbnailData);
+ protected void initTransitionEndpoints(DeviceProfile dp) {
+ super.initTransitionEndpoints(dp);
+ if (mRunningOverHome) {
+ mMaxLauncherScale = 1 / mTaskViewSimulator.getFullScreenScale();
+ }
}
- /**
- * Creates an animation that transforms the current app window into the home app.
- * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
- */
- private RectFSpringAnim createWindowAnimationToHome(float startProgress, long duration) {
- HomeAnimationFactory factory = new HomeAnimationFactory() {
- @Override
- public RectF getWindowTargetRect() {
- PagedOrientationHandler orientationHandler = mRecentsView != null
- ? mRecentsView.getPagedOrientationHandler()
- : (mDp.isLandscape
- ? new LandscapePagedViewHandler()
- : new PortraitPagedViewHandler());
- return HomeAnimationFactory
- .getDefaultWindowTargetRect(orientationHandler, mDp);
- }
+ private void updateHomeActivityTransformDuringSwipeUp(SurfaceParams.Builder builder,
+ RemoteAnimationTargetCompat app, TransformParams params) {
+ setHomeScaleAndAlpha(builder, app, mCurrentShift.value,
+ Utilities.boundToRange(1 - mCurrentShift.value, 0, 1));
+ }
- @Override
- public AnimatorPlaybackController createActivityAnimationToHome() {
- AnimatorSet anim = new AnimatorSet();
- Animator fadeInLauncher = mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1);
- fadeInLauncher.setInterpolator(ACCEL_2);
- anim.play(fadeInLauncher);
- anim.setDuration(duration);
- return AnimatorPlaybackController.wrap(anim, duration);
- }
- };
- return createWindowAnimationToHome(startProgress, factory);
+ private void setHomeScaleAndAlpha(SurfaceParams.Builder builder,
+ RemoteAnimationTargetCompat app, float verticalShift, float alpha) {
+ float scale = Utilities.mapRange(verticalShift, 1, mMaxLauncherScale);
+ mTmpMatrix.setScale(scale, scale,
+ app.localBounds.exactCenterX(), app.localBounds.exactCenterY());
+ builder.withMatrix(mTmpMatrix).withAlpha(alpha);
}
@Override
- protected float getWindowAlpha(float progress) {
- return 1 - ACCEL_1_5.getInterpolation(progress);
+ protected HomeAnimationFactory createHomeAnimationFactory(long duration) {
+ mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+ mContext.startActivity(new Intent(mGestureState.getHomeIntent()), options.toBundle());
+ return mActiveAnimationFactory;
+ }
+
+ @Override
+ protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ if (mActiveAnimationFactory != null
+ && mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) {
+ mActiveAnimationFactory = null;
+ return false;
+ }
+
+ return super.handleTaskAppeared(appearedTaskTarget);
+ }
+
+ @Override
+ protected void finishRecentsControllerToHome(Runnable callback) {
+ mRecentsAnimationController.finish(
+ false /* toRecents */, callback, true /* sendUserLeaveHint */);
+ }
+
+ @Override
+ protected void switchToScreenshot() {
+ if (mRunningOverHome) {
+ // When the current task is home, then we don't need to capture anything
+ mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ } else {
+ super.switchToScreenshot();
+ }
+ }
+
+ @Override
+ protected void notifyGestureAnimationStartToRecents() {
+ if (mRunningOverHome) {
+ mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask());
+ } else {
+ super.notifyGestureAnimationStartToRecents();
+ }
+ }
+
+ private class FallbackHomeAnimationFactory extends HomeAnimationFactory {
+
+ private final TransformParams mHomeAlphaParams = new TransformParams();
+ private final AnimatedFloat mHomeAlpha;
+
+ private final AnimatedFloat mVerticalShiftForScale = new AnimatedFloat();
+
+ private final AnimatedFloat mRecentsAlpha = new AnimatedFloat();
+
+ private final long mDuration;
+ FallbackHomeAnimationFactory(long duration) {
+ super(null);
+ mDuration = duration;
+
+ if (mRunningOverHome) {
+ mHomeAlpha = new AnimatedFloat();
+ mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1);
+ mVerticalShiftForScale.value = mCurrentShift.value;
+ mTransformParams.setHomeBuilderProxy(
+ this::updateHomeActivityTransformDuringHomeAnim);
+ } else {
+ mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
+ mHomeAlpha.value = 0;
+
+ mHomeAlphaParams.setHomeBuilderProxy(
+ this::updateHomeActivityTransformDuringHomeAnim);
+ }
+
+ mRecentsAlpha.value = 1;
+ mTransformParams.setBaseBuilderProxy(
+ this::updateRecentsActivityTransformDuringHomeAnim);
+ }
+
+ private void updateRecentsActivityTransformDuringHomeAnim(SurfaceParams.Builder builder,
+ RemoteAnimationTargetCompat app, TransformParams params) {
+ builder.withAlpha(mRecentsAlpha.value);
+ }
+
+ private void updateHomeActivityTransformDuringHomeAnim(SurfaceParams.Builder builder,
+ RemoteAnimationTargetCompat app, TransformParams params) {
+ setHomeScaleAndAlpha(builder, app, mVerticalShiftForScale.value, mHomeAlpha.value);
+ }
+
+ @NonNull
+ @Override
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ PendingAnimation pa = new PendingAnimation(mDuration);
+ pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCEL);
+ return pa.createPlaybackController();
+ }
+
+ private void updateHomeAlpha() {
+ if (mHomeAlphaParams.getTargetSet() != null) {
+ mHomeAlphaParams.applySurfaceParams(
+ mHomeAlphaParams.createSurfaceParams(BuilderProxy.NO_OP));
+ }
+ }
+
+ public boolean handleHomeTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ if (appearedTaskTarget.activityType == ACTIVITY_TYPE_HOME) {
+ RemoteAnimationTargets targets = new RemoteAnimationTargets(
+ new RemoteAnimationTargetCompat[] {appearedTaskTarget},
+ new RemoteAnimationTargetCompat[0], appearedTaskTarget.mode);
+ mHomeAlphaParams.setTargetSet(targets);
+ updateHomeAlpha();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void playAtomicAnimation(float velocity) {
+ ObjectAnimator alphaAnim = mHomeAlpha.animateToValue(mHomeAlpha.value, 1);
+ alphaAnim.setDuration(mDuration).setInterpolator(ACCEL);
+ alphaAnim.start();
+
+ if (mRunningOverHome) {
+ // Spring back launcher scale
+ new SpringAnimationBuilder(mContext)
+ .setStartValue(mVerticalShiftForScale.value)
+ .setEndValue(0)
+ .setStartVelocity(-velocity / mTransitionDragLength)
+ .setMinimumVisibleChange(1f / mDp.heightPx)
+ .setDampingRatio(0.6f)
+ .setStiffness(800)
+ .build(mVerticalShiftForScale, AnimatedFloat.VALUE)
+ .start();
+ }
+ }
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
index 33fe5a9..ba8ba33 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
@@ -17,6 +17,7 @@
package com.android.quickstep;
import static android.content.Intent.EXTRA_STREAM;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.ImageActionUtils.persistBitmapAndStartActivity;
@@ -33,6 +34,7 @@
import com.android.launcher3.BuildConfig;
import com.android.quickstep.util.ImageActionUtils;
+import com.android.systemui.shared.recents.model.Task;
import java.util.function.Supplier;
@@ -42,9 +44,10 @@
public class ImageActionsApi {
private static final String TAG = BuildConfig.APPLICATION_ID + "ImageActionsApi";
- private final Context mContext;
- private final Supplier<Bitmap> mBitmapSupplier;
- private final SystemUiProxy mSystemUiProxy;
+
+ protected final Context mContext;
+ protected final Supplier<Bitmap> mBitmapSupplier;
+ protected final SystemUiProxy mSystemUiProxy;
public ImageActionsApi(Context context, Supplier<Bitmap> bitmapSupplier) {
mContext = context;
@@ -65,7 +68,9 @@
UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext,
mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> {
- intentForUri.putExtra(EXTRA_STREAM, uri);
+ intentForUri
+ .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
+ .putExtra(EXTRA_STREAM, uri);
return new Intent[]{intentForUri};
}, TAG));
@@ -84,11 +89,11 @@
* @param screenshotBounds the location of where the bitmap was laid out on the screen in
* screen coordinates.
* @param visibleInsets that are used to draw the screenshot within the bounds.
- * @param taskId of the task that the screenshot was taken of.
+ * @param task of the task that the screenshot was taken of.
*/
public void saveScreenshot(Bitmap screenshot, Rect screenshotBounds,
- Insets visibleInsets, int taskId) {
+ Insets visibleInsets, Task.TaskKey task) {
ImageActionUtils.saveScreenshot(mSystemUiProxy, screenshot, screenshotBounds, visibleInsets,
- taskId);
+ task);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 455ae76..edefbe1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -15,35 +15,20 @@
*/
package com.android.quickstep;
-import static android.view.View.TRANSLATION_Y;
-
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_RECENTS_FADE_ANIM;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_RECENTS_TRANSLATE_X_ANIM;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-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.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.LauncherSwipeHandler.RECENTS_ATTACH_DURATION;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_SHELF_ANIM;
+import static com.android.quickstep.SysUINavigationMode.getMode;
+import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
+import static com.android.quickstep.util.LayoutUtils.getDefaultSwipeHeight;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.UserHandle;
-import android.util.Pair;
-import android.view.MotionEvent;
-import android.view.View;
+import android.util.Log;
import android.view.animation.Interpolator;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -52,25 +37,23 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
-import com.android.quickstep.util.StaggeredWorkspaceAnim;
-import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
import com.android.systemui.plugins.shared.LauncherOverlayManager;
-import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.util.function.Consumer;
@@ -79,52 +62,41 @@
/**
* {@link BaseActivityInterface} for the in-launcher recents.
*/
-public final class LauncherActivityInterface implements BaseActivityInterface<Launcher> {
+public final class LauncherActivityInterface extends
+ BaseActivityInterface<LauncherState, BaseQuickstepLauncher> {
- private Runnable mAdjustInterpolatorsRunnable;
- private Pair<Float, Float> mSwipeUpPullbackStartAndMaxProgress =
- BaseActivityInterface.super.getSwipeUpPullbackStartAndMaxProgress();
+ public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface();
+
+ private LauncherActivityInterface() {
+ super(true, OVERVIEW, BACKGROUND_APP);
+ }
@Override
- public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
- LayoutUtils.calculateLauncherTaskSize(context, dp, outRect);
+ public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
+ PagedOrientationHandler orientationHandler) {
+ calculateTaskSize(context, dp, outRect, orientationHandler);
if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) {
Rect targetInsets = dp.getInsets();
int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
return dp.hotseatBarSizePx + hotseatInset;
} else {
- return LayoutUtils.getShelfTrackingDistance(context, dp);
+ return LayoutUtils.getShelfTrackingDistance(context, dp, orientationHandler);
}
}
@Override
- public Pair<Float, Float> getSwipeUpPullbackStartAndMaxProgress() {
- return mSwipeUpPullbackStartAndMaxProgress;
- }
-
- @Override
- public void onTransitionCancelled(boolean activityVisible) {
- Launcher launcher = getCreatedActivity();
- if (launcher == null) {
- return;
- }
- LauncherState startState = launcher.getStateManager().getRestState();
- launcher.getStateManager().goToState(startState, activityVisible);
- }
-
- @Override
public void onSwipeUpToRecentsComplete() {
- // Re apply state in case we did something funky during the transition.
+ super.onSwipeUpToRecentsComplete();
Launcher launcher = getCreatedActivity();
- if (launcher == null) {
- return;
+ if (launcher != null) {
+ RecentsView recentsView = launcher.getOverviewPanel();
+ DiscoveryBounce.showForOverviewIfNeeded(launcher,
+ recentsView.getPagedOrientationHandler());
}
- launcher.getStateManager().reapplyState();
- DiscoveryBounce.showForOverviewIfNeeded(launcher);
}
@Override
- public void onSwipeUpToHomeComplete() {
+ public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
@@ -132,15 +104,8 @@
// Ensure recents is at the correct position for NORMAL state. For example, when we detach
// recents, we assume the first task is invisible, making translation off by one task.
launcher.getStateManager().reapplyState();
- setLauncherHideBackArrow(false);
- }
-
- private void setLauncherHideBackArrow(boolean hideBackArrow) {
- Launcher launcher = getCreatedActivity();
- if (launcher == null) {
- return;
- }
- launcher.getRootView().setForceHideBackArrow(hideBackArrow);
+ launcher.getRootView().setForceHideBackArrow(false);
+ notifyRecentsOfOrientation(deviceState);
}
@Override
@@ -152,266 +117,47 @@
launcher.onAssistantVisibilityChanged(visibility);
}
- @NonNull
@Override
- public HomeAnimationFactory prepareHomeUI() {
- Launcher launcher = getCreatedActivity();
- final DeviceProfile dp = launcher.getDeviceProfile();
- final RecentsView recentsView = launcher.getOverviewPanel();
- final TaskView runningTaskView = recentsView.getRunningTaskView();
- final View workspaceView;
- if (runningTaskView != null && runningTaskView.getTask().key.getComponent() != null) {
- workspaceView = launcher.getWorkspace().getFirstMatchForAppClose(
- runningTaskView.getTask().key.getComponent().getPackageName(),
- UserHandle.of(runningTaskView.getTask().key.userId));
- } else {
- workspaceView = null;
- }
- final RectF iconLocation = new RectF();
- boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
- FloatingIconView floatingIconView = canUseWorkspaceView
- ? FloatingIconView.getFloatingIconView(launcher, workspaceView,
- true /* hideOriginal */, iconLocation, false /* isOpening */)
- : null;
- setLauncherHideBackArrow(true);
- return new HomeAnimationFactory() {
- @Nullable
- @Override
- public View getFloatingView() {
- return floatingIconView;
- }
-
- @NonNull
- @Override
- public RectF getWindowTargetRect() {
- if (canUseWorkspaceView) {
- return iconLocation;
- } else {
- return HomeAnimationFactory
- .getDefaultWindowTargetRect(recentsView.getPagedOrientationHandler(), dp);
- }
- }
-
- @NonNull
- @Override
- public AnimatorPlaybackController createActivityAnimationToHome() {
- // Return an empty APC here since we have an non-user controlled animation to home.
- long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
- return launcher.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy,
- 0 /* animComponents */);
- }
-
- @Override
- public void playAtomicAnimation(float velocity) {
- new StaggeredWorkspaceAnim(launcher, velocity, true /* animateOverviewScrim */)
- .start();
- }
- };
- }
-
- @Override
- public AnimationFactory prepareRecentsUI(boolean activityVisible,
- boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
- BaseQuickstepLauncher launcher = getCreatedActivity();
- final LauncherState startState = launcher.getStateManager().getState();
-
- LauncherState resetState = startState;
- if (startState.disableRestore) {
- resetState = launcher.getStateManager().getRestState();
- }
- launcher.getStateManager().setRestState(resetState);
-
- final LauncherState fromState = animateActivity ? BACKGROUND_APP : OVERVIEW;
- launcher.getStateManager().goToState(fromState, false);
- // Since all apps is not visible, we can safely reset the scroll position.
- // This ensures then the next swipe up to all-apps starts from scroll 0.
- launcher.getAppsView().reset(false /* animate */);
-
- return new AnimationFactory() {
- private final ShelfPeekAnim mShelfAnim = launcher.getShelfPeekAnim();
- private boolean mIsAttachedToWindow;
-
- @Override
- public void createActivityInterface(long transitionLength) {
- createActivityInterfaceInternal(launcher, fromState, transitionLength, callback);
- // Creating the activity controller animation sometimes reapplies the launcher state
- // (because we set the animation as the current state animation), so we reapply the
- // attached state here as well to ensure recents is shown/hidden appropriately.
- if (SysUINavigationMode.getMode(launcher) == Mode.NO_BUTTON) {
- setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
- }
- }
-
- @Override
- public void adjustActivityControllerInterpolators() {
- if (mAdjustInterpolatorsRunnable != null) {
- mAdjustInterpolatorsRunnable.run();
- mAdjustInterpolatorsRunnable = null;
- }
- }
-
- @Override
- public void onTransitionCancelled() {
- launcher.getStateManager().goToState(startState, false /* animate */);
- }
-
+ public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
+ boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
+ notifyRecentsOfOrientation(deviceState);
+ DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) {
@Override
public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
long duration) {
- mShelfAnim.setShelfState(shelfState, interpolator, duration);
+ mActivity.getShelfPeekAnim().setShelfState(shelfState, interpolator, duration);
}
@Override
- public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
- if (mIsAttachedToWindow == attached && animate) {
- return;
+ protected void createBackgroundToOverviewAnim(BaseQuickstepLauncher activity,
+ PendingAnimation pa) {
+ super.createBackgroundToOverviewAnim(activity, pa);
+
+ if (!activity.getDeviceProfile().isVerticalBarLayout()
+ && SysUINavigationMode.getMode(activity) != Mode.NO_BUTTON) {
+ // Don't animate the shelf when the mode is NO_BUTTON, because we
+ // update it atomically.
+ pa.add(activity.getStateManager().createStateElementAnimation(
+ INDEX_SHELF_ANIM,
+ BACKGROUND_APP.getVerticalProgress(activity),
+ OVERVIEW.getVerticalProgress(activity)));
}
- mIsAttachedToWindow = attached;
- LauncherRecentsView recentsView = launcher.getOverviewPanel();
- Animator fadeAnim = launcher.getStateManager()
- .createStateElementAnimation(
- INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
- int runningTaskIndex = recentsView.getRunningTaskIndex();
- if (runningTaskIndex == recentsView.getTaskViewStartIndex()) {
- // If we are on the first task (we haven't quick switched), translate recents in
- // from the side. Calculate the start translation based on current scale/scroll.
- float currScale = recentsView.getScaleX();
- float scrollOffsetX = recentsView.getScrollOffset();
- float offscreenX = recentsView.getOffscreenTranslationX(currScale);
+ // Animate the blur and wallpaper zoom
+ float fromDepthRatio = BACKGROUND_APP.getDepth(activity);
+ float toDepthRatio = OVERVIEW.getDepth(activity);
+ pa.addFloat(getDepthController(),
+ new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
+ fromDepthRatio, toDepthRatio, LINEAR);
- float fromTranslation = attached ? offscreenX - scrollOffsetX : 0;
- float toTranslation = attached ? 0 : offscreenX - scrollOffsetX;
- launcher.getStateManager()
- .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
-
- PagedOrientationHandler pagedOrientationHandler =
- recentsView.getPagedViewOrientedState().getOrientationHandler();
- if (!recentsView.isShown() && animate) {
- pagedOrientationHandler
- .getPrimaryViewTranslate().set(recentsView, fromTranslation);
- } else {
- fromTranslation =
- pagedOrientationHandler.getPrimaryViewTranslate().get(recentsView);
- }
-
- if (!animate) {
- pagedOrientationHandler
- .getPrimaryViewTranslate().set(recentsView, toTranslation);
- } else {
- launcher.getStateManager().createStateElementAnimation(
- INDEX_RECENTS_TRANSLATE_X_ANIM,
- fromTranslation, toTranslation).start();
- }
-
- fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
- } else {
- fadeAnim.setInterpolator(ACCEL_DEACCEL);
- }
- fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start();
}
};
- }
- private void createActivityInterfaceInternal(Launcher activity, LauncherState fromState,
- long transitionLength, Consumer<AnimatorPlaybackController> callback) {
- LauncherState endState = OVERVIEW;
- if (fromState == endState) {
- return;
- }
-
- AnimatorSet anim = new AnimatorSet();
- if (!activity.getDeviceProfile().isVerticalBarLayout()
- && SysUINavigationMode.getMode(activity) != Mode.NO_BUTTON) {
- // Don't animate the shelf when the mode is NO_BUTTON, because we update it atomically.
- anim.play(activity.getStateManager().createStateElementAnimation(
- INDEX_SHELF_ANIM,
- fromState.getVerticalProgress(activity),
- endState.getVerticalProgress(activity)));
- }
-
- // Animate the blur and wallpaper zoom
- DepthController depthController = getDepthController();
- float fromDepthRatio = fromState.getDepth(activity);
- float toDepthRatio = endState.getDepth(activity);
- Animator depthAnimator = ObjectAnimator.ofFloat(depthController,
- new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
- fromDepthRatio, toDepthRatio);
- anim.play(depthAnimator);
-
- playScaleDownAnim(anim, activity, fromState, endState);
-
- anim.setDuration(transitionLength * 2);
- anim.setInterpolator(LINEAR);
- AnimatorPlaybackController controller =
- AnimatorPlaybackController.wrap(anim, transitionLength * 2);
- activity.getStateManager().setCurrentUserControlledAnimation(controller);
-
- // Since we are changing the start position of the UI, reapply the state, at the end
- controller.setEndAction(() -> {
- activity.getStateManager().goToState(
- controller.getInterpolatedProgress() > 0.5 ? endState : fromState, false);
- });
- callback.accept(controller);
- }
-
- /**
- * Scale down recents from the center task being full screen to being in overview.
- */
- private void playScaleDownAnim(AnimatorSet anim, Launcher launcher, LauncherState fromState,
- LauncherState endState) {
- RecentsView recentsView = launcher.getOverviewPanel();
- if (recentsView.getCurrentPageTaskView() == null) {
- return;
- }
-
- LauncherState.ScaleAndTranslation fromScaleAndTranslation
- = fromState.getOverviewScaleAndTranslation(launcher);
- LauncherState.ScaleAndTranslation endScaleAndTranslation
- = endState.getOverviewScaleAndTranslation(launcher);
- float fromTranslationY = fromScaleAndTranslation.translationY;
- float endTranslationY = endScaleAndTranslation.translationY;
- float fromFullscreenProgress = fromState.getOverviewFullscreenProgress();
- float endFullscreenProgress = endState.getOverviewFullscreenProgress();
-
- Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
- fromScaleAndTranslation.scale, endScaleAndTranslation.scale);
- Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
- fromTranslationY, endTranslationY);
- Animator applyFullscreenProgress = ObjectAnimator.ofFloat(recentsView,
- RecentsView.FULLSCREEN_PROGRESS, fromFullscreenProgress, endFullscreenProgress);
- anim.playTogether(scale, translateY, applyFullscreenProgress);
-
- mAdjustInterpolatorsRunnable = () -> {
- // Adjust the translateY interpolator to account for the running task's top inset.
- // When progress <= 1, this is handled by each task view as they set their fullscreen
- // progress. However, once we go to progress > 1, fullscreen progress stays at 0, so
- // recents as a whole needs to translate further to keep up with the app window.
- TaskView runningTaskView = recentsView.getRunningTaskView();
- if (runningTaskView == null) {
- runningTaskView = recentsView.getCurrentPageTaskView();
- if (runningTaskView == null) {
- // There are no task views in LockTask mode when Overview is enabled.
- return;
- }
- }
- TimeInterpolator oldInterpolator = translateY.getInterpolator();
- Rect fallbackInsets = launcher.getDeviceProfile().getInsets();
- float extraTranslationY = runningTaskView.getThumbnail().getInsets(fallbackInsets).top;
- float normalizedTranslationY = extraTranslationY / (fromTranslationY - endTranslationY);
- translateY.setInterpolator(t -> {
- float newT = oldInterpolator.getInterpolation(t);
- return newT <= 1f ? newT : newT + normalizedTranslationY * (newT - 1);
- });
- };
-
- // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f.
- float pullbackStartProgress = (0.75f - fromScaleAndTranslation.scale)
- / (endScaleAndTranslation.scale - fromScaleAndTranslation.scale);
- float pullbackMaxProgress = (0.5f - fromScaleAndTranslation.scale)
- / (endScaleAndTranslation.scale - fromScaleAndTranslation.scale);
- mSwipeUpPullbackStartAndMaxProgress = new Pair<>(
- pullbackStartProgress, pullbackMaxProgress);
+ BaseQuickstepLauncher launcher = factory.initUI();
+ // Since all apps is not visible, we can safely reset the scroll position.
+ // This ensures then the next swipe up to all-apps starts from scroll 0.
+ launcher.getAppsView().reset(false /* animate */);
+ return factory;
}
@Override
@@ -420,6 +166,15 @@
onInitListener.test(alreadyOnHome));
}
+ @Override
+ public void setOnDeferredActivityLaunchCallback(Runnable r) {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
+ launcher.setOnDeferredActivityLaunchCallback(r);
+ }
+
@Nullable
@Override
public BaseQuickstepLauncher getCreatedActivity() {
@@ -427,11 +182,13 @@
}
@Nullable
- @UiThread
- private Launcher getVisibleLauncher() {
- Launcher launcher = getCreatedActivity();
- return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
- launcher : null;
+ @Override
+ public DepthController getDepthController() {
+ BaseQuickstepLauncher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return null;
+ }
+ return launcher.getDepthController();
}
@Nullable
@@ -442,8 +199,19 @@
? launcher.getOverviewPanel() : null;
}
+ @Nullable
+ @UiThread
+ private Launcher getVisibleLauncher() {
+ Launcher launcher = getCreatedActivity();
+ return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus()
+ ? launcher : null;
+ }
+
@Override
public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "switchToRecentsIfVisible");
+ }
Launcher launcher = getVisibleLauncher();
if (launcher == null) {
return false;
@@ -458,9 +226,29 @@
return true;
}
+
@Override
- public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
- return deviceState.isInDeferredGestureRegion(ev);
+ public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) {
+ final StateManager<LauncherState> stateManager = getCreatedActivity().getStateManager();
+ stateManager.addStateListener(
+ new StateManager.StateListener<LauncherState>() {
+ @Override
+ public void onStateTransitionComplete(LauncherState toState) {
+ // Are we going from Recents to Workspace?
+ if (toState == LauncherState.NORMAL) {
+ exitRunnable.run();
+ notifyRecentsOfOrientation(deviceState);
+ stateManager.removeStateListener(this);
+ }
+ }
+ });
+ }
+
+ private void notifyRecentsOfOrientation(RecentsAnimationDeviceState deviceState) {
+ // reset layout on swipe to home
+ RecentsView recentsView = getCreatedActivity().getOverviewPanel();
+ recentsView.setLayoutRotation(deviceState.getCurrentActiveRotation(),
+ deviceState.getDisplayRotation());
}
@Override
@@ -469,11 +257,21 @@
}
@Override
- public boolean shouldMinimizeSplitScreen() {
+ public boolean allowMinimizeSplitScreen() {
return true;
}
@Override
+ public void updateOverviewPredictionState() {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
+ PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
+ PredictionUiStateManager.Client.OVERVIEW);
+ }
+
+ @Override
public int getContainerType() {
final Launcher launcher = getVisibleLauncher();
return launcher != null ? launcher.getStateManager().getState().containerType
@@ -497,15 +295,6 @@
}
@Override
- public void onLaunchTaskSuccess() {
- Launcher launcher = getCreatedActivity();
- if (launcher == null) {
- return;
- }
- launcher.getStateManager().moveToRestState();
- }
-
- @Override
public void closeOverlay() {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
@@ -520,48 +309,37 @@
}
@Override
- public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData,
- Runnable onFinishRunnable) {
- Launcher launcher = getCreatedActivity();
- if (launcher == null) {
- return;
- }
- RecentsView recentsView = launcher.getOverviewPanel();
- if (recentsView == null) {
- if (onFinishRunnable != null) {
- onFinishRunnable.run();
+ protected float getExtraSpace(Context context, DeviceProfile dp,
+ PagedOrientationHandler orientationHandler) {
+ if ((dp.isVerticalBarLayout() && !showOverviewActions(context))
+ || hideShelfInTwoButtonLandscape(context, orientationHandler)) {
+ return 0;
+ } else {
+ Resources res = context.getResources();
+ if (showOverviewActions(context)) {
+ //TODO: this needs to account for the swipe gesture height and accessibility
+ // UI when shown.
+ float actionsBottomMargin = 0;
+ if (!dp.isVerticalBarLayout()) {
+ if (getMode(context) == Mode.THREE_BUTTONS) {
+ actionsBottomMargin = res.getDimensionPixelSize(
+ R.dimen.overview_actions_bottom_margin_three_button);
+ } else {
+ actionsBottomMargin = res.getDimensionPixelSize(
+ R.dimen.overview_actions_bottom_margin_gesture);
+ }
+ }
+ float actionsHeight = actionsBottomMargin
+ + res.getDimensionPixelSize(R.dimen.overview_actions_height);
+ return actionsHeight;
+ } else {
+ return getDefaultSwipeHeight(context, dp) + dp.workspacePageIndicatorHeight
+ + res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+ + res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_hotseat_bottom_padding);
}
- return;
}
- recentsView.switchToScreenshot(thumbnailData, onFinishRunnable);
}
- @Override
- public void setOnDeferredActivityLaunchCallback(Runnable r) {
- Launcher launcher = getCreatedActivity();
- if (launcher == null) {
- return;
- }
- launcher.setOnDeferredActivityLaunchCallback(r);
- }
-
- @Override
- public void updateOverviewPredictionState() {
- Launcher launcher = getCreatedActivity();
- if (launcher == null) {
- return;
- }
- PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
- PredictionUiStateManager.Client.OVERVIEW);
- }
-
- @Nullable
- @Override
- public DepthController getDepthController() {
- BaseQuickstepLauncher launcher = getCreatedActivity();
- if (launcher == null) {
- return null;
- }
- return launcher.getDepthController();
- }
}
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
new file mode 100644
index 0000000..fa7d268
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -0,0 +1,121 @@
+/*
+ * 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.LauncherState.NORMAL;
+
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.graphics.RectF;
+import android.os.UserHandle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.views.FloatingIconView;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.InputConsumerController;
+
+/**
+ * Temporary class to allow easier refactoring
+ */
+public class LauncherSwipeHandlerV2 extends
+ BaseSwipeUpHandlerV2<BaseQuickstepLauncher, RecentsView> {
+
+ public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
+ TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
+ boolean continuingLastGesture, InputConsumerController inputConsumer) {
+ super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
+ continuingLastGesture, inputConsumer);
+ }
+
+
+ @Override
+ protected HomeAnimationFactory createHomeAnimationFactory(long duration) {
+ HomeAnimationFactory homeAnimFactory;
+ if (mActivity != null) {
+ final TaskView runningTaskView = mRecentsView.getRunningTaskView();
+ final View workspaceView;
+ if (runningTaskView != null
+ && runningTaskView.getTask().key.getComponent() != null) {
+ workspaceView = mActivity.getWorkspace().getFirstMatchForAppClose(
+ runningTaskView.getTask().key.getComponent().getPackageName(),
+ UserHandle.of(runningTaskView.getTask().key.userId));
+ } else {
+ workspaceView = null;
+ }
+ final RectF iconLocation = new RectF();
+ boolean canUseWorkspaceView =
+ workspaceView != null && workspaceView.isAttachedToWindow();
+ FloatingIconView floatingIconView = canUseWorkspaceView
+ ? FloatingIconView.getFloatingIconView(mActivity, workspaceView,
+ true /* hideOriginal */, iconLocation, false /* isOpening */)
+ : null;
+
+ mActivity.getRootView().setForceHideBackArrow(true);
+ mActivity.setHintUserWillBeActive();
+
+ homeAnimFactory = new HomeAnimationFactory(floatingIconView) {
+
+ @Override
+ public RectF getWindowTargetRect() {
+ if (canUseWorkspaceView) {
+ return iconLocation;
+ } else {
+ return super.getWindowTargetRect();
+ }
+ }
+
+ @NonNull
+ @Override
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ // Return an empty APC here since we have an non-user controlled animation
+ // to home.
+ long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
+ return mActivity.getStateManager().createAnimationToNewWorkspace(
+ NORMAL, accuracy, 0 /* animComponents */);
+ }
+
+ @Override
+ public void playAtomicAnimation(float velocity) {
+ new StaggeredWorkspaceAnim(mActivity, velocity,
+ true /* animateOverviewScrim */).start();
+ }
+ };
+
+ } else {
+ homeAnimFactory = new HomeAnimationFactory(null) {
+ @Override
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
+ }
+ };
+ mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
+ isPresent -> mRecentsView.startHome());
+ }
+ return homeAnimFactory;
+ }
+
+ @Override
+ protected void finishRecentsControllerToHome(Runnable callback) {
+ mRecentsAnimationController.finish(
+ true /* toRecents */, callback, true /* sendUserLeaveHint */);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java
deleted file mode 100644
index 65f323c..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-/**
- * Empty activity to start a recents transition
- */
-public class LockScreenRecentsActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- finish();
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index c6b719a..434a929 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -28,9 +28,10 @@
import android.view.ViewConfiguration;
import androidx.annotation.BinderThread;
-import com.android.launcher3.BaseDraggingActivity;
+
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.RemoteAnimationProvider;
@@ -111,7 +112,7 @@
protected void onTransitionComplete() {
// TODO(b/138729100) This doesn't execute first time launcher is run
if (mTriggeredFromAltTab) {
- RecentsView rv = (RecentsView) mActivityInterface.getVisibleRecentsView();
+ RecentsView rv = mActivityInterface.getVisibleRecentsView();
if (rv == null) {
return;
}
@@ -136,7 +137,7 @@
@Override
protected boolean handleCommand(long elapsedTime) {
- RecentsView recents = (RecentsView) mActivityInterface.getVisibleRecentsView();
+ RecentsView recents = mActivityInterface.getVisibleRecentsView();
if (recents == null) {
return false;
}
@@ -150,9 +151,9 @@
}
}
- private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
+ private class RecentsActivityCommand<T extends StatefulActivity<?>> implements Runnable {
- protected final BaseActivityInterface<T> mActivityInterface;
+ protected final BaseActivityInterface<?, T> mActivityInterface;
private final long mCreateTime;
private final AppToOverviewAnimationProvider<T> mAnimationProvider;
@@ -164,7 +165,7 @@
mActivityInterface = mOverviewComponentObserver.getActivityInterface();
mCreateTime = SystemClock.elapsedRealtime();
mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
- RecentsModel.getRunningTaskId());
+ RecentsModel.getRunningTaskId(), mDeviceState);
// Preload the plan
mRecentsModel.getTasks(null);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index d4c746f..6f4d34c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -4,20 +4,18 @@
import android.content.Context;
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;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.quickstep.util.LayoutUtils;
-import com.android.systemui.shared.recents.model.Task;
-
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
public class QuickstepTestInformationHandler extends TestInformationHandler {
- private final Context mContext;
+ protected final Context mContext;
+
public QuickstepTestInformationHandler(Context context) {
mContext = context;
}
@@ -26,6 +24,15 @@
public Bundle call(String method) {
final Bundle response = new Bundle();
switch (method) {
+ case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
+ return getLauncherUIProperty(Bundle::putInt, l -> {
+ final float progress = LauncherState.OVERVIEW.getVerticalProgress(l)
+ - LauncherState.ALL_APPS.getVerticalProgress(l);
+ final float distance = l.getAllAppsController().getShiftRange() * progress;
+ return (int) distance;
+ });
+ }
+
case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
final float swipeHeight =
LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
@@ -35,7 +42,8 @@
case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
final float swipeHeight =
- LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile);
+ LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile,
+ PagedOrientationHandler.PORTRAIT);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
return response;
}
@@ -45,26 +53,6 @@
Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
}
- case TestProtocol.REQUEST_RECENT_TASKS_LIST: {
- ArrayList<String> taskBaseIntentComponents = new ArrayList<>();
- CountDownLatch latch = new CountDownLatch(1);
- RecentsModel.INSTANCE.get(mContext).getTasks((tasks) -> {
- for (Task t : tasks) {
- taskBaseIntentComponents.add(
- t.key.baseIntent.getComponent().flattenToString());
- }
- latch.countDown();
- });
- try {
- latch.await(2, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- response.putStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- taskBaseIntentComponents);
- return response;
- }
-
case TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED: {
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get());
@@ -77,12 +65,13 @@
@Override
protected Activity getCurrentActivity() {
- OverviewComponentObserver observer = new OverviewComponentObserver(mContext,
- new RecentsAnimationDeviceState(mContext));
+ RecentsAnimationDeviceState rads = new RecentsAnimationDeviceState(mContext);
+ OverviewComponentObserver observer = new OverviewComponentObserver(mContext, rads);
try {
return observer.getActivityInterface().getCreatedActivity();
} finally {
observer.onDestroy();
+ rads.destroy();
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 42d944f..6f2f9fb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -15,12 +15,15 @@
*/
package com.android.quickstep;
+import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
-import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
+import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.Animator;
@@ -29,48 +32,73 @@
import android.app.ActivityOptions;
import android.content.Intent;
import android.content.res.Configuration;
+import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.view.View;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAnimationRunner;
import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.util.ObjectWrapper;
+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.util.ActivityTracker;
+import com.android.launcher3.util.SystemUiController;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.fallback.FallbackRecentsStateController;
import com.android.quickstep.fallback.FallbackRecentsView;
-import com.android.quickstep.fallback.RecentsRootView;
-import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.quickstep.fallback.RecentsDragLayer;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.util.RecentsAtomicAnimationFactory;
+import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
/**
* A recents activity that shows the recently launched tasks as swipable task cards.
* See {@link com.android.quickstep.views.RecentsView}.
*/
-public final class RecentsActivity extends BaseRecentsActivity {
+public final class RecentsActivity extends StatefulActivity<RecentsState> {
- public static final String EXTRA_THUMBNAIL = "thumbnailData";
- public static final String EXTRA_TASK_ID = "taskID";
+ public static final ActivityTracker<RecentsActivity> ACTIVITY_TRACKER =
+ new ActivityTracker<>();
private Handler mUiHandler = new Handler(Looper.getMainLooper());
- private RecentsRootView mRecentsRootView;
- private FallbackRecentsView mFallbackRecentsView;
- @Override
- protected void initViews() {
- setContentView(R.layout.fallback_recents_activity);
- mRecentsRootView = findViewById(R.id.drag_layer);
+ private RecentsDragLayer mDragLayer;
+ private FallbackRecentsView mFallbackRecentsView;
+ private OverviewActionsView mActionsView;
+
+ private Configuration mOldConfig;
+
+ private StateManager<RecentsState> mStateManager;
+
+ /**
+ * Init drag layer and overview panel views.
+ */
+ protected void setupViews() {
+ inflateRootView(R.layout.fallback_recents_activity);
+ setContentView(getRootView());
+ mDragLayer = findViewById(R.id.drag_layer);
mFallbackRecentsView = findViewById(R.id.overview_panel);
- mRecentsRootView.recreateControllers();
+ mActionsView = findViewById(R.id.overview_actions_view);
+
+ mDragLayer.recreateControllers();
+ mFallbackRecentsView.init(mActionsView);
}
@Override
@@ -79,59 +107,45 @@
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
}
- public void onRootViewSizeChanged() {
- if (isInMultiWindowMode()) {
- onHandleConfigChanged();
- }
- }
-
@Override
protected void onNewIntent(Intent intent) {
- if (intent.getExtras() != null) {
- int taskID = intent.getIntExtra(EXTRA_TASK_ID, 0);
- IBinder thumbnail = intent.getExtras().getBinder(EXTRA_THUMBNAIL);
- if (taskID != 0 && thumbnail instanceof ObjectWrapper) {
- ObjectWrapper<ThumbnailData> obj = (ObjectWrapper<ThumbnailData>) thumbnail;
- ThumbnailData thumbnailData = obj.get();
- mFallbackRecentsView.showCurrentTask(taskID);
- mFallbackRecentsView.updateThumbnail(taskID, thumbnailData);
- // Clear the ref since any reference to the extras on the system side will still
- // hold a reference to the wrapper
- obj.clear();
- }
- }
- intent.removeExtra(EXTRA_TASK_ID);
- intent.removeExtra(EXTRA_THUMBNAIL);
super.onNewIntent(intent);
+ ACTIVITY_TRACKER.handleNewIntent(this, intent);
}
- @Override
+ /**
+ * Logic for when device configuration changes (rotation, screen size change, multi-window,
+ * etc.)
+ */
protected void onHandleConfigChanged() {
- super.onHandleConfigChanged();
- mRecentsRootView.recreateControllers();
+ mUserEventDispatcher = null;
+ initDeviceProfile();
+
+ AbstractFloatingView.closeOpenViews(this, true,
+ AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
+ dispatchDeviceProfileChanged();
+
+ reapplyUi();
+ mDragLayer.recreateControllers();
}
- @Override
- protected void reapplyUi() {
- mRecentsRootView.dispatchInsets();
- }
-
- @Override
+ /**
+ * Generate the device profile to use in this activity.
+ * @return device profile
+ */
protected DeviceProfile createDeviceProfile() {
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
- return (mRecentsRootView != null) && isInMultiWindowMode()
- ? dp.getMultiWindowProfile(this, mRecentsRootView.getLastKnownSize())
- : super.createDeviceProfile();
+
+ // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
+ // activity.
+ return (mDragLayer != null) && isInMultiWindowMode()
+ ? dp.getMultiWindowProfile(this, getMultiWindowDisplaySize())
+ : dp.copy(this);
}
@Override
public BaseDragLayer getDragLayer() {
- return mRecentsRootView;
- }
-
- @Override
- public View getRootView() {
- return mRecentsRootView;
+ return mDragLayer;
}
@Override
@@ -139,6 +153,10 @@
return (T) mFallbackRecentsView;
}
+ public OverviewActionsView getActionsView() {
+ return mActionsView;
+ }
+
@Override
public void returnToHomescreen() {
super.returnToHomescreen();
@@ -160,12 +178,7 @@
RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
wallpaperTargets);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mFallbackRecentsView.resetViewUI();
- }
- });
+ anim.addListener(resetStateListener());
result.setAnimation(anim, RecentsActivity.this);
}
};
@@ -183,25 +196,18 @@
RemoteAnimationTargetCompat[] wallpaperTargets) {
AnimatorSet target = new AnimatorSet();
boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
- AppWindowAnimationHelper helper = new AppWindowAnimationHelper(
- mFallbackRecentsView.getPagedViewOrientedState(), this);
- Animator recentsAnimator = getRecentsWindowAnimator(taskView, !activityClosing, appTargets,
- wallpaperTargets, null /* depthController */,
- helper);
- target.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
+ PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+ createRecentsWindowAnimator(taskView, !activityClosing, appTargets,
+ wallpaperTargets, null /* depthController */, pa);
+ target.play(pa.buildAnim());
// Found a visible recents task that matches the opening app, lets launch the app from there
if (activityClosing) {
Animator adjacentAnimation = mFallbackRecentsView
- .createAdjacentPageAnimForTaskLaunch(taskView, helper);
+ .createAdjacentPageAnimForTaskLaunch(taskView);
adjacentAnimation.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
adjacentAnimation.setDuration(RECENTS_LAUNCH_DURATION);
- adjacentAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mFallbackRecentsView.resetTaskVisuals();
- }
- });
+ adjacentAnimation.addListener(resetStateListener());
target.play(adjacentAnimation);
}
return target;
@@ -213,13 +219,14 @@
// onActivityStart callback.
mFallbackRecentsView.setContentAlpha(1);
super.onStart();
- mFallbackRecentsView.resetTaskVisuals();
}
@Override
protected void onStop() {
super.onStop();
- mFallbackRecentsView.reset();
+
+ // Workaround for b/78520668, explicitly trim memory once UI is hidden
+ onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
}
@Override
@@ -228,7 +235,102 @@
AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL);
}
- public void onTaskLaunched() {
- mFallbackRecentsView.resetTaskVisuals();
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mStateManager = new StateManager<>(this, RecentsState.DEFAULT);
+
+ mOldConfig = new Configuration(getResources().getConfiguration());
+ initDeviceProfile();
+ setupViews();
+
+ getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
+ Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
+ ACTIVITY_TRACKER.handleCreate(this);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ int diff = newConfig.diff(mOldConfig);
+ if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
+ onHandleConfigChanged();
+ }
+ mOldConfig.setTo(newConfig);
+ super.onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * Initialize/update the device profile.
+ */
+ private void initDeviceProfile() {
+ mDeviceProfile = createDeviceProfile();
+ onDeviceProfileInitiated();
+ }
+
+ @Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
+ // as a part of quickstep, so that high-res thumbnails can load the next time we enter
+ // overview
+ RecentsModel.INSTANCE.get(this).getThumbnailCache()
+ .getHighResLoadingState().setVisible(true);
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ RecentsModel.INSTANCE.get(this).onTrimMemory(level);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ ACTIVITY_TRACKER.onActivityDestroyed(this);
+ }
+
+ @Override
+ public void onBackPressed() {
+ // TODO: Launch the task we came from
+ startHome();
+ }
+
+ public void startHome() {
+ startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ @Override
+ protected StateHandler<RecentsState>[] createStateHandlers() {
+ return new StateHandler[] { new FallbackRecentsStateController(this) };
+ }
+
+ @Override
+ public StateManager<RecentsState> getStateManager() {
+ return mStateManager;
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ writer.println(prefix + "Misc:");
+ dumpMisc(prefix + "\t", writer);
+ }
+
+ @Override
+ public AtomicAnimationFactory<RecentsState> createAtomicAnimationFactory() {
+ return new RecentsAtomicAnimationFactory<>(this, 0);
+ }
+
+ private AnimatorListenerAdapter resetStateListener() {
+ return new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mFallbackRecentsView.resetTaskVisuals();
+ mStateManager.reapplyState();
+ }
+ };
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
new file mode 100644
index 0000000..dc8f1c5
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -0,0 +1,350 @@
+/*
+ * 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.anim.Interpolators.ACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
+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.launcher3.views.FloatingIconView;
+import com.android.quickstep.util.RectFSpringAnim;
+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 {
+
+ protected static final Rect TEMP_RECT = new Rect();
+ private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
+
+ protected DeviceProfile mDp;
+
+ protected final Context mContext;
+ protected final RecentsAnimationDeviceState mDeviceState;
+ protected final GestureState mGestureState;
+ protected final TaskViewSimulator mTaskViewSimulator;
+
+ protected final TransformParams mTransformParams;
+
+ // Shift in the range of [0, 1].
+ // 0 => preview snapShot is completely visible, and hotseat is completely translated down
+ // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
+ // visible.
+ protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+
+ // The distance needed to drag to reach the task size in recents.
+ protected int mTransitionDragLength;
+ // How much further we can drag past recents, as a factor of mTransitionDragLength.
+ protected float mDragLengthFactor = 1;
+ // Start resisting when swiping past this factor of mTransitionDragLength.
+ private float mDragLengthFactorStartPullback = 1f;
+ // This is how far down we can scale down, where 0f is full screen and 1f is recents.
+ private float mDragLengthFactorMaxPullback = 1f;
+
+ protected AnimatorPlaybackController mWindowTransitionController;
+
+ public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
+ GestureState gestureState, TransformParams transformParams) {
+ mContext = context;
+ mDeviceState = deviceState;
+ mGestureState = gestureState;
+ mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
+ mTransformParams = transformParams;
+
+ mTaskViewSimulator.setLayoutRotation(
+ mDeviceState.getCurrentActiveRotation(), mDeviceState.getDisplayRotation());
+ }
+
+ protected void initTransitionEndpoints(DeviceProfile dp) {
+ mDp = dp;
+
+ mTaskViewSimulator.setDp(dp);
+ mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
+ dp, mContext, TEMP_RECT,
+ mTaskViewSimulator.getOrientationState().getOrientationHandler());
+
+ if (mDeviceState.isFullyGesturalNavMode()) {
+ // We can drag all the way to the top of the screen.
+ mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
+
+ float startScale = mTaskViewSimulator.getFullScreenScale();
+ // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f.
+ mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale);
+ mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale);
+ } else {
+ mDragLengthFactor = 1;
+ mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1;
+ }
+
+ PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
+ mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor);
+ mWindowTransitionController = pa.createPlaybackController();
+ }
+
+ @UiThread
+ public void updateDisplacement(float displacement) {
+ // We are moving in the negative x/y direction
+ displacement = -displacement;
+ float shift;
+ if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
+ shift = mDragLengthFactor;
+ } else {
+ float translation = Math.max(displacement, 0);
+ shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
+ if (shift > mDragLengthFactorStartPullback) {
+ float pullbackProgress = Utilities.getProgress(shift,
+ mDragLengthFactorStartPullback, mDragLengthFactor);
+ pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
+ shift = mDragLengthFactorStartPullback + pullbackProgress
+ * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback);
+ }
+ }
+
+ mCurrentShift.updateValue(shift);
+ }
+
+ /**
+ * Called when the value of {@link #mCurrentShift} changes
+ */
+ @UiThread
+ public abstract void updateFinalShift();
+
+ protected PagedOrientationHandler getOrientationHandler() {
+ return mTaskViewSimulator.getOrientationState().getOrientationHandler();
+ }
+
+ protected abstract class HomeAnimationFactory {
+
+ public FloatingIconView mIconView;
+
+ public HomeAnimationFactory(@Nullable FloatingIconView iconView) {
+ mIconView = iconView;
+ }
+
+ public @NonNull RectF getWindowTargetRect() {
+ PagedOrientationHandler orientationHandler = getOrientationHandler();
+ DeviceProfile dp = mDp;
+ 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;
+ // Fallback to animate to center of screen.
+ return new RectF(targetX - halfIconSize, targetY - halfIconSize,
+ targetX + halfIconSize, targetY + halfIconSize);
+ }
+
+ public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome();
+
+ public void playAtomicAnimation(float velocity) {
+ // No-op
+ }
+ }
+
+ /**
+ * Creates an animation that transforms the current app window into the home app.
+ * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
+ * @param homeAnimationFactory The home animation factory.
+ */
+ protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
+ HomeAnimationFactory homeAnimationFactory) {
+ final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
+ final FloatingIconView fiv = homeAnimationFactory.mIconView;
+ final boolean isFloatingIconView = fiv != null;
+
+ mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor);
+ mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
+ RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
+
+ // Matrix to map a rect in Launcher space to window space
+ Matrix homeToWindowPositionMap = new Matrix();
+ mTaskViewSimulator.applyWindowToHomeRotation(homeToWindowPositionMap);
+
+ final RectF startRect = new RectF(cropRectF);
+ mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
+ // Move the startRect to Launcher space as floatingIconView runs in Launcher
+ Matrix windowToHomePositionMap = new Matrix();
+ homeToWindowPositionMap.invert(windowToHomePositionMap);
+ windowToHomePositionMap.mapRect(startRect);
+
+ RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
+ if (isFloatingIconView) {
+ anim.addAnimatorListener(fiv);
+ fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
+ fiv.setFastFinishRunnable(anim::end);
+ }
+
+ SpringAnimationRunner runner = new SpringAnimationRunner(
+ homeAnimationFactory, cropRectF, homeToWindowPositionMap);
+ anim.addOnUpdateListener(runner);
+ anim.addAnimatorListener(runner);
+ return anim;
+ }
+
+ /**
+ * @param progress The progress of the animation to the home screen.
+ * @return The current alpha to set on the animating app window.
+ */
+ 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);
+ }
+
+ protected class SpringAnimationRunner extends AnimationSuccessListener
+ implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
+
+ final Rect mCropRect = new Rect();
+ final Matrix mMatrix = new Matrix();
+
+ final RectF mWindowCurrentRect = new RectF();
+ final Matrix mHomeToWindowPositionMap;
+
+ final FloatingIconView mFIV;
+ final AnimatorPlaybackController mHomeAnim;
+ final RectF mCropRectF;
+
+ final float mStartRadius;
+ final float mEndRadius;
+ final float mWindowAlphaThreshold;
+
+ SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
+ Matrix homeToWindowPositionMap) {
+ mHomeAnim = factory.createActivityAnimationToHome();
+ mCropRectF = cropRectF;
+ mHomeToWindowPositionMap = homeToWindowPositionMap;
+
+ cropRectF.roundOut(mCropRect);
+ mFIV = factory.mIconView;
+
+ // 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();
+ mEndRadius = cropRectF.width() / 2f;
+
+ // 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.
+ mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f;
+ }
+
+ @Override
+ 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);
+ mTransformParams
+ .setTargetAlpha(getWindowAlpha(progress))
+ .setCornerRadius(cornerRadius);
+
+ mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
+ if (mFIV != null) {
+ mFIV.update(currentRect, 1f, progress,
+ mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false);
+ }
+ }
+
+ @Override
+ public void onBuildTargetParams(
+ Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
+ builder.withMatrix(mMatrix)
+ .withWindowCrop(mCropRect)
+ .withCornerRadius(params.getCornerRadius());
+ }
+
+ @Override
+ public void onCancel() {
+ if (mFIV != null) {
+ mFIV.fastFinish();
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mHomeAnim.dispatchOnStart();
+ }
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mHomeAnim.getAnimationPlayer().end();
+ }
+ }
+
+ public interface RunningWindowAnim {
+ void end();
+
+ void cancel();
+
+ static RunningWindowAnim wrap(Animator animator) {
+ return new RunningWindowAnim() {
+ @Override
+ public void end() {
+ animator.end();
+ }
+
+ @Override
+ public void cancel() {
+ animator.cancel();
+ }
+ };
+ }
+
+ static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
+ return new RunningWindowAnim() {
+ @Override
+ public void end() {
+ rectFSpringAnim.end();
+ }
+
+ @Override
+ public void cancel() {
+ rectFSpringAnim.cancel();
+ }
+ };
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index fbf29af..e9614d1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,20 +16,31 @@
package com.android.quickstep;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+import static android.view.Surface.ROTATION_0;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
+
+import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Rect;
+import android.os.Build;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.RequiresApi;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
@@ -54,6 +65,28 @@
shortcuts.add(shortcut);
}
}
+ RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState();
+ boolean canLauncherRotate = orientedState.canRecentsActivityRotate();
+ 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);
+ if (screenshotShortcut != null) {
+ 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);
+ if (modalShortcut != null) {
+ shortcuts.add(modalShortcut);
+ }
+ }
+ }
return shortcuts;
}
@@ -84,45 +117,68 @@
/**
* Overlay on each task handling Overview Action Buttons.
*/
- public static class TaskOverlay {
+ public static class TaskOverlay<T extends OverviewActionsView> {
private final Context mApplicationContext;
- private OverviewActionsView mActionsView;
- private final TaskThumbnailView mThumbnailView;
+ protected final TaskThumbnailView mThumbnailView;
+ private T mActionsView;
+ private ImageActionsApi mImageApi;
+ private boolean mIsAllowedByPolicy;
protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
mThumbnailView = taskThumbnailView;
+ mImageApi = new ImageActionsApi(
+ mApplicationContext, mThumbnailView::getThumbnail);
+ }
+
+ protected T getActionsView() {
+ if (mActionsView == null) {
+ mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
+ R.id.overview_actions_view);
+ }
+ return mActionsView;
}
/**
* Called when the current task is interactive for the user
*/
- public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) {
- ImageActionsApi imageApi = new ImageActionsApi(
- mApplicationContext, mThumbnailView::getThumbnail);
+ public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
+ boolean rotated) {
+ final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
- if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get()
- && SysUINavigationMode.removeShelfFromOverview(mApplicationContext)) {
- mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
- R.id.overview_actions_view);
- }
- if (mActionsView != null) {
- mActionsView.setListener(new OverviewActionsView.Listener() {
- @Override
- public void onShare() {
- imageApi.startShareActivity();
+ getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
+
+ getActionsView().setCallbacks(new OverlayUICallbacks() {
+ @Override
+ public void onShare() {
+ if (isAllowedByPolicy) {
+ mImageApi.startShareActivity();
+ } else {
+ showBlockedByPolicyMessage();
}
+ }
- @Override
- public void onScreenshot() {
- imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
- getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key.id);
- }
- });
+ @SuppressLint("NewApi")
+ @Override
+ public void onScreenshot() {
+ saveScreenshot(task);
+ }
+ });
+ }
+
+ /**
+ * Called to save screenshot of the task thumbnail.
+ */
+ @SuppressLint("NewApi")
+ private void saveScreenshot(Task task) {
+ if (mThumbnailView.isRealSnapshot()) {
+ mImageApi.saveScreenshot(mThumbnailView.getThumbnail(),
+ getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
+ } else {
+ showBlockedByPolicyMessage();
}
-
}
/**
@@ -132,13 +188,26 @@
}
/**
- * Whether the overlay is modal, which means only tapping is enabled, but no swiping.
+ * Called when the system wants to reset the modal visuals.
*/
- public boolean isOverlayModal() {
- return false;
+ public void resetModalVisuals() {
}
/**
+ * Gets the modal state system shortcut.
+ */
+ public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo) {
+ return null;
+ }
+
+ /**
+ * Gets the system shortcut for the screenshot that will be added to the task menu.
+ */
+ public SystemShortcut getScreenshotShortcut(BaseDraggingActivity activity,
+ ItemInfo iteminfo) {
+ return new ScreenshotSystemShortcut(activity, iteminfo);
+ }
+ /**
* Gets the task snapshot as it is displayed on the screen.
*
* @return the bounds of the snapshot in screen coordinates.
@@ -156,9 +225,44 @@
*
* @return the insets in screen coordinates.
*/
+ @RequiresApi(api = Build.VERSION_CODES.Q)
public Insets getTaskSnapshotInsets() {
- // TODO: return the real insets
- return Insets.of(0, 0, 0, 0);
+ return mThumbnailView.getScaledInsets();
}
+
+ private void showBlockedByPolicyMessage() {
+ Toast.makeText(
+ mThumbnailView.getContext(),
+ R.string.blocked_by_policy,
+ Toast.LENGTH_LONG).show();
+ }
+
+ private class ScreenshotSystemShortcut extends SystemShortcut {
+
+ private final BaseDraggingActivity mActivity;
+
+ ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo) {
+ super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo);
+ mActivity = activity;
+ }
+
+ @Override
+ public void onClick(View view) {
+ saveScreenshot(mThumbnailView.getTaskView().getTask());
+ dismissTaskMenuView(mActivity);
+ }
+ }
+ }
+
+ /**
+ * Callbacks the Ui can generate. This is the only way for a Ui to call methods on the
+ * 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();
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
index 9ba2e5a..ff051b6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
@@ -18,24 +18,25 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
import android.app.Activity;
import android.app.ActivityOptions;
-import android.content.ComponentName;
-import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
-import android.os.UserHandle;
import android.view.View;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo;
@@ -65,28 +66,18 @@
SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
- static WorkspaceItemInfo dummyInfo(TaskView view) {
- Task task = view.getTask();
-
- WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
- dummyInfo.intent = new Intent();
- ComponentName component = task.getTopComponent();
- dummyInfo.intent.setComponent(component);
- dummyInfo.user = UserHandle.of(task.key.userId);
- dummyInfo.title = TaskUtils.getTitle(view.getContext(), task);
- return dummyInfo;
- }
-
- TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, dummyInfo(view));
+ TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, view.getItemInfo());
abstract class MultiWindowFactory implements TaskShortcutFactory {
private final int mIconRes;
private final int mTextRes;
+ private final LauncherEvent mLauncherEvent;
- MultiWindowFactory(int iconRes, int textRes) {
+ MultiWindowFactory(int iconRes, int textRes, LauncherEvent launcherEvent) {
mIconRes = iconRes;
mTextRes = textRes;
+ mLauncherEvent = launcherEvent;
}
protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
@@ -102,7 +93,8 @@
if (!isAvailable(activity, task.key.displayId)) {
return null;
}
- return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this);
+ return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this,
+ mLauncherEvent);
}
}
@@ -114,11 +106,12 @@
private final TaskThumbnailView mThumbnailView;
private final TaskView mTaskView;
private final MultiWindowFactory mFactory;
+ private final LauncherEvent mLauncherEvent;
- public MultiWindowSystemShortcut(int iconRes, int textRes,
- BaseDraggingActivity activity, TaskView taskView, MultiWindowFactory factory) {
- super(iconRes, textRes, activity, dummyInfo(taskView));
-
+ public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
+ TaskView taskView, MultiWindowFactory factory, LauncherEvent launcherEvent) {
+ super(iconRes, textRes, activity, taskView.getItemInfo());
+ mLauncherEvent = launcherEvent;
mHandler = new Handler(Looper.getMainLooper());
mTaskView = taskView;
mRecentsView = activity.getOverviewPanel();
@@ -203,12 +196,14 @@
WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
future, animStartedListener, mHandler, true /* scaleUp */,
taskKey.displayId);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
+ .log(mLauncherEvent);
}
}
}
- TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(
- R.drawable.ic_split_screen, R.string.recent_task_option_split_screen) {
+ TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(R.drawable.ic_split_screen,
+ R.string.recent_task_option_split_screen, LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP) {
@Override
protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
@@ -241,8 +236,8 @@
}
};
- TaskShortcutFactory FREE_FORM = new MultiWindowFactory(
- R.drawable.ic_split_screen, R.string.recent_task_option_freeform) {
+ TaskShortcutFactory FREE_FORM = new MultiWindowFactory(R.drawable.ic_split_screen,
+ R.string.recent_task_option_freeform, LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP) {
@Override
protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
@@ -286,7 +281,7 @@
private final TaskView mTaskView;
public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) {
- super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, dummyInfo(tv));
+ super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, tv.getItemInfo());
mTaskView = tv;
}
@@ -302,14 +297,31 @@
};
mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler());
dismissTaskMenuView(mTarget);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
+ .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
}
}
TaskShortcutFactory INSTALL = (activity, view) ->
InstantAppResolver.newInstance(activity).isInstantApp(activity,
view.getTask().getTopComponent().getPackageName())
- ? new SystemShortcut.Install(activity, dummyInfo(view)) : null;
+ ? new SystemShortcut.Install(activity, view.getItemInfo()) : null;
TaskShortcutFactory WELLBEING = (activity, view) ->
- WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, dummyInfo(view));
+ WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, view.getItemInfo());
+
+ TaskShortcutFactory SCREENSHOT = (activity, tv) -> {
+ if (ENABLE_OVERVIEW_ACTIONS.get()) {
+ return tv.getThumbnail().getTaskOverlay()
+ .getScreenshotShortcut(activity, tv.getItemInfo());
+ }
+ return null;
+ };
+
+ TaskShortcutFactory MODAL = (activity, tv) -> {
+ if (ENABLE_OVERVIEW_ACTIONS.get() && ENABLE_OVERVIEW_SELECTIONS.get()) {
+ return tv.getThumbnail().getTaskOverlay().getModalStateSystemShortcut(tv.getItemInfo());
+ }
+ return null;
+ };
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index 6a3e1fe..e2e25f3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -15,43 +15,46 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+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.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
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;
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
import android.graphics.RectF;
+import android.os.Build;
import android.view.View;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
-import com.android.quickstep.util.AppWindowAnimationHelper;
-import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
/**
* Utility class for helpful methods related to {@link TaskView} objects and their tasks.
*/
+@TargetApi(Build.VERSION_CODES.R)
public final class TaskViewUtils {
private TaskViewUtils() {}
@@ -117,98 +120,110 @@
}
/**
- * @return Animator that controls the window of the opening targets for the recents launch
+ * Creates an animation that controls the window of the opening targets for the recents launch
* animation.
*/
- public static Animator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
+ public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets,
- DepthController depthController,
- final AppWindowAnimationHelper inOutHelper) {
- SyncRtSurfaceTransactionApplierCompat applier =
- new SyncRtSurfaceTransactionApplierCompat(v);
+ RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
+ PendingAnimation out) {
+
+ SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
final RemoteAnimationTargets targets =
new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING);
- targets.addDependentTransactionApplier(applier);
- AppWindowAnimationHelper.TransformParams params =
- new AppWindowAnimationHelper.TransformParams()
+ targets.addReleaseCheck(applier);
+
+ TransformParams params = new TransformParams()
.setSyncTransactionApplier(applier)
- .setTargetSet(targets)
- .setLauncherOnTop(true);
+ .setTargetSet(targets);
- AnimatorSet animatorSet = new AnimatorSet();
final RecentsView recentsView = v.getRecentsView();
- final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
- appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
- appAnimator.addUpdateListener(new MultiValueUpdateListener() {
+ int taskIndex = recentsView.indexOfChild(v);
+ boolean parallaxCenterAndAdjacentTask = taskIndex != recentsView.getCurrentPage();
+ int startScroll = recentsView.getScrollOffset(taskIndex);
- // Defer fading out the view until after the app window gets faded in
- final FloatProp mViewAlpha = new FloatProp(1f, 0f, 75, 75, LINEAR);
- final FloatProp mTaskAlpha = new FloatProp(0f, 1f, 0, 75, LINEAR);
- final RectF mThumbnailRect;
+ Context context = v.getContext();
+ DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
+ // RecentsView never updates the display rotation until swipe-up so the value may be stale.
+ // Use the display value instead.
+ int displayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
- {
- inOutHelper.setTaskAlphaCallback((t, alpha) -> mTaskAlpha.value);
+ TaskViewSimulator topMostSimulator = null;
+ if (targets.apps.length > 0) {
+ TaskViewSimulator tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
+ tsv.setDp(dp);
+ tsv.setLayoutRotation(displayRotation, displayRotation);
+ tsv.setPreview(targets.apps[targets.apps.length - 1]);
+ tsv.fullScreenProgress.value = 0;
+ tsv.recentsViewScale.value = 1;
+ tsv.setScroll(startScroll);
- inOutHelper.prepareAnimation(
- BaseActivity.fromContext(v.getContext()).getDeviceProfile(),
- true /* isOpening */);
- inOutHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(),
- targets.apps.length == 0 ? null : targets.apps[0]);
+ out.setFloat(tsv.fullScreenProgress,
+ AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
+ out.setFloat(tsv.recentsViewScale,
+ AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+ out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
- mThumbnailRect = new RectF(inOutHelper.getTargetRect());
- mThumbnailRect.offset(-v.getTranslationX(), -v.getTranslationY());
- Utilities.scaleRectFAboutCenter(mThumbnailRect, 1 / v.getScaleX());
- }
+ out.addOnFrameCallback(() -> tsv.apply(params));
+ topMostSimulator = tsv;
+ }
- @Override
- public void onUpdate(float percent) {
- // TODO: Take into account the current fullscreen progress for animating the insets
- params.setProgress(1 - percent);
- RectF taskBounds;
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- List<SurfaceParams> surfaceParamsList = new ArrayList<>();
- // Append the surface transform params for the app that's being opened.
- Collections.addAll(surfaceParamsList, inOutHelper.computeSurfaceParams(params));
+ // Fade in the task during the initial 20% of the animation
+ out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1, clampToProgress(LINEAR, 0, 0.2f));
- AppWindowAnimationHelper liveTileAnimationHelper =
- v.getRecentsView().getClipAnimationHelper();
- if (liveTileAnimationHelper != null) {
- // Append the surface transform params for the live tile app.
- AppWindowAnimationHelper.TransformParams liveTileParams =
- v.getRecentsView().getLiveTileParams(true /* mightNeedToRefill */);
- if (liveTileParams != null) {
- SurfaceParams[] liveTileSurfaceParams =
- liveTileAnimationHelper.computeSurfaceParams(liveTileParams);
- if (liveTileSurfaceParams != null) {
- Collections.addAll(surfaceParamsList, liveTileSurfaceParams);
- }
- }
- }
- // Apply surface transform using the surface params list.
- AppWindowAnimationHelper.applySurfaceParams(params.getSyncTransactionApplier(),
- surfaceParamsList.toArray(new SurfaceParams[surfaceParamsList.size()]));
- // Get the task bounds for the app that's being opened after surface transform
- // update.
- taskBounds = inOutHelper.updateCurrentRect(params);
- } else {
- taskBounds = inOutHelper.applyTransform(params);
+ if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
+ out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
+
+ TaskViewSimulator simulatorToCopy = topMostSimulator;
+ simulatorToCopy.apply(params);
+
+ // Mt represents the overall transformation on the thumbnailView relative to the
+ // Launcher's rootView
+ // K(t) represents transformation on the running window by the taskViewSimulator at
+ // any time t.
+ // at t = 0, we know that the simulator matches the thumbnailView. So if we apply K(0)`
+ // on the Launcher's rootView, the thumbnailView would match the full running task
+ // window. If we apply "K(0)` K(t)" thumbnailView will match the final transformed
+ // window at any time t. This gives the overall matrix on thumbnailView to be:
+ // Mt K(0)` K(t)
+ // 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]);
+
+ 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 animationMatrix = new Matrix();
+ out.addOnFrameCallback(() -> {
+ animationMatrix.set(mt);
+ animationMatrix.postConcat(k0i);
+ animationMatrix.postConcat(simulatorToCopy.getCurrentMatrix());
+ animationMatrix.postConcat(mti);
+ ttv.setAnimationMatrix(animationMatrix);
+ });
+
+ out.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ttv.setAnimationMatrix(null);
}
+ });
+ }
- int taskIndex = recentsView.indexOfChild(v);
- int centerTaskIndex = recentsView.getCurrentPage();
- boolean parallaxCenterAndAdjacentTask = taskIndex != centerTaskIndex;
- if (!skipViewChanges && parallaxCenterAndAdjacentTask) {
- float scale = taskBounds.width() / mThumbnailRect.width();
- v.setScaleX(scale);
- v.setScaleY(scale);
- v.setTranslationX(taskBounds.centerX() - mThumbnailRect.centerX());
- v.setTranslationY(taskBounds.centerY() - mThumbnailRect.centerY());
- v.setAlpha(mViewAlpha.value);
- }
- }
- });
- appAnimator.addListener(new AnimatorListenerAdapter() {
+ out.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
targets.release();
@@ -216,12 +231,8 @@
});
if (depthController != null) {
- ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController,
- DEPTH, BACKGROUND_APP.getDepth(v.getContext()));
- animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
- } else {
- animatorSet.play(appAnimator);
+ out.setFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(context),
+ TOUCH_RESPONSE_INTERPOLATOR);
}
- return animatorSet;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index c889632..a3705fd 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static android.content.Intent.ACTION_CHOOSER;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
@@ -26,18 +27,20 @@
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
import android.annotation.TargetApi;
import android.app.ActivityManager;
-import android.app.ActivityManager.RunningTaskInfo;
+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;
+import android.graphics.Rect;
import android.graphics.Region;
+import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -46,6 +49,7 @@
import android.view.Choreographer;
import android.view.InputEvent;
import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityManager;
import androidx.annotation.BinderThread;
import androidx.annotation.Nullable;
@@ -53,31 +57,37 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.tracing.nano.LauncherTraceProto;
import com.android.launcher3.tracing.nano.TouchInteractionServiceProto;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
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.ResetGestureInputConsumer;
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
+import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistantUtilities;
import com.android.quickstep.util.ProtoTracer;
-import com.android.quickstep.views.RecentsView;
+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;
@@ -86,7 +96,6 @@
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.tracing.ProtoTraceable;
import java.io.FileDescriptor;
@@ -115,7 +124,7 @@
/**
* Service connected by system-UI for handling touch interaction.
*/
-@TargetApi(Build.VERSION_CODES.Q)
+@TargetApi(Build.VERSION_CODES.R)
public class TouchInteractionService extends Service implements PluginListener<OverscrollPlugin>,
ProtoTraceable<LauncherTraceProto> {
@@ -126,6 +135,12 @@
private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
private static final int MAX_BACK_NOTIFICATION_COUNT = 3;
+ /**
+ * System Action ID to show all apps.
+ * TODO: Use AccessibilityService's corresponding global action constant in S
+ */
+ private static final int SYSTEM_ACTION_ID_ALL_APPS = 14;
+
private int mBackGestureNotificationCounter = -1;
@Nullable
private OverscrollPlugin mOverscrollPlugin;
@@ -220,6 +235,11 @@
MAIN_EXECUTOR.execute(() -> mDeviceState.setDeferredGestureRegion(region));
}
+ public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets) {
+ WindowBounds wb = new WindowBounds(bounds, insets);
+ MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
+ }
+
/** Deprecated methods **/
public void onQuickStep(MotionEvent motionEvent) { }
@@ -279,6 +299,7 @@
mAM = ActivityManagerWrapper.getInstance();
mDeviceState = new RecentsAnimationDeviceState(this);
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
+ mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
ProtoTracer.INSTANCE.get(this).add(this);
@@ -289,9 +310,6 @@
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "disposeEventHandlers");
- }
}
if (mInputMonitorCompat != null) {
mInputMonitorCompat.dispose();
@@ -300,25 +318,16 @@
}
private void initInputMonitor() {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 1");
- }
disposeEventHandlers();
if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) {
return;
}
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 2");
- }
Bundle bundle = SystemUiProxy.INSTANCE.get(this).monitorGestureInput("swipe-up",
mDeviceState.getDisplayId());
mInputMonitorCompat = InputMonitorCompat.fromBundle(bundle, KEY_EXTRA_INPUT_MONITOR);
mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
mMainChoreographer, this::onInputEvent);
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 3");
- }
mDeviceState.updateGestureTouchRegions();
}
@@ -331,6 +340,13 @@
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
}
+ /**
+ * Called when the one handed mode overlay package changes, to recreate touch region.
+ */
+ private void onOneHandedModeOverlayChanged(int newGesturalHeight) {
+ initInputMonitor();
+ }
+
@UiThread
public void onUserUnlocked() {
mTaskAnimationManager = new TaskAnimationManager();
@@ -351,6 +367,9 @@
PluginManagerWrapper.INSTANCE.get(getBaseContext()).addPluginListener(this,
OverscrollPlugin.class, false /* allowMultiple */);
+
+ mOverviewComponentObserver.setOverviewChangeListener(this::onOverviewTargetChange);
+ onOverviewTargetChange(mOverviewComponentObserver.isHomeAndOverviewSame());
}
private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
@@ -365,11 +384,29 @@
if (!sharedPrefs.getBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)) {
sharedPrefs.edit()
.putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
- .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
+ .putBoolean(OnboardingPrefs.HOME_BOUNCE_SEEN, false)
.apply();
}
}
+ private void onOverviewTargetChange(boolean isHomeAndOverviewSame) {
+ AccessibilityManager am = getSystemService(AccessibilityManager.class);
+
+ if (isHomeAndOverviewSame) {
+ Intent intent = new Intent(mOverviewComponentObserver.getHomeIntent())
+ .setAction(Intent.ACTION_ALL_APPS);
+ RemoteAction allAppsAction = new RemoteAction(
+ Icon.createWithResource(this, R.drawable.ic_apps),
+ getString(R.string.all_apps_label),
+ getString(R.string.all_apps_label),
+ PendingIntent.getActivity(this, SYSTEM_ACTION_ID_ALL_APPS, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT));
+ am.registerSystemAction(allAppsAction, SYSTEM_ACTION_ID_ALL_APPS);
+ } else {
+ am.unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
+ }
+ }
+
@UiThread
private void onSystemUiFlagsChanged() {
if (mDeviceState.isUserUnlocked()) {
@@ -408,6 +445,9 @@
ProtoTracer.INSTANCE.get(TouchInteractionService.this).stop();
ProtoTracer.INSTANCE.get(this).remove(this);
+ getSystemService(AccessibilityManager.class)
+ .unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
+
sConnected = false;
super.onDestroy();
}
@@ -419,9 +459,6 @@
}
private void onInputEvent(InputEvent ev) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onInputEvent " + ev);
- }
if (!(ev instanceof MotionEvent)) {
Log.e(TAG, "Unknown event " + ev);
return;
@@ -440,38 +477,49 @@
final int action = event.getAction();
if (action == ACTION_DOWN) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "TouchInteractionService.onInputEvent:DOWN");
+ }
mDeviceState.setOrientationTransformIfNeeded(event);
- GestureState newGestureState;
if (mDeviceState.isInSwipeUpTouchRegion(event)) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME,
+ "TouchInteractionService.onInputEvent:isInSwipeUpTouchRegion");
+ }
// Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
// onConsumerInactive and wipe the previous gesture state
GestureState prevGestureState = new GestureState(mGestureState);
- newGestureState = createGestureState();
+ GestureState newGestureState = createGestureState(mGestureState);
mConsumer.onConsumerAboutToBeSwitched();
- mConsumer = newConsumer(prevGestureState, newGestureState, event);
+ mGestureState = newGestureState;
+ mConsumer = newConsumer(prevGestureState, mGestureState, event);
- ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType());
+ ActiveGestureLog.INSTANCE.addLog("setInputConsumer: " + mConsumer.getName());
mUncheckedConsumer = mConsumer;
- } else if (mDeviceState.isUserUnlocked()
- && mDeviceState.isFullyGesturalNavMode()
- && mDeviceState.canTriggerAssistantAction(event)) {
- newGestureState = createGestureState();
- // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
- // not interrupt it. QuickSwitch assumes that interruption can only happen if the
- // next gesture is also quick switch.
- mUncheckedConsumer = new AssistantInputConsumer(
- this,
- newGestureState,
- InputConsumer.NO_OP, mInputMonitorCompat,
- mOverviewComponentObserver.assistantGestureIsConstrained());
+ } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()) {
+ mGestureState = createGestureState(mGestureState);
+ ActivityManager.RunningTaskInfo runningTask = mGestureState.getRunningTask();
+ if (mDeviceState.canTriggerAssistantAction(event, runningTask)) {
+ // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
+ // should not interrupt it. QuickSwitch assumes that interruption can only
+ // happen if the next gesture is also quick switch.
+ mUncheckedConsumer = new AssistantInputConsumer(
+ this,
+ mGestureState,
+ InputConsumer.NO_OP, mInputMonitorCompat,
+ mOverviewComponentObserver.assistantGestureIsConstrained());
+ } else {
+ mUncheckedConsumer = InputConsumer.NO_OP;
+ }
+ } else if (mDeviceState.canTriggerOneHandedAction(event)
+ && !mDeviceState.isOneHandedModeActive()) {
+ // Consume gesture event for triggering one handed feature.
+ mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
+ InputConsumer.NO_OP, mInputMonitorCompat);
} else {
- newGestureState = DEFAULT_STATE;
mUncheckedConsumer = InputConsumer.NO_OP;
}
-
- // Save the current gesture state
- mGestureState = newGestureState;
} else {
// Other events
if (mUncheckedConsumer != InputConsumer.NO_OP) {
@@ -480,28 +528,51 @@
}
}
- ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
- mUncheckedConsumer.onMotionEvent(event);
-
- if (action == ACTION_UP || action == ACTION_CANCEL) {
- if (mConsumer != null && !mConsumer.isConsumerDetachedFromGesture()) {
- onConsumerInactive(mConsumer);
+ if (mUncheckedConsumer != InputConsumer.NO_OP) {
+ switch (event.getActionMasked()) {
+ case ACTION_DOWN:
+ case ACTION_UP:
+ ActiveGestureLog.INSTANCE.addLog("onMotionEvent("
+ + (int) event.getRawX() + ", " + (int) event.getRawY() + ")",
+ event.getActionMasked());
+ break;
+ default:
+ ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
+ break;
}
}
+ boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL)
+ && mConsumer != null
+ && !mConsumer.getActiveConsumerInHierarchy().isConsumerDetachedFromGesture();
+ mUncheckedConsumer.onMotionEvent(event);
+
+ if (cleanUpConsumer) {
+ reset();
+ }
TraceHelper.INSTANCE.endFlagsOverride(traceToken);
}
- private GestureState createGestureState() {
+ private GestureState createGestureState(GestureState previousGestureState) {
GestureState gestureState = new GestureState(mOverviewComponentObserver,
ActiveGestureLog.INSTANCE.generateAndSetLogId());
- gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
- () -> mAM.getRunningTask(true /* filterOnlyVisibleRecents */)));
+ if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+ gestureState.updateRunningTask(previousGestureState.getRunningTask());
+ gestureState.updateLastStartedTaskId(previousGestureState.getLastStartedTaskId());
+ gestureState.updatePreviouslyAppearedTaskIds(
+ previousGestureState.getPreviouslyAppearedTaskIds());
+ } else {
+ gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
+ () -> mAM.getRunningTask(false /* filterOnlyVisibleRecents */)));
+ }
return gestureState;
}
private InputConsumer newConsumer(GestureState previousGestureState,
GestureState newGestureState, MotionEvent event) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "newConsumer");
+ }
boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
if (!mDeviceState.isUserUnlocked()) {
@@ -513,6 +584,9 @@
return mResetGestureInputConsumer;
}
}
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "newConsumer:user is unlocked");
+ }
// When there is an existing recents animation running, bypass systemState check as this is
// a followup gesture and the first gesture started in a valid system state.
@@ -520,12 +594,11 @@
|| previousGestureState.isRecentsAnimationRunning()
? newBaseConsumer(previousGestureState, newGestureState, event)
: mResetGestureInputConsumer;
- // TODO(b/149880412): 2 button landscape mode is wrecked. Fixit!
if (mDeviceState.isGesturalNavMode()) {
handleOrientationSetup(base);
}
if (mDeviceState.isFullyGesturalNavMode()) {
- if (mDeviceState.canTriggerAssistantAction(event)) {
+ if (mDeviceState.canTriggerAssistantAction(event, newGestureState.getRunningTask())) {
base = new AssistantInputConsumer(
this,
newGestureState,
@@ -555,12 +628,24 @@
}
}
+ // If Bubbles is expanded, use the overlay input consumer, which will close Bubbles
+ // instead of going all the way home when a swipe up is detected.
+ if (mDeviceState.isBubblesExpanded() || mDeviceState.isGlobalActionsShowing()) {
+ base = new SysUiOverlayInputConsumer(
+ getBaseContext(), mDeviceState, mInputMonitorCompat);
+ }
+
if (mDeviceState.isScreenPinningActive()) {
// Note: we only allow accessibility to wrap this, and it replaces the previous
// base input consumer (which should be NO_OP anyway since topTaskLocked == true).
base = new ScreenPinnedInputConsumer(this, newGestureState);
}
+ if (mDeviceState.canTriggerOneHandedAction(event)) {
+ base = new OneHandedModeInputConsumer(this, mDeviceState, base,
+ mInputMonitorCompat);
+ }
+
if (mDeviceState.isAccessibilityMenuAvailable()) {
base = new AccessibilityInputConsumer(this, mDeviceState, base,
mInputMonitorCompat);
@@ -569,24 +654,21 @@
if (mDeviceState.isScreenPinningActive()) {
base = mResetGestureInputConsumer;
}
+
+ if (mDeviceState.canTriggerOneHandedAction(event)) {
+ base = new OneHandedModeInputConsumer(this, mDeviceState, base,
+ mInputMonitorCompat);
+ }
}
return base;
}
private void handleOrientationSetup(InputConsumer baseInputConsumer) {
- if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
- return;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "handleOrientationSetup.1");
}
- mDeviceState.enableMultipleRegions(baseInputConsumer instanceof OtherActivityInputConsumer);
- BaseDraggingActivity activity =
- mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
- if (activity == null || !(activity.getOverviewPanel() instanceof RecentsView)) {
- return;
- }
- ((RecentsView) activity.getOverviewPanel())
- .setLayoutRotation(mDeviceState.getCurrentActiveRotation(),
- mDeviceState.getDisplayRotation());
- activity.getDragLayer().recreateControllers();
+
+ baseInputConsumer.notifyOrientationSetup();
}
private InputConsumer newBaseConsumer(GestureState previousGestureState,
@@ -596,19 +678,23 @@
return createDeviceLockedInputConsumer(gestureState);
}
- RunningTaskInfo runningTask = gestureState.getRunningTask();
- ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
- boolean forceOverviewInputConsumer = runningTask != null
- && runningTask.baseIntent.getComponent().equals(homeComponent);
+ // Use overview input consumer for sharesheets on top of home.
+ boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
+ && gestureState.getRunningTask() != null
+ && ACTION_CHOOSER.equals(gestureState.getRunningTask().baseIntent.getAction());
+ if (AssistantUtilities.isExcludedAssistant(gestureState.getRunningTask())) {
+ // In the case where we are in the excluded assistant state, ignore it and treat the
+ // running activity as the task behind the assistant
+ gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.assistant",
+ () -> mAM.getRunningTask(true /* filterOnlyVisibleRecents */)));
+ ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
+ ComponentName runningComponent =
+ gestureState.getRunningTask().baseIntent.getComponent();
+ forceOverviewInputConsumer =
+ runningComponent != null && runningComponent.equals(homeComponent);
+ }
- if (previousGestureState.getFinishingRecentsAnimationTaskId() > 0) {
- // If the finish animation was interrupted, then continue using the other activity input
- // consumer but with the next task as the running task
- RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
- info.id = previousGestureState.getFinishingRecentsAnimationTaskId();
- gestureState.updateRunningTask(info);
- return createOtherActivityInputConsumer(previousGestureState, gestureState, event);
- } else if (gestureState.getRunningTask() == null) {
+ if (gestureState.getRunningTask() == null) {
return mResetGestureInputConsumer;
} else if (previousGestureState.isRunningAnimationToLauncher()
|| gestureState.getActivityInterface().isResumed()
@@ -622,25 +708,22 @@
} else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) {
return mResetGestureInputConsumer;
} else {
- return createOtherActivityInputConsumer(previousGestureState, gestureState, event);
+ return createOtherActivityInputConsumer(gestureState, event);
}
}
- private InputConsumer createOtherActivityInputConsumer(GestureState previousGestureState,
- GestureState gestureState, MotionEvent event) {
+ private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
+ MotionEvent event) {
- final boolean shouldDefer;
final BaseSwipeUpHandler.Factory factory;
-
if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
- shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0;
factory = mFallbackSwipeHandlerFactory;
} else {
- shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState,
- event);
factory = mLauncherSwipeHandlerFactory;
}
+ final boolean shouldDefer = !mOverviewComponentObserver.isHomeAndOverviewSame()
+ || gestureState.getActivityInterface().deferStartingActivity(mDeviceState, event);
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
return new OtherActivityInputConsumer(this, mDeviceState, mTaskAnimationManager,
gestureState, shouldDefer, this::onConsumerInactive,
@@ -659,7 +742,7 @@
public InputConsumer createOverviewInputConsumer(GestureState previousGestureState,
GestureState gestureState, MotionEvent event,
boolean forceOverviewInputConsumer) {
- BaseDraggingActivity activity = gestureState.getActivityInterface().getCreatedActivity();
+ StatefulActivity activity = gestureState.getActivityInterface().getCreatedActivity();
if (activity == null) {
return mResetGestureInputConsumer;
}
@@ -678,38 +761,47 @@
}
/**
- * To be called by the consumer when it's no longer active.
+ * To be called by the consumer when it's no longer active. This can be called by any consumer
+ * in the hierarchy at any point during the gesture (ie. if a delegate consumer starts
+ * intercepting touches, the base consumer can try to call this).
*/
private void onConsumerInactive(InputConsumer caller) {
- if (mConsumer != null && mConsumer.isInConsumerHierarchy(caller)) {
- mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
- mGestureState = new GestureState();
+ if (mConsumer != null && mConsumer.getActiveConsumerInHierarchy() == caller) {
+ reset();
}
}
+ private void reset() {
+ mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
+ mGestureState = DEFAULT_STATE;
+ }
+
private void preloadOverview(boolean fromInit) {
if (!mDeviceState.isUserUnlocked()) {
return;
}
+
if (mDeviceState.isButtonNavMode() && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
// Prevent the overview from being started before the real home on first boot.
return;
}
- if (RestoreDbTask.isPending(this)) {
+ if (RestoreDbTask.isPending(this) || !mDeviceState.isUserSetupComplete()) {
// Preloading while a restore is pending may cause launcher to start the restore
// too early.
return;
}
- final BaseActivityInterface<BaseDraggingActivity> activityInterface =
+ final BaseActivityInterface activityInterface =
mOverviewComponentObserver.getActivityInterface();
+ final Intent overviewIntent = new Intent(
+ mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
if (activityInterface.getCreatedActivity() == null) {
// Make sure that UI states will be initialized.
activityInterface.createActivityInitListener((wasVisible) -> {
AppLaunchTracker.INSTANCE.get(TouchInteractionService.this);
return false;
- }).register();
+ }).register(overviewIntent);
} else if (fromInit) {
// The activity has been created before the initialization of overview service. It is
// usually happens when booting or launcher is the top activity, so we should already
@@ -717,8 +809,7 @@
return;
}
- mTaskAnimationManager.preloadRecentsAnimation(
- mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
+ mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
}
@Override
@@ -735,6 +826,13 @@
}
if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
activity.getResources().getConfiguration().diff(newConfig))) {
+ // Since navBar gestural height are different between portrait and landscape,
+ // can handle orientation changes and refresh navigation gestural region through
+ // onOneHandedModeChanged()
+ int newGesturalHeight = ResourceUtils.getNavbarSize(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
+ getApplicationContext().getResources());
+ mDeviceState.onOneHandedModeChanged(newGesturalHeight);
return;
}
@@ -767,6 +865,7 @@
if (mGestureState != null) {
mGestureState.dump(pw);
}
+ SysUINavigationMode.INSTANCE.get(this).dump(pw);
pw.println("TouchState:");
BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
: mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
@@ -795,16 +894,16 @@
}
}
- private BaseSwipeUpHandler createLauncherSwipeHandler(GestureState gestureState,
- long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
- return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+ private BaseSwipeUpHandler createLauncherSwipeHandler(
+ GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+ return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
}
- private BaseSwipeUpHandler createFallbackSwipeHandler(GestureState gestureState,
- long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
- return new FallbackSwipeHandler(this, mDeviceState, gestureState,
- mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
+ private BaseSwipeUpHandler createFallbackSwipeHandler(
+ GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+ return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+ gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
}
protected boolean shouldNotifyBackGesture() {
@@ -823,11 +922,6 @@
}
}
- public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) {
- UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
- .startRecentsActivity(intent, null, listener, null, null));
- }
-
@Override
public void onPluginConnected(OverscrollPlugin overscrollPlugin, Context context) {
mOverscrollPlugin = overscrollPlugin;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
index 6f919c1..be3fdde 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep.fallback;
+import android.graphics.PointF;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
@@ -30,7 +31,8 @@
/**
* In 0-button mode, intercepts swipe up from the nav bar on FallbackRecentsView to go home.
*/
-public class FallbackNavBarTouchController implements TouchController {
+public class FallbackNavBarTouchController implements TouchController,
+ TriggerSwipeUpTouchTracker.OnSwipeUpListener {
private final RecentsActivity mActivity;
@Nullable
@@ -44,7 +46,7 @@
DefaultDisplay.INSTANCE.get(mActivity).getInfo());
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
true /* disableHorizontalSwipe */, navBarPosition,
- null /* onInterceptTouch */, this::onSwipeUp);
+ null /* onInterceptTouch */, this);
} else {
mTriggerSwipeUpTracker = null;
}
@@ -72,7 +74,11 @@
return false;
}
- private void onSwipeUp(boolean wasFling) {
+ @Override
+ public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
mActivity.<FallbackRecentsView>getOverviewPanel().startHome();
}
+
+ @Override
+ public void onSwipeUpCancelled() {}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
new file mode 100644
index 0000000..3f1e7ba
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -0,0 +1,94 @@
+/*
+ * 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.fallback;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
+
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.views.ClearAllButton;
+
+/**
+ * State controller for fallback recents activity
+ */
+public class FallbackRecentsStateController implements StateHandler<RecentsState> {
+
+ private final StateAnimationConfig mNoConfig = new StateAnimationConfig();
+ private final RecentsActivity mActivity;
+ private final FallbackRecentsView mRecentsView;
+
+ public FallbackRecentsStateController(RecentsActivity activity) {
+ mActivity = activity;
+ mRecentsView = activity.getOverviewPanel();
+ }
+
+ @Override
+ public void setState(RecentsState state) {
+ mRecentsView.updateEmptyMessage();
+ mRecentsView.resetTaskVisuals();
+ setProperties(state, mNoConfig, PropertySetter.NO_ANIM_PROPERTY_SETTER);
+ }
+
+ @Override
+ public void setStateWithAnimation(RecentsState toState, StateAnimationConfig config,
+ PendingAnimation setter) {
+ if (!config.hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_PEEK | PLAY_ATOMIC_OVERVIEW_SCALE)) {
+ // The entire recents animation is played atomically.
+ return;
+ }
+ if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
+ return;
+ }
+ // While animating into recents, update the visible task data as needed
+ setter.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
+ mRecentsView.updateEmptyMessage();
+
+ setProperties(toState, config, setter);
+ }
+
+ private void setProperties(RecentsState state, StateAnimationConfig config,
+ PropertySetter setter) {
+ float buttonAlpha = state.hasButtons() ? 1 : 0;
+ setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
+ buttonAlpha, LINEAR);
+ setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
+ MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+
+ float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
+ setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0],
+ config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
+ setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
+ config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
+
+ setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
+ config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
+ setter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, state.isFullScreen() ? 1 : 0, LINEAR);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index dc0c194..d20bbe9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -15,21 +15,20 @@
*/
package com.android.quickstep.fallback;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.quickstep.fallback.RecentsState.DEFAULT;
+import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
+import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
+import android.os.Build;
import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.view.View;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.quickstep.FallbackActivityInterface;
import com.android.quickstep.RecentsActivity;
-import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -37,36 +36,24 @@
import java.util.ArrayList;
-public class FallbackRecentsView extends RecentsView<RecentsActivity> {
+@TargetApi(Build.VERSION_CODES.R)
+public class FallbackRecentsView extends RecentsView<RecentsActivity>
+ implements StateListener<RecentsState> {
- public static final FloatProperty<FallbackRecentsView> ZOOM_PROGRESS =
- new FloatProperty<FallbackRecentsView> ("zoomInProgress") {
-
- @Override
- public void setValue(FallbackRecentsView view, float value) {
- view.setZoomProgress(value);
- }
-
- @Override
- public Float get(FallbackRecentsView view) {
- return view.mZoomInProgress;
- }
- };
-
- private float mZoomInProgress = 0;
- private boolean mInOverviewState = true;
-
- private float mZoomScale = 1f;
- private float mZoomTranslationY = 0f;
-
- private RunningTaskInfo mRunningTaskInfo;
+ private RunningTaskInfo mHomeTaskInfo;
public FallbackRecentsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ super(context, attrs, defStyleAttr, FallbackActivityInterface.INSTANCE);
+ mActivity.getStateManager().addStateListener(this);
+ }
+
+ @Override
+ public void init(OverviewActionsView actionsView) {
+ super.init(actionsView);
setOverviewStateEnabled(true);
setOverlayEnabled(true);
}
@@ -77,107 +64,65 @@
}
@Override
- public void onViewAdded(View child) {
- super.onViewAdded(child);
- updateEmptyMessage();
- }
-
- @Override
- public void onViewRemoved(View child) {
- super.onViewRemoved(child);
- updateEmptyMessage();
- }
-
- @Override
- public void draw(Canvas canvas) {
- maybeDrawEmptyMessage(canvas);
- super.draw(canvas);
- }
-
- @Override
- public void reset() {
- super.reset();
- resetViewUI();
- }
-
- @Override
- protected void getTaskSize(DeviceProfile dp, Rect outRect) {
- LayoutUtils.calculateFallbackTaskSize(getContext(), dp, outRect);
- }
-
- @Override
public boolean shouldUseMultiWindowTaskSizeStrategy() {
// Just use the activity task size for multi-window as well.
return false;
}
- public void resetViewUI() {
- setZoomProgress(0);
- resetTaskVisuals();
+ /**
+ * When starting gesture interaction from home, we add a temporary invisible tile corresponding
+ * to the home task. This allows us to handle quick-switch similarly to a quick-switching
+ * from a foreground task.
+ */
+ public void onGestureAnimationStartOnHome(RunningTaskInfo homeTaskInfo) {
+ mHomeTaskInfo = homeTaskInfo;
+ onGestureAnimationStart(homeTaskInfo == null ? -1 : homeTaskInfo.taskId);
}
- public void setInOverviewState(boolean inOverviewState) {
- if (mInOverviewState != inOverviewState) {
- mInOverviewState = inOverviewState;
- if (mInOverviewState) {
- resetTaskVisuals();
- } else {
- setZoomProgress(1);
+ /**
+ * When the gesture ends and recents view become interactive, we also remove the temporary
+ * invisible tile added for the home task. This also pushes the remaining tiles back
+ * to the center.
+ */
+ @Override
+ public void onGestureAnimationEnd() {
+ super.onGestureAnimationEnd();
+ if (mHomeTaskInfo != null) {
+ TaskView tv = getTaskView(mHomeTaskInfo.taskId);
+ if (tv != null) {
+ PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150);
+ pa.addEndListener(e -> setCurrentTask(-1));
+ runDismissAnimation(pa);
}
}
}
@Override
- public void resetTaskVisuals() {
- super.resetTaskVisuals();
- setFullscreenProgress(mFullscreenProgress);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- if (getTaskViewCount() == 0) {
- mZoomScale = 1f;
- mZoomTranslationY = 0f;
- } else {
- TaskView dummyTask = getTaskViewAt(0);
- ScaleAndTranslation sat = getTempAppWindowAnimationHelper()
- .updateForFullscreenOverview(dummyTask)
- .getScaleAndTranslation();
- mZoomScale = sat.scale;
- mZoomTranslationY = sat.translationY;
- }
-
- setZoomProgress(mZoomInProgress);
- }
-
- public void setZoomProgress(float progress) {
- mZoomInProgress = progress;
- SCALE_PROPERTY.set(this, Utilities.mapRange(mZoomInProgress, 1, mZoomScale));
- TRANSLATION_Y.set(this, Utilities.mapRange(mZoomInProgress, 0, mZoomTranslationY));
- FULLSCREEN_PROGRESS.set(this, mZoomInProgress);
- }
-
- public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
- mRunningTaskInfo = runningTaskInfo;
- onGestureAnimationStart(runningTaskInfo == null ? -1 : runningTaskInfo.taskId);
- }
-
- @Override
public void setCurrentTask(int runningTaskId) {
super.setCurrentTask(runningTaskId);
- if (mRunningTaskInfo != null && mRunningTaskInfo.taskId != runningTaskId) {
- mRunningTaskInfo = null;
+ if (mHomeTaskInfo != null && mHomeTaskInfo.taskId != runningTaskId) {
+ mHomeTaskInfo = null;
+ setRunningTaskHidden(false);
}
}
@Override
+ protected boolean shouldAddDummyTaskView(int runningTaskId) {
+ if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId
+ && getTaskViewCount() == 0) {
+ // Do not add a dummy task if we are running over home with empty recents, so that we
+ // show the empty recents message instead of showing a dummy task and later removing it.
+ return false;
+ }
+ return super.shouldAddDummyTaskView(runningTaskId);
+ }
+
+ @Override
protected void applyLoadPlan(ArrayList<Task> tasks) {
// When quick-switching on 3p-launcher, we add a "dummy" tile corresponding to Launcher
// as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
// track the index of the next task appropriately, as if we are switching on any other app.
- if (mRunningTaskInfo != null && mRunningTaskInfo.taskId == mRunningTaskId) {
+ if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId && !tasks.isEmpty()) {
// Check if the task list has running task
boolean found = false;
for (Task t : tasks) {
@@ -189,7 +134,7 @@
if (!found) {
ArrayList<Task> newList = new ArrayList<>(tasks.size() + 1);
newList.addAll(tasks);
- newList.add(Task.from(new TaskKey(mRunningTaskInfo), mRunningTaskInfo, false));
+ newList.add(Task.from(new TaskKey(mHomeTaskInfo), mHomeTaskInfo, false));
tasks = newList;
}
}
@@ -197,7 +142,44 @@
}
@Override
- protected boolean supportsVerticalLandscape() {
- return false;
+ public void setRunningTaskHidden(boolean isHidden) {
+ if (mHomeTaskInfo != null) {
+ // Always keep the home task hidden
+ isHidden = true;
+ }
+ super.setRunningTaskHidden(isHidden);
+ }
+
+ @Override
+ public void setModalStateEnabled(boolean isModalState) {
+ super.setModalStateEnabled(isModalState);
+ if (isModalState) {
+ mActivity.getStateManager().goToState(RecentsState.MODAL_TASK);
+ } else {
+ if (mActivity.isInState(RecentsState.MODAL_TASK)) {
+ mActivity.getStateManager().goToState(DEFAULT);
+ }
+ }
+ }
+
+ @Override
+ public void onStateTransitionStart(RecentsState toState) {
+ setOverviewStateEnabled(true);
+ setFreezeViewVisibility(true);
+ }
+
+ @Override
+ public void onStateTransitionComplete(RecentsState finalState) {
+ setOverlayEnabled(finalState == DEFAULT || finalState == MODAL_TASK);
+ setFreezeViewVisibility(false);
+ }
+
+ @Override
+ public void setOverviewStateEnabled(boolean enabled) {
+ super.setOverviewStateEnabled(enabled);
+ if (enabled) {
+ RecentsState state = mActivity.getStateManager().getState();
+ setDisallowScrollToClearAll(!state.hasButtons());
+ }
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java
new file mode 100644
index 0000000..a00015a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.quickstep.fallback;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.RecentsActivity;
+
+/**
+ * Drag layer for fallback recents activity
+ */
+public class RecentsDragLayer extends BaseDragLayer<RecentsActivity> {
+
+ public RecentsDragLayer(Context context, AttributeSet attrs) {
+ super(context, attrs, 1 /* alphaChannelCount */);
+ }
+
+ @Override
+ public void recreateControllers() {
+ mControllers = new TouchController[] {
+ new RecentsTaskController(mActivity),
+ new FallbackNavBarTouchController(mActivity),
+ };
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ super.setInsets(insets);
+ setBackground(insets.top == 0 || !mAllowSysuiScrims
+ ? null
+ : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
deleted file mode 100644
index 7f5ec9b..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ /dev/null
@@ -1,93 +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.quickstep.fallback;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TouchController;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.RecentsActivity;
-
-public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
-
- private static final int MIN_SIZE = 10;
-
- private final Point mLastKnownSize = new Point(MIN_SIZE, MIN_SIZE);
-
- public RecentsRootView(Context context, AttributeSet attrs) {
- super(context, attrs, 1 /* alphaChannelCount */);
- setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | SYSTEM_UI_FLAG_LAYOUT_STABLE);
- }
-
- public Point getLastKnownSize() {
- return mLastKnownSize;
- }
-
- @Override
- public void recreateControllers() {
- mControllers = new TouchController[] {
- new RecentsTaskController(mActivity),
- new FallbackNavBarTouchController(mActivity),
- };
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Check size changes before the actual measure, to avoid multiple measure calls.
- int width = Math.max(MIN_SIZE, MeasureSpec.getSize(widthMeasureSpec));
- int height = Math.max(MIN_SIZE, MeasureSpec.getSize(heightMeasureSpec));
- if (mLastKnownSize.x != width || mLastKnownSize.y != height) {
- mLastKnownSize.set(width, height);
- mActivity.onRootViewSizeChanged();
- }
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @TargetApi(23)
- @Override
- protected boolean fitSystemWindows(Rect insets) {
- // Update device profile before notifying the children.
- mActivity.getDeviceProfile().updateInsets(insets);
- setInsets(insets);
- return false; // Let children get the full insets
- }
-
- @Override
- public void setInsets(Rect insets) {
- // If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by
- // modifying child layout params.
- if (!insets.equals(mInsets)) {
- super.setInsets(insets);
- }
- setBackground(insets.top == 0 || !mAllowSysuiScrims
- ? null
- : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
- }
-
- public void dispatchInsets() {
- mActivity.getDeviceProfile().updateInsets(mInsets);
- super.setInsets(mInsets);
- }
-}
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java
new file mode 100644
index 0000000..211a30c
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java
@@ -0,0 +1,116 @@
+/*
+ * 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.fallback;
+
+import static com.android.launcher3.uioverrides.states.BackgroundAppState.getOverviewScaleAndOffsetForBackgroundState;
+import static com.android.launcher3.uioverrides.states.OverviewModalTaskState.getOverviewScaleAndOffsetForModalState;
+
+import android.content.Context;
+
+import com.android.launcher3.statemanager.BaseState;
+import com.android.quickstep.RecentsActivity;
+
+/**
+ * State definition for Fallback recents
+ */
+public class RecentsState implements BaseState<RecentsState> {
+
+ private static final int FLAG_MODAL = BaseState.getFlag(0);
+ private static final int FLAG_HAS_BUTTONS = BaseState.getFlag(1);
+ private static final int FLAG_FULL_SCREEN = BaseState.getFlag(2);
+
+ public static final RecentsState DEFAULT = new RecentsState(0, FLAG_HAS_BUTTONS);
+ public static final RecentsState MODAL_TASK = new ModalState(1,
+ FLAG_DISABLE_RESTORE | FLAG_HAS_BUTTONS | FLAG_MODAL);
+ public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
+ FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN);
+
+ public final int ordinal;
+ private final int mFlags;
+
+ private static final float NO_OFFSET = 0;
+ private static final float NO_SCALE = 1;
+
+ public RecentsState(int id, int flags) {
+ this.ordinal = id;
+ this.mFlags = flags;
+ }
+
+
+ @Override
+ public String toString() {
+ return "Ordinal-" + ordinal;
+ }
+
+ @Override
+ public final boolean hasFlag(int mask) {
+ return (mFlags & mask) != 0;
+ }
+
+ @Override
+ public int getTransitionDuration(Context context) {
+ return 250;
+ }
+
+ @Override
+ public RecentsState getHistoryForState(RecentsState previousState) {
+ return DEFAULT;
+ }
+
+ /**
+ * For this state, how modal should over view been shown. 0 modalness means all tasks drawn,
+ * 1 modalness means the current task is show on its own.
+ */
+ public float getOverviewModalness() {
+ return hasFlag(FLAG_MODAL) ? 1 : 0;
+ }
+
+ public boolean isFullScreen() {
+ return hasFlag(FLAG_FULL_SCREEN);
+ }
+
+ public boolean hasButtons() {
+ return hasFlag(FLAG_HAS_BUTTONS);
+ }
+
+ public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
+ return new float[] { NO_SCALE, NO_OFFSET };
+ }
+
+
+ private static class ModalState extends RecentsState {
+
+ public ModalState(int id, int flags) {
+ super(id, flags);
+ }
+
+ @Override
+ public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
+ return getOverviewScaleAndOffsetForModalState(activity);
+ }
+ }
+
+ private static class BackgroundAppState extends RecentsState {
+ public BackgroundAppState(int id, int flags) {
+ super(id, flags);
+ }
+
+ @Override
+ public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
+ return getOverviewScaleAndOffsetForBackgroundState(activity);
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
index a113604..d7458d2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
@@ -28,4 +28,9 @@
protected boolean isRecentsInteractive() {
return mActivity.hasWindowFocus();
}
+
+ @Override
+ protected boolean isRecentsModal() {
+ return false;
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index bcc9707..67a15a7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -25,13 +25,11 @@
}
@Override
- public boolean isConsumerDetachedFromGesture() {
- return mDelegate.isConsumerDetachedFromGesture();
- }
-
- @Override
- public boolean isInConsumerHierarchy(InputConsumer candidate) {
- return this == candidate || mDelegate.isInConsumerHierarchy(candidate);
+ public InputConsumer getActiveConsumerInHierarchy() {
+ if (mState == STATE_ACTIVE) {
+ return this;
+ }
+ return mDelegate.getActiveConsumerInHierarchy();
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 7b8d40c..3a97216 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -21,46 +21,49 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
+import static com.android.quickstep.BaseSwipeUpHandlerV2.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
-import android.content.ComponentName;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
-import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
-import com.android.quickstep.LockScreenRecentsActivity;
import com.android.quickstep.MultiStateCallback;
+import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationController;
import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.TaskAnimationManager;
-import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.TransformParams.BuilderProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
/**
* A dummy input consumer used when the device is still locked, e.g. from secure camera.
*/
public class DeviceLockedInputConsumer implements InputConsumer,
- RecentsAnimationCallbacks.RecentsAnimationListener {
-
- private static final float SCALE_DOWN = 0.75f;
+ RecentsAnimationCallbacks.RecentsAnimationListener, BuilderProxy {
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[2] : null;
private static int getFlagForIndex(int index, String name) {
@@ -83,18 +86,20 @@
private final InputMonitorCompat mInputMonitorCompat;
private final PointF mTouchDown = new PointF();
- private final AppWindowAnimationHelper mAppWindowAnimationHelper;
- private final AppWindowAnimationHelper.TransformParams mTransformParams;
- private final Point mDisplaySize;
+ private final TransformParams mTransformParams;
private final MultiStateCallback mStateCallback;
+ private final Point mDisplaySize;
+ private final Matrix mMatrix = new Matrix();
+ private final float mMaxTranslationY;
+
private VelocityTracker mVelocityTracker;
- private float mProgress;
+ private final AnimatedFloat mProgress = new AnimatedFloat(this::applyTransform);
private boolean mThresholdCrossed = false;
+ private boolean mHomeLaunched = false;
private RecentsAnimationController mRecentsAnimationController;
- private RecentsAnimationTargets mRecentsAnimationTargets;
public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
@@ -104,9 +109,10 @@
mTaskAnimationManager = taskAnimationManager;
mGestureState = gestureState;
mTouchSlopSquared = squaredTouchSlop(context);
- mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
- mTransformParams = new AppWindowAnimationHelper.TransformParams();
+ mTransformParams = new TransformParams();
mInputMonitorCompat = inputMonitorCompat;
+ mMaxTranslationY = context.getResources().getDimensionPixelSize(
+ R.dimen.device_locked_y_offset);
// Do not use DeviceProfile as the user data might be locked
mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize;
@@ -157,9 +163,7 @@
}
} else {
float dy = Math.max(mTouchDown.y - y, 0);
- mProgress = dy / mDisplaySize.y;
- mTransformParams.setProgress(mProgress);
- mAppWindowAnimationHelper.applyTransform(mTransformParams);
+ mProgress.updateValue(dy / mDisplaySize.y);
}
break;
}
@@ -175,7 +179,6 @@
* the animation can still be running.
*/
private void finishTouchTracking(MotionEvent ev) {
- mStateCallback.setState(STATE_HANDLER_INVALIDATED);
if (mThresholdCrossed && ev.getAction() == ACTION_UP) {
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
@@ -189,14 +192,29 @@
// Is fling
dismissTask = velocityY < 0;
} else {
- dismissTask = mProgress >= (1 - MIN_PROGRESS_FOR_OVERVIEW);
+ dismissTask = mProgress.value >= (1 - MIN_PROGRESS_FOR_OVERVIEW);
}
- if (dismissTask) {
- // For now, just start the home intent so user is prompted to unlock the device.
- mContext.startActivity(new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- }
+
+ // Animate back to fullscreen before finishing
+ ObjectAnimator animator = mProgress.animateToValue(mProgress.value, 0);
+ animator.setDuration(100);
+ animator.setInterpolator(Interpolators.ACCEL);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (dismissTask) {
+ // For now, just start the home intent so user is prompted to unlock the device.
+ mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ mHomeLaunched = true;
+ }
+ mStateCallback.setState(STATE_HANDLER_INVALIDATED);
+ }
+ });
+ animator.start();
+ } else {
+ mStateCallback.setState(STATE_HANDLER_INVALIDATED);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
@@ -204,13 +222,11 @@
private void startRecentsTransition() {
mThresholdCrossed = true;
+ mHomeLaunched = false;
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
mInputMonitorCompat.pilferPointers();
- Intent intent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_DEFAULT)
- .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ Intent intent = mGestureState.getHomeIntent()
.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, this);
}
@@ -219,38 +235,40 @@
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
mRecentsAnimationController = controller;
- mRecentsAnimationTargets = targets;
-
- Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
- RemoteAnimationTargetCompat targetCompat = targets.findTask(
- mGestureState.getRunningTaskId());
- if (targetCompat != null) {
- mAppWindowAnimationHelper.updateSource(displaySize, targetCompat);
- }
-
- Utilities.scaleRectAboutCenter(displaySize, SCALE_DOWN);
- displaySize.offsetTo(displaySize.left, 0);
- mTransformParams.setTargetSet(mRecentsAnimationTargets)
- .setLauncherOnTop(true);
- mAppWindowAnimationHelper.updateTargetRect(displaySize);
- mAppWindowAnimationHelper.applyTransform(mTransformParams);
-
+ mTransformParams.setTargetSet(targets);
+ applyTransform();
mStateCallback.setState(STATE_TARGET_RECEIVED);
}
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
mRecentsAnimationController = null;
- mRecentsAnimationTargets = null;
+ mTransformParams.setTargetSet(null);
}
private void endRemoteAnimation() {
- if (mRecentsAnimationController != null) {
+ if (mHomeLaunched) {
+ ActivityManagerWrapper.getInstance().cancelRecentsAnimation(false);
+ } else if (mRecentsAnimationController != null) {
mRecentsAnimationController.finishController(
false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */);
}
}
+ private void applyTransform() {
+ mTransformParams.setProgress(mProgress.value);
+ if (mTransformParams.getTargetSet() != null) {
+ mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
+ }
+ }
+
+ @Override
+ public void onBuildTargetParams(
+ Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
+ mMatrix.setTranslate(0, mProgress.value * mMaxTranslationY);
+ builder.withMatrix(mMatrix);
+ }
+
@Override
public void onConsumerAboutToBeSwitched() {
mStateCallback.setState(STATE_HANDLER_INVALIDATED);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
new file mode 100644
index 0000000..0bb8fff
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.inputconsumers;
+
+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 com.android.launcher3.Utilities.squaredHypot;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Touch consumer for handling gesture event to launch one handed
+ * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+ */
+public class OneHandedModeInputConsumer extends DelegateInputConsumer {
+
+ private static final String TAG = "OneHandedModeInputConsumer";
+ private static final int ANGLE_MAX = 150;
+ private static final int ANGLE_MIN = 30;
+
+ private final Context mContext;
+ private final RecentsAnimationDeviceState mDeviceState;
+
+ private final float mDragDistThreshold;
+ private final float mSquaredSlop;
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ private final PointF mStartDragPos = new PointF();
+
+ private boolean mPassedSlop;
+
+ public OneHandedModeInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+ InputConsumer delegate, InputMonitorCompat inputMonitor) {
+ super(delegate, inputMonitor);
+ mContext = context;
+ mDeviceState = deviceState;
+ mDragDistThreshold = context.getResources().getDimensionPixelSize(
+ R.dimen.gestures_onehanded_drag_threshold);
+ mSquaredSlop = Utilities.squaredTouchSlop(context);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_ONE_HANDED | mDelegate.getType();
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case ACTION_DOWN: {
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ break;
+ }
+ case ACTION_MOVE: {
+ if (mState == STATE_DELEGATE_ACTIVE) {
+ break;
+ }
+ if (!mDelegate.allowInterceptByParent()) {
+ mState = STATE_DELEGATE_ACTIVE;
+ break;
+ }
+
+ mLastPos.set(ev.getX(), ev.getY());
+ if (!mPassedSlop) {
+ if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
+ > mSquaredSlop) {
+ mStartDragPos.set(mLastPos.x, mLastPos.y);
+ if ((!mDeviceState.isOneHandedModeActive() && isValidStartAngle(
+ mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))
+ || (mDeviceState.isOneHandedModeActive() && isValidExitAngle(
+ mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))) {
+ mPassedSlop = true;
+ setActive(ev);
+ } else {
+ mState = STATE_DELEGATE_ACTIVE;
+ }
+ }
+ } else {
+ float distance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
+ mLastPos.y - mStartDragPos.y);
+ if (distance > mDragDistThreshold && mPassedSlop) {
+ onStopGestureDetected();
+ }
+ }
+ break;
+ }
+ case ACTION_UP:
+ case ACTION_CANCEL: {
+ if (mLastPos.y >= mStartDragPos.y && mPassedSlop) {
+ onStartGestureDetected();
+ }
+
+ mPassedSlop = false;
+ mState = STATE_INACTIVE;
+ break;
+ }
+ }
+
+ if (mState != STATE_ACTIVE) {
+ mDelegate.onMotionEvent(ev);
+ }
+ }
+
+ private void onStartGestureDetected() {
+ if (mDeviceState.isOneHandedModeEnabled()) {
+ if (!mDeviceState.isOneHandedModeActive()) {
+ SystemUiProxy.INSTANCE.get(mContext).startOneHandedMode();
+ }
+ } else if (mDeviceState.isSwipeToNotificationEnabled()) {
+ SystemUiProxy.INSTANCE.get(mContext).expandNotificationPanel();
+ }
+ }
+
+ private void onStopGestureDetected() {
+ if (!mDeviceState.isOneHandedModeEnabled() || !mDeviceState.isOneHandedModeActive()) {
+ return;
+ }
+
+ SystemUiProxy.INSTANCE.get(mContext).stopOneHandedMode();
+ }
+
+ private boolean isValidStartAngle(float deltaX, float deltaY) {
+ final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+ return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN);
+ }
+
+ private boolean isValidExitAngle(float deltaX, float deltaY) {
+ final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+ return angle > ANGLE_MIN && angle < ANGLE_MAX;
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index fe9ef2b..26df9c7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -23,9 +23,12 @@
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
+import static com.android.launcher3.PagedView.ACTION_MOVE_ALLOW_EASY_FLING;
+import static com.android.launcher3.PagedView.DEBUG_FAILED_QUICKSWITCH;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
+import static com.android.quickstep.GestureState.STATE_OVERSCROLL_WINDOW_CREATED;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -37,6 +40,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
@@ -75,7 +79,8 @@
private static final String UP_EVT = "OtherActivityInputConsumer.UP";
// TODO: Move to quickstep contract
- public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
+ public static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 9;
+ public static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 2;
private final RecentsAnimationDeviceState mDeviceState;
private final NavBarPosition mNavBarPosition;
@@ -111,6 +116,9 @@
private boolean mPassedWindowMoveSlop;
// Slop used to determine when we say that the gesture has started.
private boolean mPassedPilferInputSlop;
+ // Same as mPassedPilferInputSlop, except when continuing a gesture mPassedPilferInputSlop is
+ // initially true while this one is false.
+ private boolean mPassedSlopOnThisGesture;
// Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
private float mStartDisplacement;
@@ -146,10 +154,12 @@
boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning();
mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
- mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
- float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop;
- mSquaredTouchSlop = slop * slop;
+ float slopMultiplier = mDeviceState.isFullyGesturalNavMode()
+ ? QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL
+ : QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
+ mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
+ mSquaredTouchSlop = slopMultiplier * mTouchSlop * mTouchSlop;
mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
@@ -183,6 +193,10 @@
&& !mRecentsViewDispatcher.hasConsumer()) {
mRecentsViewDispatcher.setConsumer(mInteractionHandler
.getRecentsViewDispatcher(mNavBarPosition.getRotation()));
+ int action = ev.getAction();
+ ev.setAction(ACTION_MOVE_ALLOW_EASY_FLING);
+ mRecentsViewDispatcher.dispatchEvent(ev);
+ ev.setAction(action);
}
int edgeFlags = ev.getEdgeFlags();
ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
@@ -206,7 +220,7 @@
// Start the window animation on down to give more time for launcher to draw if the
// user didn't start the gesture over the back button
if (!mIsDeferredDownTarget) {
- startTouchTrackingForWindowAnimation(ev.getEventTime(), false);
+ startTouchTrackingForWindowAnimation(ev.getEventTime());
}
TraceHelper.INSTANCE.endSection(traceToken);
@@ -243,6 +257,7 @@
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
float displacement = getDisplacement(ev);
float displacementX = mLastPos.x - mDownPos.x;
+ float displacementY = mLastPos.y - mDownPos.y;
if (!mPassedWindowMoveSlop) {
if (!mIsDeferredDownTarget) {
@@ -257,11 +272,21 @@
float horizontalDist = Math.abs(displacementX);
float upDist = -displacement;
- boolean isLikelyToStartNewTask = horizontalDist > upDist;
+ boolean passedSlop = squaredHypot(displacementX, displacementY)
+ >= mSquaredTouchSlop;
+ if (!mPassedSlopOnThisGesture && passedSlop) {
+ mPassedSlopOnThisGesture = true;
+ }
+ // Until passing slop, we don't know what direction we're going, so assume
+ // we're quick switching to avoid translating recents away when continuing
+ // the gesture (in which case mPassedPilferInputSlop starts as true).
+ boolean haveNotPassedSlopOnContinuedGesture =
+ !mPassedSlopOnThisGesture && mPassedPilferInputSlop;
+ boolean isLikelyToStartNewTask = haveNotPassedSlopOnContinuedGesture
+ || horizontalDist > upDist;
if (!mPassedPilferInputSlop) {
- float displacementY = mLastPos.y - mDownPos.y;
- if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
+ if (passedSlop) {
if (mDisableHorizontalSwipe
&& Math.abs(displacementX) > Math.abs(displacementY)) {
// Horizontal gesture is not allowed in this region
@@ -274,15 +299,14 @@
if (mIsDeferredDownTarget) {
// Deferred gesture, start the animation and gesture tracking once
// we pass the actual touch slop
- startTouchTrackingForWindowAnimation(
- ev.getEventTime(), isLikelyToStartNewTask);
+ startTouchTrackingForWindowAnimation(ev.getEventTime());
}
if (!mPassedWindowMoveSlop) {
mPassedWindowMoveSlop = true;
mStartDisplacement = Math.min(displacement, -mTouchSlop);
}
- notifyGestureStarted();
+ notifyGestureStarted(isLikelyToStartNewTask);
}
}
@@ -303,13 +327,20 @@
}
case ACTION_CANCEL:
case ACTION_UP: {
+ if (DEBUG_FAILED_QUICKSWITCH && !mPassedWindowMoveSlop) {
+ float displacementX = mLastPos.x - mDownPos.x;
+ float displacementY = mLastPos.y - mDownPos.y;
+ Log.d("Quickswitch", "mPassedWindowMoveSlop=false"
+ + " disp=" + squaredHypot(displacementX, displacementY)
+ + " slop=" + mSquaredTouchSlop);
+ }
finishTouchTracking(ev);
break;
}
}
}
- private void notifyGestureStarted() {
+ private void notifyGestureStarted(boolean isLikelyToStartNewTask) {
ActiveGestureLog.INSTANCE.addLog("startQuickstep");
if (mInteractionHandler == null) {
return;
@@ -322,26 +353,25 @@
CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
// Notify the handler that the gesture has actually started
- mInteractionHandler.onGestureStarted();
+ mInteractionHandler.onGestureStarted(isLikelyToStartNewTask);
}
- private void startTouchTrackingForWindowAnimation(
- long touchTimeMs, boolean isLikelyToStartNewTask) {
+ private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs,
- mTaskAnimationManager.isRecentsAnimationRunning(), isLikelyToStartNewTask);
+ mTaskAnimationManager.isRecentsAnimationRunning());
mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged);
- mInteractionHandler.initWhenReady();
+ Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
+ mInteractionHandler.initWhenReady(intent);
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
mActiveCallbacks.addListener(mInteractionHandler);
mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
- notifyGestureStarted();
+ notifyGestureStarted(true /*isLikelyToStartNewTask*/);
} else {
- Intent intent = mInteractionHandler.getLaunchIntent();
intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent,
mInteractionHandler);
@@ -393,6 +423,11 @@
}
@Override
+ public void notifyOrientationSetup() {
+ mDeviceState.onStartGesture();
+ }
+
+ @Override
public void onConsumerAboutToBeSwitched() {
Preconditions.assertUIThread();
mMainThreadHandler.removeCallbacks(mCancelRecentsAnimationRunnable);
@@ -430,6 +465,6 @@
@Override
public boolean allowInterceptByParent() {
- return !mPassedPilferInputSlop;
+ return !mPassedPilferInputSlop || mGestureState.hasState(STATE_OVERSCROLL_WINDOW_CREATED);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
index 0a21413..fb420a2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
@@ -24,49 +24,61 @@
import static com.android.launcher3.Utilities.squaredHypot;
+import static java.lang.Math.abs;
+
import android.content.Context;
import android.graphics.PointF;
-import android.view.GestureDetector;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
-import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.plugins.OverscrollPlugin;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
* Input consumer for handling events to pass to an {@code OverscrollPlugin}.
- *
- * @param <T> Draggable activity subclass used by RecentsView
*/
-public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends DelegateInputConsumer {
-
+public class OverscrollInputConsumer extends DelegateInputConsumer {
private static final String TAG = "OverscrollInputConsumer";
+ private static final boolean DEBUG_LOGS_ENABLED = false;
+ private static void debugPrint(String log) {
+ if (DEBUG_LOGS_ENABLED) {
+ Log.v(TAG, log);
+ }
+ }
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private final PointF mStartDragPos = new PointF();
private final int mAngleThreshold;
- private final float mFlingThresholdPx;
+ private final int mFlingDistanceThresholdPx;
+ private final int mFlingVelocityThresholdPx;
private int mActivePointerId = -1;
private boolean mPassedSlop = false;
-
+ // True if we set ourselves as active, meaning we no longer pass events to the delegate.
+ private boolean mPassedActiveThreshold = false;
+ // When a gesture crosses this length, this recognizer will attempt to interpret touch events.
private final float mSquaredSlop;
+ // When a gesture crosses this length, this recognizer will become the sole active recognizer.
+ private final float mSquaredActiveThreshold;
+ // When a gesture crosses this length, the overscroll view should be shown.
+ private final float mSquaredFinishThreshold;
+ private boolean mThisDownIsIgnored = false;
- private final Context mContext;
private final GestureState mGestureState;
@Nullable
private final OverscrollPlugin mPlugin;
- private final GestureDetector mGestureDetector;
+ @Nullable
private RecentsView mRecentsView;
public OverscrollInputConsumer(Context context, GestureState gestureState,
@@ -75,19 +87,24 @@
mAngleThreshold = context.getResources()
.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
- mFlingThresholdPx = context.getResources()
- .getDimension(R.dimen.gestures_overscroll_fling_threshold);
- mContext = context;
+ mFlingDistanceThresholdPx = (int) context.getResources()
+ .getDimension(R.dimen.gestures_overscroll_fling_threshold);
+ mFlingVelocityThresholdPx = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
mGestureState = gestureState;
mPlugin = plugin;
float slop = ViewConfiguration.get(context).getScaledTouchSlop();
mSquaredSlop = slop * slop;
- mGestureDetector = new GestureDetector(context, new FlingGestureListener());
- gestureState.getActivityInterface().createActivityInitListener(this::onActivityInit)
- .register();
+
+ float finishGestureThreshold = (int) context.getResources()
+ .getDimension(R.dimen.gestures_overscroll_finish_threshold);
+ mSquaredFinishThreshold = finishGestureThreshold * finishGestureThreshold;
+
+ float activeThreshold = (int) context.getResources()
+ .getDimension(R.dimen.gestures_overscroll_active_threshold);
+ mSquaredActiveThreshold = activeThreshold * activeThreshold;
}
@Override
@@ -95,20 +112,28 @@
return TYPE_OVERSCROLL | mDelegate.getType();
}
- private boolean onActivityInit(Boolean alreadyOnHome) {
- mRecentsView = mGestureState.getActivityInterface().getCreatedActivity().getOverviewPanel();
-
- return true;
- }
-
@Override
public void onMotionEvent(MotionEvent ev) {
+ if (mPlugin == null) {
+ return;
+ }
+
+ debugPrint("got event, underlying activity is " + getUnderlyingActivity());
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
+ debugPrint("ACTION_DOWN");
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
-
+ if (mPlugin.blockOtherGestures()) {
+ debugPrint("mPlugin.blockOtherGestures(), becoming active on ACTION_DOWN");
+ // Otherwise, if an appear gesture is performed when the Activity is visible,
+ // the Activity will dismiss its keyboard.
+ mPassedActiveThreshold = true;
+ mPassedSlop = true;
+ mStartDragPos.set(mLastPos.x, mLastPos.y);
+ setActive(ev);
+ }
break;
}
case ACTION_POINTER_DOWN: {
@@ -134,79 +159,95 @@
if (mState == STATE_DELEGATE_ACTIVE) {
break;
}
+
if (!mDelegate.allowInterceptByParent()) {
mState = STATE_DELEGATE_ACTIVE;
break;
}
+
+ // Update last touch position.
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
break;
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
- if (!mPassedSlop) {
- // Normal gesture, ensure we pass the slop before we start tracking the gesture
- if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
- > mSquaredSlop) {
-
- mPassedSlop = true;
- mStartDragPos.set(mLastPos.x, mLastPos.y);
- if (isOverscrolled()) {
- setActive(ev);
-
- if (mPlugin != null) {
- mPlugin.onTouchStart(getDeviceState(), getUnderlyingActivity());
- }
- } else {
- mState = STATE_DELEGATE_ACTIVE;
- }
- }
+ float squaredDist = squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y);
+ if ((!mPassedSlop) && (squaredDist > mSquaredSlop)) {
+ mPassedSlop = true;
+ mStartDragPos.set(mLastPos.x, mLastPos.y);
+ mGestureState.setState(GestureState.STATE_OVERSCROLL_WINDOW_CREATED);
}
- if (mPassedSlop && mState != STATE_DELEGATE_ACTIVE && isOverscrolled()
- && mPlugin != null) {
- mPlugin.onTouchTraveled(getDistancePx());
+ boolean becomeActive = mPassedSlop && !mPassedActiveThreshold && isOverscrolled()
+ && (squaredDist > mSquaredActiveThreshold);
+ if (becomeActive) {
+ debugPrint("Past slop and past threshold, set active");
+ mPassedActiveThreshold = true;
+ setActive(ev);
+ }
+
+ if (mPassedActiveThreshold) {
+ debugPrint("ACTION_MOVE Relaying touch event");
+ mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(),
+ (int) Math.sqrt(mSquaredFinishThreshold), mFlingDistanceThresholdPx,
+ mFlingVelocityThresholdPx, getDeviceState(), getUnderlyingActivity());
}
break;
}
case ACTION_CANCEL:
case ACTION_UP:
- if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) {
- mPlugin.onTouchEnd(getDistancePx());
+ debugPrint("ACTION_UP");
+ if (mPassedActiveThreshold) {
+ debugPrint("ACTION_UP Relaying touch event");
+
+ mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(),
+ (int) Math.sqrt(mSquaredFinishThreshold), mFlingDistanceThresholdPx,
+ mFlingVelocityThresholdPx, getDeviceState(), getUnderlyingActivity());
}
+
mPassedSlop = false;
+ mPassedActiveThreshold = false;
mState = STATE_INACTIVE;
break;
}
- if (mState != STATE_DELEGATE_ACTIVE) {
- mGestureDetector.onTouchEvent(ev);
- }
-
if (mState != STATE_ACTIVE) {
mDelegate.onMotionEvent(ev);
}
}
private boolean isOverscrolled() {
+ if (mRecentsView == null) {
+ BaseDraggingActivity activity = mGestureState.getActivityInterface()
+ .getCreatedActivity();
+ if (activity != null) {
+ mRecentsView = activity.getOverviewPanel();
+ }
+ }
+
// Make sure there isn't an app to quick switch to on our right
int maxIndex = 0;
- if ((mRecentsView instanceof LauncherRecentsView)
- && ((LauncherRecentsView) mRecentsView).hasRecentsExtraCard()) {
+ if (mRecentsView != null && mRecentsView.hasRecentsExtraCard()) {
maxIndex = 1;
}
- boolean atRightMostApp = (mRecentsView == null
- || mRecentsView.getRunningTaskIndex() <= maxIndex);
+ boolean atRightMostApp = mRecentsView == null
+ || (mRecentsView.getRunningTaskIndex() <= maxIndex);
// Check if the gesture is within our angle threshold of horizontal
- float deltaY = Math.abs(mLastPos.y - mDownPos.y);
- float deltaX = mDownPos.x - mLastPos.x; // Positive if this is a gesture to the left
- boolean angleInBounds = Math.toDegrees(Math.atan2(deltaY, deltaX)) < mAngleThreshold;
+ float deltaY = abs(mLastPos.y - mDownPos.y);
+ float deltaX = mLastPos.x - mDownPos.x;
- return atRightMostApp && angleInBounds;
+ boolean angleInBounds = (Math.toDegrees(Math.atan2(deltaY, abs(deltaX))) < mAngleThreshold);
+
+ boolean overscrollVisible = mPlugin.blockOtherGestures();
+ boolean overscrollInvisibleAndLeftSwipe = !overscrollVisible && deltaX < 0;
+ boolean gestureDirectionMatchesVisibility = overscrollVisible
+ || overscrollInvisibleAndLeftSwipe;
+ return atRightMostApp && angleInBounds && gestureDirectionMatchesVisibility;
}
private String getDeviceState() {
@@ -225,35 +266,22 @@
return deviceState;
}
- private int getDistancePx() {
- return (int) Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y);
+ private int getHorizontalDistancePx() {
+ return (int) (mLastPos.x - mDownPos.x);
}
- private String getUnderlyingActivity() {
+ private int getVerticalDistancePx() {
+ return (int) (mLastPos.y - mDownPos.y);
+ }
+
+ private @NonNull String getUnderlyingActivity() {
+ // Overly defensive, got guidance on code review that something in the chain of
+ // `mGestureState.getRunningTask().topActivity` can be null and thus cause a null pointer
+ // exception to be thrown, but we aren't sure which part can be null.
+ if ((mGestureState == null) || (mGestureState.getRunningTask() == null)
+ || (mGestureState.getRunningTask().topActivity == null)) {
+ return "";
+ }
return mGestureState.getRunningTask().topActivity.flattenToString();
}
-
- private class FlingGestureListener extends GestureDetector.SimpleOnGestureListener {
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- if (isValidAngle(velocityX, -velocityY)
- && getDistancePx() >= mFlingThresholdPx
- && mState != STATE_DELEGATE_ACTIVE) {
-
- if (mPlugin != null) {
- mPlugin.onFling(-velocityX);
- }
- }
- return true;
- }
-
- private boolean isValidAngle(float deltaX, float deltaY) {
- float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
- // normalize so that angle is measured clockwise from horizontal in the bottom right
- // corner and counterclockwise from horizontal in the bottom left corner
-
- angle = angle > 90 ? 180 - angle : angle;
- return (angle < mAngleThreshold);
- }
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index d3160b3..9bfe84f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -18,13 +18,14 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.BaseDragLayer;
@@ -35,22 +36,18 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputMonitorCompat;
-import java.util.function.Predicate;
-
/**
* Input consumer for handling touch on the recents/Launcher activity.
*/
-public class OverviewInputConsumer<T extends BaseDraggingActivity>
+public class OverviewInputConsumer<T extends StatefulActivity<?>>
implements InputConsumer {
private final T mActivity;
- private final BaseActivityInterface<T> mActivityInterface;
+ private final BaseActivityInterface<?, T> mActivityInterface;
private final BaseDragLayer mTarget;
private final InputMonitorCompat mInputMonitor;
private final int[] mLocationOnScreen = new int[2];
- private final boolean mProxyTouch;
- private final Predicate<MotionEvent> mEventReceiver;
private final boolean mStartingInActivityBounds;
private boolean mTargetHandledTouch;
@@ -63,15 +60,7 @@
mActivityInterface = gestureState.getActivityInterface();
mTarget = activity.getDragLayer();
- if (startingInActivityBounds) {
- mEventReceiver = mTarget::dispatchTouchEvent;
- mProxyTouch = true;
- } else {
- // Only proxy touches to controllers if we are starting touch from nav bar.
- mEventReceiver = mTarget::proxyTouchEvent;
- mTarget.getLocationOnScreen(mLocationOnScreen);
- mProxyTouch = mTarget.prepareProxyEventStarting();
- }
+ mTarget.getLocationOnScreen(mLocationOnScreen);
}
@Override
@@ -86,16 +75,15 @@
@Override
public void onMotionEvent(MotionEvent ev) {
- if (!mProxyTouch) {
- return;
- }
-
int flags = ev.getEdgeFlags();
if (!mStartingInActivityBounds) {
ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
}
ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
- boolean handled = mEventReceiver.test(ev);
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "OverviewInputConsumer");
+ }
+ boolean handled = mTarget.proxyTouchEvent(ev, mStartingInActivityBounds);
ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
ev.setEdgeFlags(flags);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index ac1c3a8..d972c0f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -15,15 +15,20 @@
*/
package com.android.quickstep.inputconsumers;
+import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+
import android.content.Context;
import android.content.Intent;
+import android.graphics.PointF;
import android.view.MotionEvent;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.logging.StatsLogUtils;
+import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.quickstep.GestureState;
@@ -33,19 +38,22 @@
import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
import com.android.systemui.shared.system.InputMonitorCompat;
-public class OverviewWithoutFocusInputConsumer implements InputConsumer {
+public class OverviewWithoutFocusInputConsumer implements InputConsumer,
+ TriggerSwipeUpTouchTracker.OnSwipeUpListener {
private final Context mContext;
private final InputMonitorCompat mInputMonitor;
private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
+ private final GestureState mGestureState;
public OverviewWithoutFocusInputConsumer(Context context,
RecentsAnimationDeviceState deviceState, GestureState gestureState,
InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) {
mContext = context;
+ mGestureState = gestureState;
mInputMonitor = inputMonitor;
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, disableHorizontalSwipe,
- deviceState.getNavBarPosition(), this::onInterceptTouch, this::onSwipeUp);
+ deviceState.getNavBarPosition(), this::onInterceptTouch, this);
}
@Override
@@ -70,7 +78,8 @@
}
}
- private void onSwipeUp(boolean wasFling) {
+ @Override
+ public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
mContext.startActivity(new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
@@ -78,9 +87,23 @@
BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
int pageIndex = -1; // This number doesn't reflect workspace page index.
// It only indicates that launcher client screen was shown.
- int containerType = StatsLogUtils.getContainerTypeFromState(activity.getCurrentState());
+ int containerType = (mGestureState != null && mGestureState.getEndTarget() != null)
+ ? mGestureState.getEndTarget().containerType
+ : LauncherLogProto.ContainerType.WORKSPACE;
activity.getUserEventDispatcher().logActionOnContainer(
wasFling ? Touch.FLING : Touch.SWIPE, Direction.UP, containerType, pageIndex);
activity.getUserEventDispatcher().setPreviousHomeGesture(true);
+ activity.getStatsLogManager().logger()
+ .withSrcState(LAUNCHER_STATE_HOME)
+ .withDstState(LAUNCHER_STATE_HOME)
+ .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setWorkspace(
+ LauncherAtom.WorkspaceContainer.newBuilder()
+ .setPageIndex(-1))
+ .build())
+ .log(LAUNCHER_HOME_GESTURE);
}
+
+ @Override
+ public void onSwipeUpCancelled() {}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
new file mode 100644
index 0000000..3f833c0
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
@@ -0,0 +1,86 @@
+/*
+ * 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.inputconsumers;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Input consumer used when a fullscreen System UI overlay is showing (such as the expanded Bubbles
+ * UI).
+ *
+ * This responds to swipes up by sending a closeSystemDialogs broadcast (causing overlays to close)
+ * rather than closing the app behind the overlay and sending the user all the way home.
+ */
+public class SysUiOverlayInputConsumer implements InputConsumer,
+ TriggerSwipeUpTouchTracker.OnSwipeUpListener {
+
+ private final Context mContext;
+ private final InputMonitorCompat mInputMonitor;
+ private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
+
+ public SysUiOverlayInputConsumer(
+ Context context,
+ RecentsAnimationDeviceState deviceState,
+ InputMonitorCompat inputMonitor) {
+ mContext = context;
+ mInputMonitor = inputMonitor;
+ mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, true,
+ deviceState.getNavBarPosition(), this::onInterceptTouch, this);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_SYSUI_OVERLAY;
+ }
+
+ @Override
+ public boolean allowInterceptByParent() {
+ return !mTriggerSwipeUpTracker.interceptedTouch();
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ mTriggerSwipeUpTracker.onMotionEvent(ev);
+ }
+
+ private void onInterceptTouch() {
+ if (mInputMonitor != null) {
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
+ mInputMonitor.pilferPointers();
+ }
+ }
+
+ @Override
+ public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
+ // Close system dialogs when a swipe up is detected.
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ }
+
+ @Override
+ public void onSwipeUpCancelled() {
+
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
index b9ef57e..e000803 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
@@ -19,8 +19,8 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import java.util.ArrayList;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
deleted file mode 100644
index d9e9cc7..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ /dev/null
@@ -1,582 +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.quickstep.util;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Matrix;
-import android.graphics.Matrix.ScaleToFit;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.Build;
-import android.view.Surface;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.model.PagedViewOrientedState;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.RemoteAnimationTargets;
-import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailView;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.utilities.RectFEvaluator;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-import com.android.systemui.shared.system.TransactionCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
-import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
-
-/**
- * Utility class to handle window clip animation
- */
-@TargetApi(Build.VERSION_CODES.P)
-public class AppWindowAnimationHelper {
-
- // The bounds of the source app in device coordinates
- private final Rect mSourceStackBounds = new Rect();
- // The insets of the source app
- private final Rect mSourceInsets = new Rect();
- // The source app bounds with the source insets applied, in the device coordinates
- private final RectF mSourceRect = new RectF();
- // The bounds of the task view in device coordinates
- private final RectF mTargetRect = new RectF();
- // The bounds of the app window (between mSourceRect and mTargetRect) in device coordinates
- private final RectF mCurrentRect = new RectF();
- // The insets to be used for clipping the app window, which can be larger than mSourceInsets
- // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
- // app window coordinates.
- private final RectF mSourceWindowClipInsets = new RectF();
- // The clip rect in source app window coordinates. The app window surface will only be drawn
- // within these bounds. This clip rect starts at the full mSourceStackBounds, and insets by
- // mSourceWindowClipInsets as the transform progress goes to 1.
- private final RectF mCurrentClipRectF = new RectF();
-
- // The bounds of launcher (not including insets) in device coordinates
- public final Rect mHomeStackBounds = new Rect();
- private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
- private final Matrix mTmpMatrix = new Matrix();
- private final Rect mTmpRect = new Rect();
- private final RectF mTmpRectF = new RectF();
- private final RectF mCurrentRectWithInsets = new RectF();
- private PagedViewOrientedState mOrientedState;
- // Corner radius of windows, in pixels
- private final float mWindowCornerRadius;
- // Corner radius of windows when they're in overview mode.
- private final float mTaskCornerRadius;
- // If windows can have real time rounded corners.
- private final boolean mSupportsRoundedCornersOnWindows;
- // Whether or not to actually use the rounded cornders on windows
- private boolean mUseRoundedCornersOnWindows;
-
- // Corner radius currently applied to transformed window.
- private float mCurrentCornerRadius;
-
- // Whether to boost the opening animation target layers, or the closing
- private int mBoostModeTargetLayers = -1;
-
- private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
- private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1;
-
- public AppWindowAnimationHelper(PagedViewOrientedState orientedState, Context context) {
- Resources res = context.getResources();
- mOrientedState = orientedState;
- mWindowCornerRadius = getWindowCornerRadius(res);
- mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(res);
- mTaskCornerRadius = TaskCornerRadius.get(context);
- mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows;
- }
-
- public AppWindowAnimationHelper(Context context) {
- this(null, context);
- }
-
- private void updateSourceStack(RemoteAnimationTargetCompat target) {
- mSourceInsets.set(target.contentInsets);
- mSourceStackBounds.set(target.screenSpaceBounds);
-
- // TODO: Should sourceContainerBounds already have this offset?
- mSourceStackBounds.offsetTo(target.position.x, target.position.y);
- }
-
- public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
- updateSourceStack(target);
- updateHomeBounds(homeStackBounds);
- }
-
- public void updateHomeBounds(Rect homeStackBounds) {
- mHomeStackBounds.set(homeStackBounds);
- }
-
- public void updateTargetRect(Rect targetRect) {
- mSourceRect.set(mSourceInsets.left, mSourceInsets.top,
- mSourceStackBounds.width() - mSourceInsets.right,
- mSourceStackBounds.height() - mSourceInsets.bottom);
- mTargetRect.set(targetRect);
- mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
- mHomeStackBounds.top - mSourceStackBounds.top);
-
- // Calculate the clip based on the target rect (since the content insets and the
- // launcher insets may differ, so the aspect ratio of the target rect can differ
- // from the source rect. The difference between the target rect (scaled to the
- // source rect) is the amount to clip on each edge.
- RectF scaledTargetRect = new RectF(mTargetRect);
- float scale = getSrcToTargetScale();
- Utilities.scaleRectFAboutCenter(scaledTargetRect, scale);
-
- scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
- mSourceWindowClipInsets.set(
- Math.max(scaledTargetRect.left, 0),
- Math.max(scaledTargetRect.top, 0),
- Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
- Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
- mSourceRect.set(scaledTargetRect);
- }
-
- private float getSrcToTargetScale() {
- if (mOrientedState == null ||
- (mOrientedState.getDisplayRotation() == Surface.ROTATION_0
- || mOrientedState.getDisplayRotation() == Surface.ROTATION_180)) {
- return mSourceRect.width() / mTargetRect.width();
- } else {
- return mSourceRect.height() / mTargetRect.height();
- }
- }
-
- public void prepareAnimation(DeviceProfile dp, boolean isOpening) {
- mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING;
- mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows && !dp.isMultiWindowMode;
- }
-
- public RectF applyTransform(TransformParams params) {
- SurfaceParams[] surfaceParams = computeSurfaceParams(params);
- if (surfaceParams == null) {
- return null;
- }
- applySurfaceParams(params.mSyncTransactionApplier, surfaceParams);
- return mCurrentRect;
- }
-
- /**
- * Updates this AppWindowAnimationHelper's state based on the given TransformParams, and returns
- * the SurfaceParams to apply via {@link SyncRtSurfaceTransactionApplierCompat#applyParams}.
- */
- public SurfaceParams[] computeSurfaceParams(TransformParams params) {
- if (params.mTargetSet == null) {
- return null;
- }
-
- float progress = Utilities.boundToRange(params.mProgress, 0, 1);
- updateCurrentRect(params);
-
- SurfaceParams[] surfaceParams = new SurfaceParams[params.mTargetSet.unfilteredApps.length];
- for (int i = 0; i < params.mTargetSet.unfilteredApps.length; i++) {
- RemoteAnimationTargetCompat app = params.mTargetSet.unfilteredApps[i];
- SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash);
- if (app.localBounds != null) {
- mTmpMatrix.setTranslate(0, 0);
- if (app.activityType == ACTIVITY_TYPE_HOME && app.mode == MODE_CLOSING) {
- mTmpMatrix.setTranslate(app.localBounds.left, app.localBounds.top);
- }
- } else {
- mTmpMatrix.setTranslate(app.position.x, app.position.y);
- }
-
- Rect crop = mTmpRect;
- crop.set(app.screenSpaceBounds);
- crop.offsetTo(0, 0);
- float alpha;
- float cornerRadius = 0f;
- float scale = Math.max(mCurrentRect.width(), mTargetRect.width()) / crop.width();
- int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
- if (app.mode == params.mTargetSet.targetMode) {
- alpha = mTaskAlphaCallback.getAlpha(app, params.mTargetAlpha);
- if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
- mTmpMatrix.setRectToRect(mSourceRect, mCurrentRect, ScaleToFit.FILL);
- if (app.localBounds != null) {
- mTmpMatrix.postTranslate(app.localBounds.left, app.localBounds.top);
- } else {
- mTmpMatrix.postTranslate(app.position.x, app.position.y);
- }
- mCurrentClipRectF.roundOut(crop);
- if (mSupportsRoundedCornersOnWindows) {
- if (params.mCornerRadius > -1) {
- cornerRadius = params.mCornerRadius;
- scale = mCurrentRect.width() / crop.width();
- } else {
- float windowCornerRadius = mUseRoundedCornersOnWindows
- ? mWindowCornerRadius : 0;
- cornerRadius = Utilities.mapRange(progress, windowCornerRadius,
- mTaskCornerRadius);
- }
- mCurrentCornerRadius = cornerRadius;
- }
- // Fade out Assistant overlay.
- if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT
- && app.isNotInRecents) {
- alpha = 1 - Interpolators.DEACCEL_2_5.getInterpolation(progress);
- }
- } else if (params.mTargetSet.hasRecents) {
- // If home has a different target then recents, reverse anim the
- // home target.
- alpha = 1 - (progress * params.mTargetAlpha);
- }
- } else {
- alpha = mBaseAlphaCallback.getAlpha(app, progress);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.mLauncherOnTop) {
- crop = null;
- layer = Integer.MAX_VALUE;
- }
- }
- builder.withAlpha(alpha)
- .withMatrix(mTmpMatrix)
- .withWindowCrop(crop)
- .withLayer(layer)
- // Since radius is in Surface space, but we draw the rounded corners in screen
- // space, we have to undo the scale
- .withCornerRadius(cornerRadius / scale);
- surfaceParams[i] = builder.build();
- }
- return surfaceParams;
- }
-
- public RectF updateCurrentRect(TransformParams params) {
- if (params.mCurrentRect != null) {
- mCurrentRect.set(params.mCurrentRect);
- } else {
- mTmpRectF.set(mTargetRect);
- Utilities.scaleRectFAboutCenter(mTmpRectF, params.mOffsetScale);
- mCurrentRect.set(mRectFEvaluator.evaluate(params.mProgress, mSourceRect, mTmpRectF));
- if (mOrientedState == null || mOrientedState.areMultipleLayoutOrientationsDisabled()) {
- mCurrentRect.offset(params.mOffset, 0);
- } else {
- int displayRotation = mOrientedState.getDisplayRotation();
- mOrientedState.getOrientationHandler().offsetTaskRect(mCurrentRect,
- params.mOffset, displayRotation);
- }
- }
-
- updateClipRect(params);
- return mCurrentRect;
- }
-
- private void updateClipRect(TransformParams params) {
- // Don't clip past progress > 1.
- float progress = Math.min(1, params.mProgress);
- mCurrentClipRectF.left = mSourceWindowClipInsets.left * progress;
- mCurrentClipRectF.top = mSourceWindowClipInsets.top * progress;
- mCurrentClipRectF.right =
- mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress);
- mCurrentClipRectF.bottom =
- mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress);
- }
-
- public RectF getCurrentRectWithInsets() {
- mTmpMatrix.mapRect(mCurrentRectWithInsets, mCurrentClipRectF);
- return mCurrentRectWithInsets;
- }
-
- public static void applySurfaceParams(@Nullable SyncRtSurfaceTransactionApplierCompat
- syncTransactionApplier, SurfaceParams[] params) {
- if (syncTransactionApplier != null) {
- syncTransactionApplier.scheduleApply(params);
- } else {
- TransactionCompat t = new TransactionCompat();
- for (SurfaceParams param : params) {
- SyncRtSurfaceTransactionApplierCompat.applyParams(t, param);
- }
- t.setEarlyWakeup();
- t.apply();
- }
- }
-
- public void setTaskAlphaCallback(TargetAlphaProvider callback) {
- mTaskAlphaCallback = callback;
- }
-
- public void setBaseAlphaCallback(TargetAlphaProvider callback) {
- mBaseAlphaCallback = callback;
- }
-
- public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) {
- fromTaskThumbnailView(ttv, rv, null);
- }
-
- public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv,
- @Nullable RemoteAnimationTargetCompat target) {
- BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext());
- BaseDragLayer dl = activity.getDragLayer();
-
- int[] pos = new int[2];
- dl.getLocationOnScreen(pos);
- mHomeStackBounds.set(0, 0, dl.getWidth(), dl.getHeight());
- mHomeStackBounds.offset(pos[0], pos[1]);
-
- if (target != null) {
- updateSourceStack(target);
- } else if (rv.shouldUseMultiWindowTaskSizeStrategy()) {
- updateStackBoundsToMultiWindowTaskSize(activity);
- } else {
- mSourceStackBounds.set(mHomeStackBounds);
- Rect fallback = dl.getInsets();
- mSourceInsets.set(ttv.getInsets(fallback));
- }
-
- Rect targetRect = new Rect();
- dl.getDescendantRectRelativeToSelf(ttv, targetRect);
- updateTargetRect(targetRect);
-
- if (target == null) {
- // Transform the clip relative to the target rect. Only do this in the case where we
- // aren't applying the insets to the app windows (where the clip should be in target app
- // space)
- float scale = mTargetRect.width() / mSourceRect.width();
- mSourceWindowClipInsets.left = mSourceWindowClipInsets.left * scale;
- mSourceWindowClipInsets.top = mSourceWindowClipInsets.top * scale;
- mSourceWindowClipInsets.right = mSourceWindowClipInsets.right * scale;
- mSourceWindowClipInsets.bottom = mSourceWindowClipInsets.bottom * scale;
- }
- }
-
- /**
- * Compute scale and translation y such that the specified task view fills the screen.
- */
- public AppWindowAnimationHelper updateForFullscreenOverview(TaskView v) {
- TaskThumbnailView thumbnailView = v.getThumbnail();
- RecentsView recentsView = v.getRecentsView();
- fromTaskThumbnailView(thumbnailView, recentsView);
- Rect taskSize = new Rect();
- recentsView.getTaskSize(taskSize);
- updateTargetRect(taskSize);
- return this;
- }
-
- /**
- * @return The source rect's scale and translation relative to the target rect.
- */
- public LauncherState.ScaleAndTranslation getScaleAndTranslation() {
- float scale = getSrcToTargetScale();
- float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY();
- return new LauncherState.ScaleAndTranslation(scale, 0, translationY);
- }
-
- private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
- SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(activity);
- if (proxy.isActive()) {
- mSourceStackBounds.set(proxy.getNonMinimizedSplitScreenSecondaryBounds());
- return;
- }
-
- // Assume that the task size is half screen size (minus the insets and the divider size)
- DeviceProfile fullDp = activity.getDeviceProfile().getFullScreenProfile();
- // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
- // account for system insets
- int taskWidth = fullDp.availableWidthPx;
- int taskHeight = fullDp.availableHeightPx;
- int halfDividerSize = activity.getResources()
- .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
-
- Rect insets = new Rect();
- WindowManagerWrapper.getInstance().getStableInsets(insets);
- if (fullDp.isLandscape) {
- taskWidth = taskWidth / 2 - halfDividerSize;
- } else {
- taskHeight = taskHeight / 2 - halfDividerSize;
- }
-
- // Align the task to bottom left/right edge (closer to nav bar).
- int left = activity.getDeviceProfile().isSeascape() ? insets.left
- : (insets.left + fullDp.availableWidthPx - taskWidth);
- mSourceStackBounds.set(0, 0, taskWidth, taskHeight);
- mSourceStackBounds.offset(left, insets.top + fullDp.availableHeightPx - taskHeight);
- }
-
- public RectF getTargetRect() {
- return mTargetRect;
- }
-
- public float getCurrentCornerRadius() {
- return mCurrentCornerRadius;
- }
-
- public interface TargetAlphaProvider {
- float getAlpha(RemoteAnimationTargetCompat target, float expectedAlpha);
- }
-
- public static class TransformParams {
- private float mProgress;
- private float mOffset;
- private float mOffsetScale;
- private @Nullable RectF mCurrentRect;
- private float mTargetAlpha;
- private float mCornerRadius;
- private boolean mLauncherOnTop;
- private RemoteAnimationTargets mTargetSet;
- private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
-
- public TransformParams() {
- mProgress = 0;
- mOffset = 0;
- mOffsetScale = 1;
- mCurrentRect = null;
- mTargetAlpha = 1;
- mCornerRadius = -1;
- mLauncherOnTop = false;
- }
-
- /**
- * Sets the progress of the transformation, where 0 is the source and 1 is the target. We
- * automatically adjust properties such as currentRect and cornerRadius based on this
- * progress, unless they are manually overridden by setting them on this TransformParams.
- */
- public TransformParams setProgress(float progress) {
- mProgress = progress;
- return this;
- }
-
- /**
- * Sets the corner radius of the transformed window, in pixels. If unspecified (-1), we
- * simply interpolate between the window's corner radius to the task view's corner radius,
- * based on {@link #mProgress}.
- */
- public TransformParams setCornerRadius(float cornerRadius) {
- mCornerRadius = cornerRadius;
- return this;
- }
-
- /**
- * Sets the current rect to show the transformed window, in device coordinates. This gives
- * the caller manual control of where to show the window. If unspecified (null), we
- * interpolate between {@link AppWindowAnimationHelper#mSourceRect} and
- * {@link AppWindowAnimationHelper#mTargetRect}, based on {@link #mProgress}.
- */
- public TransformParams setCurrentRect(RectF currentRect) {
- mCurrentRect = currentRect;
- return this;
- }
-
- /**
- * Specifies the alpha of the transformed window. Default is 1.
- */
- public TransformParams setTargetAlpha(float targetAlpha) {
- mTargetAlpha = targetAlpha;
- return this;
- }
-
- /**
- * If {@link #mCurrentRect} is null (i.e. {@link #setCurrentRect(RectF)} hasn't overridden
- * the default), then offset the current rect by this amount after computing the rect based
- * on {@link #mProgress}.
- */
- public TransformParams setOffset(float offset) {
- mOffset = offset;
- return this;
- }
-
- /**
- * If {@link #mCurrentRect} is null (i.e. {@link #setCurrentRect(RectF)} hasn't overridden
- * the default), then scale the current rect by this amount after computing the rect based
- * on {@link #mProgress}.
- */
- public TransformParams setOffsetScale(float offsetScale) {
- mOffsetScale = offsetScale;
- return this;
- }
-
- /**
- * If true, sets the crop = null and layer = Integer.MAX_VALUE for targets that don't match
- * {@link #mTargetSet}.targetMode. (Currently only does this when live tiles are enabled.)
- */
- public TransformParams setLauncherOnTop(boolean launcherOnTop) {
- mLauncherOnTop = launcherOnTop;
- return this;
- }
-
- /**
- * Specifies the set of RemoteAnimationTargetCompats that are included in the transformation
- * that these TransformParams help compute. These TransformParams generally only apply to
- * the targetSet.apps which match the targetSet.targetMode (e.g. the MODE_CLOSING app when
- * swiping to home).
- */
- public TransformParams setTargetSet(RemoteAnimationTargets targetSet) {
- mTargetSet = targetSet;
- return this;
- }
-
- /**
- * Sets the SyncRtSurfaceTransactionApplierCompat that will apply the SurfaceParams that
- * are computed based on these TransformParams.
- */
- public TransformParams setSyncTransactionApplier(
- SyncRtSurfaceTransactionApplierCompat applier) {
- mSyncTransactionApplier = applier;
- return this;
- }
-
- // Pubic getters so outside packages can read the values.
-
- public float getProgress() {
- return mProgress;
- }
-
- public float getOffset() {
- return mOffset;
- }
-
- public float getOffsetScale() {
- return mOffsetScale;
- }
-
- @Nullable
- public RectF getCurrentRect() {
- return mCurrentRect;
- }
-
- public float getTargetAlpha() {
- return mTargetAlpha;
- }
-
- public float getCornerRadius() {
- return mCornerRadius;
- }
-
- public boolean isLauncherOnTop() {
- return mLauncherOnTop;
- }
-
- public RemoteAnimationTargets getTargetSet() {
- return mTargetSet;
- }
-
- public SyncRtSurfaceTransactionApplierCompat getSyncTransactionApplier() {
- return mSyncTransactionApplier;
- }
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
new file mode 100644
index 0000000..5b0d503
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
@@ -0,0 +1,67 @@
+/*
+ * 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.util;
+
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+
+import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.quickstep.views.RecentsView;
+
+public class RecentsAtomicAnimationFactory<ACTIVITY_TYPE extends StatefulActivity, STATE_TYPE>
+ extends AtomicAnimationFactory<STATE_TYPE> {
+
+ public static final int INDEX_RECENTS_FADE_ANIM = AtomicAnimationFactory.NEXT_INDEX + 0;
+ public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = AtomicAnimationFactory.NEXT_INDEX + 1;
+
+ private static final int MY_ANIM_COUNT = 2;
+ protected static final int NEXT_INDEX = AtomicAnimationFactory.NEXT_INDEX + MY_ANIM_COUNT;
+
+ protected final ACTIVITY_TYPE mActivity;
+
+ /**
+ * @param extraAnims number of animations supported by the subclass. This should not include
+ * the 2 animations supported by this class.
+ */
+ public RecentsAtomicAnimationFactory(ACTIVITY_TYPE activity, int extraAnims) {
+ super(MY_ANIM_COUNT + extraAnims);
+ mActivity = activity;
+ }
+
+ @Override
+ public Animator createStateElementAnimation(int index, float... values) {
+ switch (index) {
+ case INDEX_RECENTS_FADE_ANIM:
+ return ObjectAnimator.ofFloat(mActivity.getOverviewPanel(),
+ RecentsView.CONTENT_ALPHA, values);
+ case INDEX_RECENTS_TRANSLATE_X_ANIM: {
+ RecentsView rv = mActivity.getOverviewPanel();
+ return new SpringAnimationBuilder(mActivity)
+ .setMinimumVisibleChange(1f / rv.getPageOffsetScale())
+ .setDampingRatio(0.8f)
+ .setStiffness(250)
+ .setValues(values)
+ .build(rv, ADJACENT_PAGE_OFFSET);
+ }
+ default:
+ return super.createStateElementAnimation(index, values);
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index dde7605..e5d2c53 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -29,6 +29,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.FlingSpringAnim;
import com.android.launcher3.util.DynamicResource;
+import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck;
import com.android.systemui.plugins.ResourceProvider;
import java.util.ArrayList;
@@ -39,7 +40,7 @@
* Applies spring forces to animate from a starting rect to a target rect,
* while providing update callbacks to the caller.
*/
-public class RectFSpringAnim {
+public class RectFSpringAnim extends ReleaseCheck {
private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X =
new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") {
@@ -116,6 +117,7 @@
ResourceProvider rp = DynamicResource.provider(context);
mMinVisChange = rp.getDimension(R.dimen.swipe_up_fling_min_visible_change);
mYOvershoot = rp.getDimension(R.dimen.swipe_up_y_overshoot);
+ setCanRelease(true);
}
public void onTargetPositionChanged() {
@@ -190,10 +192,12 @@
maybeOnEnd();
});
+ setCanRelease(false);
+ mAnimsStarted = true;
+
mRectXAnim.start();
mRectYAnim.start();
mRectScaleAnim.start();
- mAnimsStarted = true;
for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
animatorListener.onAnimationStart(null);
}
@@ -207,9 +211,23 @@
mRectScaleAnim.skipToEnd();
}
}
+ mRectXAnimEnded = true;
+ mRectYAnimEnded = true;
+ mRectScaleAnimEnded = true;
+ maybeOnEnd();
+ }
+
+ private boolean isEnded() {
+ return mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded;
}
private void onUpdate() {
+ if (isEnded()) {
+ // Prevent further updates from being called. This can happen between callbacks for
+ // ending the x/y/scale animations.
+ return;
+ }
+
if (!mOnUpdateListeners.isEmpty()) {
float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(),
mTargetRect.width());
@@ -229,8 +247,9 @@
}
private void maybeOnEnd() {
- if (mAnimsStarted && mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) {
+ if (mAnimsStarted && isEnded()) {
mAnimsStarted = false;
+ setCanRelease(true);
for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
animatorListener.onAnimationEnd(null);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
index 217eca5..85006da 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -15,10 +15,10 @@
*/
package com.android.quickstep.util;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_SHELF_ANIM;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 9cf45b3..3cafd42 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -20,16 +20,20 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.view.View;
import android.view.ViewGroup;
+import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
@@ -38,8 +42,10 @@
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.DynamicResource;
import com.android.quickstep.views.RecentsView;
@@ -63,7 +69,7 @@
private final AnimatorSet mAnimators = new AnimatorSet();
public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim) {
- prepareToAnimate(launcher);
+ prepareToAnimate(launcher, animateOverviewScrim);
mVelocity = velocity;
@@ -117,15 +123,21 @@
addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows);
}
- View qsb = launcher.findViewById(R.id.search_container_all_apps);
- addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows);
+ if (launcher.getAppsView().getSearchUiManager()
+ .isQsbVisible(NORMAL.getVisibleElements(launcher))) {
+ addStaggeredAnimationForView(launcher.getAppsView().getSearchView(),
+ grid.inv.numRows + 2, totalRows);
+ }
}
if (animateOverviewScrim) {
- addScrimAnimationForState(launcher, BACKGROUND_APP, 0);
- addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+ PendingAnimation pendingAnimation = new PendingAnimation(ALPHA_DURATION_MS);
+ addScrimAnimationForState(launcher, NORMAL, pendingAnimation);
+ mAnimators.play(pendingAnimation.buildAnim());
}
+ addDepthAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+
mAnimators.play(launcher.getDragLayer().getScrim().createSysuiMultiplierAnim(0f, 1f)
.setDuration(ALPHA_DURATION_MS));
mAnimators.addListener(new AnimatorListenerAdapter() {
@@ -144,21 +156,30 @@
/**
* Setup workspace with 0 duration to prepare for our staggered animation.
*/
- private void prepareToAnimate(Launcher launcher) {
+ private void prepareToAnimate(Launcher launcher, boolean animateOverviewScrim) {
StateAnimationConfig config = new StateAnimationConfig();
- config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW;
+ config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER;
config.duration = 0;
// setRecentsAttachedToAppWindow() will animate recents out.
launcher.getStateManager().createAtomicAnimation(BACKGROUND_APP, NORMAL, config).start();
// Stop scrolling so that it doesn't interfere with the translation offscreen.
launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
+
+ if (animateOverviewScrim) {
+ addScrimAnimationForState(launcher, BACKGROUND_APP, NO_ANIM_PROPERTY_SETTER);
+ }
}
public AnimatorSet getAnimators() {
return mAnimators;
}
+ public StaggeredWorkspaceAnim addAnimatorListener(Animator.AnimatorListener listener) {
+ mAnimators.addListener(listener);
+ return this;
+ }
+
/**
* Starts the animation.
*/
@@ -184,14 +205,21 @@
ResourceProvider rp = DynamicResource.provider(v.getContext());
float stiffness = rp.getFloat(R.dimen.staggered_stiffness);
float damping = rp.getFloat(R.dimen.staggered_damping_ratio);
- ObjectAnimator springTransY = new SpringAnimationBuilder<>(v, VIEW_TRANSLATE_Y)
+ ValueAnimator springTransY = new SpringAnimationBuilder(v.getContext())
.setStiffness(stiffness)
.setDampingRatio(damping)
.setMinimumVisibleChange(1f)
+ .setStartValue(mSpringTransY)
.setEndValue(0)
.setStartVelocity(mVelocity)
- .build(v.getContext());
+ .build(v, VIEW_TRANSLATE_Y);
springTransY.setStartDelay(startDelay);
+ springTransY.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setTranslationY(0f);
+ }
+ });
mAnimators.play(springTransY);
v.setAlpha(0);
@@ -199,16 +227,32 @@
alpha.setInterpolator(LINEAR);
alpha.setDuration(ALPHA_DURATION_MS);
alpha.setStartDelay(startDelay);
+ alpha.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setAlpha(1f);
+ }
+ });
mAnimators.play(alpha);
}
- private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) {
- PendingAnimation builder = new PendingAnimation(duration, mAnimators);
- launcher.getWorkspace().getStateTransitionAnimation().setScrim(builder, state);
- builder.setFloat(
+ private void addScrimAnimationForState(Launcher launcher, LauncherState state,
+ PropertySetter setter) {
+ launcher.getWorkspace().getStateTransitionAnimation().setScrim(setter, state);
+ setter.setFloat(
launcher.getDragLayer().getOverviewScrim(),
OverviewScrim.SCRIM_PROGRESS,
state.getOverviewScrimAlpha(launcher),
ACCEL_DEACCEL);
}
+
+ private void addDepthAnimationForState(Launcher launcher, LauncherState state, long duration) {
+ if (!(launcher instanceof BaseQuickstepLauncher)) {
+ return;
+ }
+ PendingAnimation builder = new PendingAnimation(duration);
+ DepthController depthController = ((BaseQuickstepLauncher) launcher).getDepthController();
+ depthController.setStateWithAnimation(state, new StateAnimationConfig(), builder);
+ mAnimators.play(builder.buildAnim());
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java
new file mode 100644
index 0000000..0436a1c
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java
@@ -0,0 +1,134 @@
+/*
+ * 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.util;
+
+import static com.android.systemui.shared.system.TransactionCompat.deferTransactionUntil;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import android.view.View;
+
+import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+import com.android.systemui.shared.system.ViewRootImplCompat;
+
+import java.util.function.Consumer;
+
+
+/**
+ * Helper class to apply surface transactions in sync with RenderThread similar to
+ * android.view.SyncRtSurfaceTransactionApplier
+ * with some Launcher specific utility methods
+ */
+@TargetApi(Build.VERSION_CODES.R)
+public class SurfaceTransactionApplier extends ReleaseCheck {
+
+ private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
+
+ private final SurfaceControl mBarrierSurfaceControl;
+ private final ViewRootImplCompat mTargetViewRootImpl;
+ private final Handler mApplyHandler;
+
+ private int mLastSequenceNumber = 0;
+
+ /**
+ * @param targetView The view in the surface that acts as synchronization anchor.
+ */
+ public SurfaceTransactionApplier(View targetView) {
+ mTargetViewRootImpl = new ViewRootImplCompat(targetView);
+ mBarrierSurfaceControl = mTargetViewRootImpl.getRenderSurfaceControl();
+ mApplyHandler = new Handler(this::onApplyMessage);
+ }
+
+ protected boolean onApplyMessage(Message msg) {
+ if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) {
+ setCanRelease(msg.arg1 == mLastSequenceNumber);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Schedules applying surface parameters on the next frame.
+ *
+ * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into
+ * this method to avoid synchronization issues.
+ */
+ public void scheduleApply(final SurfaceParams... params) {
+ View view = mTargetViewRootImpl.getView();
+ if (view == null) {
+ return;
+ }
+
+ mLastSequenceNumber++;
+ final int toApplySeqNo = mLastSequenceNumber;
+ setCanRelease(false);
+ mTargetViewRootImpl.registerRtFrameCallback(frame -> {
+ if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {
+ Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
+ .sendToTarget();
+ return;
+ }
+ Transaction t = new Transaction();
+ for (int i = params.length - 1; i >= 0; i--) {
+ SurfaceParams surfaceParams = params[i];
+ if (surfaceParams.surface.isValid()) {
+ deferTransactionUntil(t, surfaceParams.surface, mBarrierSurfaceControl, frame);
+ surfaceParams.applyTo(t);
+ }
+ }
+ t.apply();
+ Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
+ .sendToTarget();
+ });
+
+ // Make sure a frame gets scheduled.
+ view.invalidate();
+ }
+
+ /**
+ * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is
+ * attached if necessary.
+ */
+ public static void create(
+ final View targetView, final Consumer<SurfaceTransactionApplier> callback) {
+ if (targetView == null) {
+ // No target view, no applier
+ callback.accept(null);
+ } else if (new ViewRootImplCompat(targetView).isValid()) {
+ // Already attached, we're good to go
+ callback.accept(new SurfaceTransactionApplier(targetView));
+ } else {
+ // Haven't been attached before we can get the view root
+ targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ targetView.removeOnAttachStateChangeListener(this);
+ callback.accept(new SurfaceTransactionApplier(targetView));
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ // Do nothing
+ }
+ });
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
new file mode 100644
index 0000000..c9ed498
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -0,0 +1,314 @@
+/*
+ * 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.util;
+
+import static com.android.launcher3.states.RotationHelper.deltaRotation;
+import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
+import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
+import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
+
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.IntProperty;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.views.RecentsView.ScrollState;
+import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
+import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskView.FullscreenDrawParams;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
+
+/**
+ * A utility class which emulates the layout behavior of TaskView and RecentsView
+ */
+public class TaskViewSimulator implements TransformParams.BuilderProxy {
+
+ public static final IntProperty<TaskViewSimulator> SCROLL =
+ new IntProperty<TaskViewSimulator>("scroll") {
+ @Override
+ public void setValue(TaskViewSimulator simulator, int i) {
+ simulator.setScroll(i);
+ }
+
+ @Override
+ public Integer get(TaskViewSimulator simulator) {
+ return simulator.mScrollState.scroll;
+ }
+ };
+
+ private final Rect mTmpCropRect = new Rect();
+ private final RectF mTempRectF = new RectF();
+ private final float[] mTempPoint = new float[2];
+
+ private final RecentsOrientedState mOrientationState;
+ private final Context mContext;
+ private final BaseActivityInterface mSizeStrategy;
+
+ private final Rect mTaskRect = new Rect();
+ private final PointF mPivot = new PointF();
+ private DeviceProfile mDp;
+
+ private final Matrix mMatrix = new Matrix();
+ private final Point mRunningTargetWindowPosition = new Point();
+
+ // Thumbnail view properties
+ private final Rect mThumbnailPosition = new Rect();
+ private final ThumbnailData mThumbnailData = new ThumbnailData();
+ private final PreviewPositionHelper mPositionHelper = new PreviewPositionHelper();
+ private final Matrix mInversePositionMatrix = new Matrix();
+
+ // TaskView properties
+ private final FullscreenDrawParams mCurrentFullscreenParams;
+ private float mCurveScale = 1;
+
+ // RecentsView properties
+ public final AnimatedFloat recentsViewScale = new AnimatedFloat();
+ public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
+ private final ScrollState mScrollState = new ScrollState();
+ private final int mPageSpacing;
+
+ // Cached calculations
+ private boolean mLayoutValid = false;
+ private boolean mScrollValid = false;
+
+ public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
+ mContext = context;
+ mSizeStrategy = sizeStrategy;
+
+ mOrientationState = new RecentsOrientedState(context, sizeStrategy, i -> { });
+ mOrientationState.setGestureActive(true);
+
+ mCurrentFullscreenParams = new FullscreenDrawParams(context);
+ mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
+ }
+
+ /**
+ * Sets the device profile for the current state
+ */
+ public void setDp(DeviceProfile dp) {
+ mDp = dp;
+ mOrientationState.setMultiWindowMode(mDp.isMultiWindowMode);
+ mLayoutValid = false;
+ }
+
+ /**
+ * @see com.android.quickstep.views.RecentsView#setLayoutRotation(int, int)
+ */
+ public void setLayoutRotation(int touchRotation, int displayRotation) {
+ mOrientationState.update(touchRotation, displayRotation);
+ mLayoutValid = false;
+ }
+
+ /**
+ * @see com.android.quickstep.views.RecentsView#onConfigurationChanged(Configuration)
+ */
+ public void setRecentsConfiguration(Configuration configuration) {
+ mOrientationState.setActivityConfiguration(configuration);
+ mLayoutValid = false;
+ }
+
+ /**
+ * @see com.android.quickstep.views.RecentsView#FULLSCREEN_PROGRESS
+ */
+ public float getFullScreenScale() {
+ if (mDp == null) {
+ return 1;
+ }
+ mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect,
+ mOrientationState.getOrientationHandler());
+ return mOrientationState.getFullScreenScaleAndPivot(mTaskRect, mDp, mPivot);
+ }
+
+ /**
+ * Sets the targets which the simulator will control
+ */
+ public void setPreview(RemoteAnimationTargetCompat runningTarget) {
+ setPreviewBounds(runningTarget.screenSpaceBounds, runningTarget.contentInsets);
+ mRunningTargetWindowPosition.set(runningTarget.screenSpaceBounds.left,
+ runningTarget.screenSpaceBounds.top);
+ }
+
+ /**
+ * Sets the targets which the simulator will control
+ */
+ public void setPreviewBounds(Rect bounds, Rect insets) {
+ mThumbnailData.insets.set(insets);
+ // TODO: What is this?
+ mThumbnailData.windowingMode = WINDOWING_MODE_FULLSCREEN;
+
+ mThumbnailPosition.set(bounds);
+ mLayoutValid = false;
+ }
+
+ /**
+ * Updates the scroll for RecentsView
+ */
+ public void setScroll(int scroll) {
+ if (mScrollState.scroll != scroll) {
+ mScrollState.scroll = scroll;
+ mScrollValid = false;
+ }
+ }
+
+ /**
+ * Adds animation for all the components corresponding to transition from an app to overview
+ */
+ public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
+ pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
+ pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator);
+ }
+
+ /**
+ * Returns the current clipped/visible window bounds in the window coordinate space
+ */
+ public RectF getCurrentCropRect() {
+ // Crop rect is the inverse of thumbnail matrix
+ RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
+ mTempRectF.set(-insets.left, -insets.top,
+ mTaskRect.width() + insets.right, mTaskRect.height() + insets.bottom);
+ mInversePositionMatrix.mapRect(mTempRectF);
+ return mTempRectF;
+ }
+
+ public RecentsOrientedState getOrientationState() {
+ return mOrientationState;
+ }
+
+ /**
+ * Returns the current transform applied to the window
+ */
+ public Matrix getCurrentMatrix() {
+ return mMatrix;
+ }
+
+ /**
+ * Applies the rotation on the matrix to so that it maps from launcher coordinate space to
+ * window coordinate space.
+ */
+ public void applyWindowToHomeRotation(Matrix matrix) {
+ mMatrix.postTranslate(mDp.windowX, mDp.windowY);
+ postDisplayRotation(deltaRotation(
+ mOrientationState.getRecentsActivityRotation(),
+ mOrientationState.getDisplayRotation()),
+ mDp.widthPx, mDp.heightPx, matrix);
+ matrix.postTranslate(-mRunningTargetWindowPosition.x, -mRunningTargetWindowPosition.y);
+ }
+
+ /**
+ * Applies the target to the previously set parameters
+ */
+ public void apply(TransformParams params) {
+ if (mDp == null || mThumbnailPosition.isEmpty()) {
+ return;
+ }
+ if (!mLayoutValid) {
+ mLayoutValid = true;
+
+ getFullScreenScale();
+ mThumbnailData.rotation = mOrientationState.getDisplayRotation();
+
+ mPositionHelper.updateThumbnailMatrix(
+ mThumbnailPosition, mThumbnailData,
+ mTaskRect.width(), mTaskRect.height(),
+ mDp, mOrientationState.getRecentsActivityRotation());
+ mPositionHelper.getMatrix().invert(mInversePositionMatrix);
+
+ PagedOrientationHandler poh = mOrientationState.getOrientationHandler();
+ mScrollState.halfPageSize =
+ poh.getPrimaryValue(mTaskRect.width(), mTaskRect.height()) / 2;
+ mScrollState.halfScreenSize = poh.getPrimaryValue(mDp.widthPx, mDp.heightPx) / 2;
+ mScrollValid = false;
+ }
+
+ if (!mScrollValid) {
+ mScrollValid = true;
+ int start = mOrientationState.getOrientationHandler()
+ .getPrimaryValue(mTaskRect.left, mTaskRect.top);
+ mScrollState.screenCenter = start + mScrollState.scroll + mScrollState.halfPageSize;
+ mScrollState.updateInterpolation(start, mPageSpacing);
+ mCurveScale = TaskView.getCurveScaleForInterpolation(mScrollState.linearInterpolation);
+ }
+
+ float progress = Utilities.boundToRange(fullScreenProgress.value, 0, 1);
+ mCurrentFullscreenParams.setProgress(
+ progress, recentsViewScale.value, mTaskRect.width(), mDp, mPositionHelper);
+
+ // Apply thumbnail matrix
+ RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
+ float scale = mCurrentFullscreenParams.mScale;
+ float taskWidth = mTaskRect.width();
+ float taskHeight = mTaskRect.height();
+
+ mMatrix.set(mPositionHelper.getMatrix());
+ mMatrix.postTranslate(insets.left, insets.top);
+ mMatrix.postScale(scale, scale);
+
+ // Apply TaskView matrix: translate, scale, scroll
+ mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
+ mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2);
+ mOrientationState.getOrientationHandler().set(
+ mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll);
+
+ // Apply recensView matrix
+ mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
+ applyWindowToHomeRotation(mMatrix);
+
+ // Crop rect is the inverse of thumbnail matrix
+ mTempRectF.set(-insets.left, -insets.top,
+ taskWidth + insets.right, taskHeight + insets.bottom);
+ mInversePositionMatrix.mapRect(mTempRectF);
+ mTempRectF.roundOut(mTmpCropRect);
+
+ params.applySurfaceParams(params.createSurfaceParams(this));
+ }
+
+ @Override
+ public void onBuildTargetParams(
+ Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
+ builder.withMatrix(mMatrix)
+ .withWindowCrop(mTmpCropRect)
+ .withCornerRadius(getCurrentCornerRadius());
+ }
+
+ /**
+ * Returns the corner radius that should be applied to the target so that it matches the
+ * TaskView
+ */
+ public float getCurrentCornerRadius() {
+ float visibleRadius = mCurrentFullscreenParams.mCurrentDrawnCornerRadius;
+ mTempPoint[0] = visibleRadius;
+ mTempPoint[1] = 0;
+ mInversePositionMatrix.mapVectors(mTempPoint);
+
+ // Ideally we should use square-root. This is an optimization as one of the dimension is 0.
+ return Math.max(Math.abs(mTempPoint[0]), Math.abs(mTempPoint[1]));
+ }
+
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
new file mode 100644
index 0000000..0135f74
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
@@ -0,0 +1,207 @@
+/*
+ * 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.util;
+
+import android.util.FloatProperty;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.quickstep.RemoteAnimationTargets;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+import com.android.systemui.shared.system.TransactionCompat;
+
+public class TransformParams {
+
+ public static FloatProperty<TransformParams> PROGRESS =
+ new FloatProperty<TransformParams>("progress") {
+ @Override
+ public void setValue(TransformParams params, float v) {
+ params.setProgress(v);
+ }
+
+ @Override
+ public Float get(TransformParams params) {
+ return params.getProgress();
+ }
+ };
+
+ public static FloatProperty<TransformParams> TARGET_ALPHA =
+ new FloatProperty<TransformParams>("targetAlpha") {
+ @Override
+ public void setValue(TransformParams params, float v) {
+ params.setTargetAlpha(v);
+ }
+
+ @Override
+ public Float get(TransformParams params) {
+ return params.getTargetAlpha();
+ }
+ };
+
+ private float mProgress;
+ private float mTargetAlpha;
+ private float mCornerRadius;
+ private RemoteAnimationTargets mTargetSet;
+ private SurfaceTransactionApplier mSyncTransactionApplier;
+
+ private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
+ private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
+
+ public TransformParams() {
+ mProgress = 0;
+ mTargetAlpha = 1;
+ mCornerRadius = -1;
+ }
+
+ /**
+ * Sets the progress of the transformation, where 0 is the source and 1 is the target. We
+ * automatically adjust properties such as currentRect and cornerRadius based on this
+ * progress, unless they are manually overridden by setting them on this TransformParams.
+ */
+ public TransformParams setProgress(float progress) {
+ mProgress = progress;
+ return this;
+ }
+
+ /**
+ * Sets the corner radius of the transformed window, in pixels. If unspecified (-1), we
+ * simply interpolate between the window's corner radius to the task view's corner radius,
+ * based on {@link #mProgress}.
+ */
+ public TransformParams setCornerRadius(float cornerRadius) {
+ mCornerRadius = cornerRadius;
+ return this;
+ }
+
+ /**
+ * Specifies the alpha of the transformed window. Default is 1.
+ */
+ public TransformParams setTargetAlpha(float targetAlpha) {
+ mTargetAlpha = targetAlpha;
+ return this;
+ }
+
+ /**
+ * Specifies the set of RemoteAnimationTargetCompats that are included in the transformation
+ * that these TransformParams help compute. These TransformParams generally only apply to
+ * the targetSet.apps which match the targetSet.targetMode (e.g. the MODE_CLOSING app when
+ * swiping to home).
+ */
+ public TransformParams setTargetSet(RemoteAnimationTargets targetSet) {
+ mTargetSet = targetSet;
+ return this;
+ }
+
+ /**
+ * Sets the SyncRtSurfaceTransactionApplierCompat that will apply the SurfaceParams that
+ * are computed based on these TransformParams.
+ */
+ public TransformParams setSyncTransactionApplier(
+ SurfaceTransactionApplier applier) {
+ mSyncTransactionApplier = applier;
+ return this;
+ }
+
+ /**
+ * Sets an alternate function to control transform for non-target apps. The default
+ * implementation keeps the targets visible with alpha=1
+ */
+ public TransformParams setBaseBuilderProxy(BuilderProxy proxy) {
+ mBaseBuilderProxy = proxy;
+ return this;
+ }
+
+ /**
+ * Sets an alternate function to control transform for home target. The default
+ * implementation keeps the targets visible with alpha=1
+ */
+ public TransformParams setHomeBuilderProxy(BuilderProxy proxy) {
+ mHomeBuilderProxy = proxy;
+ return this;
+ }
+
+ public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
+ RemoteAnimationTargets targets = mTargetSet;
+ SurfaceParams[] surfaceParams = new SurfaceParams[targets.unfilteredApps.length];
+ for (int i = 0; i < targets.unfilteredApps.length; i++) {
+ RemoteAnimationTargetCompat app = targets.unfilteredApps[i];
+ SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash);
+
+ if (app.mode == targets.targetMode) {
+ if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+ mHomeBuilderProxy.onBuildTargetParams(builder, app, this);
+ } else {
+ // Fade out Assistant overlay.
+ if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT
+ && app.isNotInRecents) {
+ float progress = Utilities.boundToRange(getProgress(), 0, 1);
+ builder.withAlpha(1 - Interpolators.DEACCEL_2_5.getInterpolation(progress));
+ } else {
+ builder.withAlpha(getTargetAlpha());
+ }
+
+ proxy.onBuildTargetParams(builder, app, this);
+ }
+ } else {
+ mBaseBuilderProxy.onBuildTargetParams(builder, app, this);
+ }
+ surfaceParams[i] = builder.build();
+ }
+ return surfaceParams;
+ }
+
+ // Pubic getters so outside packages can read the values.
+
+ public float getProgress() {
+ return mProgress;
+ }
+
+ public float getTargetAlpha() {
+ return mTargetAlpha;
+ }
+
+ public float getCornerRadius() {
+ return mCornerRadius;
+ }
+
+ public RemoteAnimationTargets getTargetSet() {
+ return mTargetSet;
+ }
+
+ public void applySurfaceParams(SurfaceParams[] params) {
+ if (mSyncTransactionApplier != null) {
+ mSyncTransactionApplier.scheduleApply(params);
+ } else {
+ TransactionCompat t = new TransactionCompat();
+ for (SurfaceParams param : params) {
+ SyncRtSurfaceTransactionApplierCompat.applyParams(t, param);
+ }
+ t.apply();
+ }
+ }
+
+ @FunctionalInterface
+ public interface BuilderProxy {
+
+ BuilderProxy NO_OP = (builder, app, params) -> { };
+ BuilderProxy ALWAYS_VISIBLE = (builder, app, params) ->builder.withAlpha(1);
+
+ void onBuildTargetParams(SurfaceParams.Builder builder,
+ RemoteAnimationTargetCompat app, TransformParams params);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
index c71258b..29b9558 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
@@ -149,8 +149,12 @@
isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
}
- if (isSwipeUp && mOnSwipeUp != null) {
- mOnSwipeUp.onSwipeUp(wasFling);
+ if (mOnSwipeUp != null) {
+ if (isSwipeUp) {
+ mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY));
+ } else {
+ mOnSwipeUp.onSwipeUpCancelled();
+ }
}
}
@@ -161,7 +165,11 @@
/**
* Called on touch up if a swipe up was detected.
* @param wasFling Whether the swipe was a fling, or just passed touch slop at low velocity.
+ * @param finalVelocity The final velocity of the swipe.
*/
- void onSwipeUp(boolean wasFling);
+ void onSwipeUp(boolean wasFling, PointF finalVelocity);
+
+ /** Called on touch up if a swipe up was not detected. */
+ void onSwipeUpCancelled();
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
new file mode 100644
index 0000000..0979c07
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
@@ -0,0 +1,240 @@
+/*
+ * 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.views;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_EDU_SHOWN;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+
+import androidx.core.graphics.ColorUtils;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.Themes;
+import com.android.quickstep.util.MultiValueUpdateListener;
+
+/**
+ * View used to educate the user on how to access All Apps when in No Nav Button navigation mode.
+ */
+public class AllAppsEduView extends AbstractFloatingView {
+
+ private Launcher mLauncher;
+
+ private AnimatorSet mAnimation;
+
+ private GradientDrawable mCircle;
+ private GradientDrawable mGradient;
+
+ private int mCircleSizePx;
+ private int mPaddingPx;
+ private int mWidthPx;
+ private int mMaxHeightPx;
+
+ public AllAppsEduView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mCircle = (GradientDrawable) context.getDrawable(R.drawable.all_apps_edu_circle);
+ mCircleSizePx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_circle_size);
+ mPaddingPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_padding);
+ mWidthPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_width);
+ mMaxHeightPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_max_height);
+ setWillNotDraw(false);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ mGradient.draw(canvas);
+ mCircle.draw(canvas);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mIsOpen = true;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mIsOpen = false;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ mLauncher.getDragLayer().removeView(this);
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // TODO
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ALL_APPS_EDU) != 0;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ return mAnimation != null && mAnimation.isRunning();
+ }
+
+ private void playAnimation() {
+ if (mAnimation != null) {
+ return;
+ }
+ mAnimation = new AnimatorSet();
+
+ final Rect circleBoundsOg = new Rect(mCircle.getBounds());
+ final Rect gradientBoundsOg = new Rect(mGradient.getBounds());
+ final Rect temp = new Rect();
+ final float transY = mMaxHeightPx - mCircleSizePx - mPaddingPx;
+
+ // 1st: Circle alpha/scale
+ int firstPart = 600;
+ // 2nd: Circle animates upwards, Gradient alpha fades in, Gradient grows, All Apps hint
+ int secondPart = 1200;
+ int introDuration = firstPart + secondPart;
+
+ StateAnimationConfig config = new StateAnimationConfig();
+ config.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
+ 0, 0.08f));
+ config.duration = secondPart;
+ config.userControlled = false;
+ AnimatorPlaybackController stateAnimationController =
+ mLauncher.getStateManager().createAnimationToNewWorkspace(ALL_APPS, config);
+ float maxAllAppsProgress = 0.15f;
+
+ ValueAnimator intro = ValueAnimator.ofFloat(0, 1f);
+ intro.setInterpolator(LINEAR);
+ intro.setDuration(introDuration);
+ intro.addUpdateListener((new MultiValueUpdateListener() {
+ FloatProp mCircleAlpha = new FloatProp(0, 255, 0, firstPart, LINEAR);
+ FloatProp mCircleScale = new FloatProp(2f, 1f, 0, firstPart, OVERSHOOT_1_7);
+ FloatProp mDeltaY = new FloatProp(0, transY, firstPart, secondPart, FAST_OUT_SLOW_IN);
+ FloatProp mGradientAlpha = new FloatProp(0, 255, firstPart, secondPart * 0.3f, LINEAR);
+
+ @Override
+ public void onUpdate(float progress) {
+ temp.set(circleBoundsOg);
+ temp.offset(0, (int) -mDeltaY.value);
+ Utilities.scaleRectAboutCenter(temp, mCircleScale.value);
+ mCircle.setBounds(temp);
+ mCircle.setAlpha((int) mCircleAlpha.value);
+ mGradient.setAlpha((int) mGradientAlpha.value);
+
+ temp.set(gradientBoundsOg);
+ temp.top -= mDeltaY.value;
+ mGradient.setBounds(temp);
+ invalidate();
+
+ float stateProgress = Utilities.mapToRange(mDeltaY.value, 0, transY, 0,
+ maxAllAppsProgress, LINEAR);
+ stateAnimationController.setPlayFraction(stateProgress);
+ }
+ }));
+ intro.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCircle.setAlpha(0);
+ mGradient.setAlpha(0);
+ }
+ });
+ mAnimation.play(intro);
+
+ ValueAnimator closeAllApps = ValueAnimator.ofFloat(maxAllAppsProgress, 0f);
+ closeAllApps.addUpdateListener(valueAnimator -> {
+ stateAnimationController.setPlayFraction((float) valueAnimator.getAnimatedValue());
+ });
+ closeAllApps.setInterpolator(FAST_OUT_SLOW_IN);
+ closeAllApps.setStartDelay(introDuration);
+ closeAllApps.setDuration(250);
+ mAnimation.play(closeAllApps);
+
+ mAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimation = null;
+ stateAnimationController.dispatchOnCancel();
+ handleClose(false);
+ }
+ });
+ mAnimation.start();
+ }
+
+ private void init(Launcher launcher) {
+ mLauncher = launcher;
+
+ int accentColor = Themes.getColorAccent(launcher);
+ mGradient = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
+ Themes.getAttrBoolean(launcher, R.attr.isMainColorDark)
+ ? new int[] {0xB3FFFFFF, 0x00FFFFFF}
+ : new int[] {ColorUtils.setAlphaComponent(accentColor, 127),
+ ColorUtils.setAlphaComponent(accentColor, 0)});
+ float r = mWidthPx / 2f;
+ mGradient.setCornerRadii(new float[] {r, r, r, r, 0, 0, 0, 0});
+
+ int top = mMaxHeightPx - mCircleSizePx + mPaddingPx;
+ mCircle.setBounds(mPaddingPx, top, mPaddingPx + mCircleSizePx, top + mCircleSizePx);
+ mGradient.setBounds(0, mMaxHeightPx - mCircleSizePx, mWidthPx, mMaxHeightPx);
+
+ DeviceProfile grid = launcher.getDeviceProfile();
+ DragLayer.LayoutParams lp = new DragLayer.LayoutParams(mWidthPx, mMaxHeightPx);
+ lp.ignoreInsets = true;
+ lp.leftMargin = (grid.widthPx - mWidthPx) / 2;
+ lp.topMargin = grid.heightPx - grid.hotseatBarSizePx - mMaxHeightPx;
+ setLayoutParams(lp);
+ }
+
+ /**
+ * Shows the All Apps education view and plays the animation.
+ */
+ public static void show(Launcher launcher) {
+ final DragLayer dragLayer = launcher.getDragLayer();
+ ViewGroup parent = (ViewGroup) dragLayer.getParent();
+ AllAppsEduView view = launcher.getViewCache().getView(R.layout.all_apps_edu_view,
+ launcher, parent);
+ view.init(launcher);
+ launcher.getDragLayer().addView(view);
+ launcher.getStatsLogManager().logger().log(LAUNCHER_ALL_APPS_EDU_SHOWN);
+
+ view.requestLayout();
+ view.playAnimation();
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
index 763f5be..fd74357 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
@@ -47,23 +47,27 @@
private boolean mIsRtl;
private int mScrollOffset;
- private RecentsView mParent;
public ClearAllButton(Context context, AttributeSet attrs) {
super(context, attrs);
+ mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- mScrollOffset = mIsRtl ? mParent.getPaddingRight() / 2 : - mParent.getPaddingLeft() / 2;
+ PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
+ mScrollOffset = orientationHandler.getClearAllScrollOffset(getRecentsView(), mIsRtl);
+ }
+
+ private RecentsView getRecentsView() {
+ return (RecentsView) getParent();
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mParent = (RecentsView) getParent();
- mIsRtl = !mParent.getPagedOrientationHandler().getRecentsRtlSetting(getResources());
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
@Override
@@ -78,21 +82,6 @@
}
}
- public void onLayoutChanged() {
- if (mParent == null) {
- return;
- }
- setRotation(mParent.getPagedOrientationHandler().getDegreesRotated());
- }
-
- public void setRtl(boolean rtl) {
- if (mIsRtl == rtl) {
- return;
- }
- mIsRtl = rtl;
- invalidate();
- }
-
public void setVisibilityAlpha(float alpha) {
if (mVisibilityAlpha != alpha) {
mVisibilityAlpha = alpha;
@@ -102,7 +91,7 @@
@Override
public void onPageScroll(ScrollState scrollState) {
- PagedOrientationHandler orientationHandler = mParent.getPagedOrientationHandler();
+ PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
if (orientationSize == 0) {
return;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/IconView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/IconView.java
index eb8da6e..7cc00b7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/IconView.java
@@ -21,12 +21,12 @@
import android.util.AttributeSet;
import android.view.View;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.FastBitmapDrawable;
import java.util.ArrayList;
-import androidx.annotation.NonNull;
-
/**
* A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
* when the drawable changes.
@@ -130,4 +130,14 @@
mScaleListeners.remove(listener);
}
}
+
+ @Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+ if (alpha > 0) {
+ setVisibility(VISIBLE);
+ } else {
+ setVisibility(INVISIBLE);
+ }
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 98eb29a..846b944 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
+import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
@@ -29,43 +30,38 @@
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
import android.os.Build;
+import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.MotionEvent;
-import android.view.View;
+import android.view.Surface;
import android.widget.FrameLayout;
import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.views.ScrimView;
+import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.util.AppWindowAnimationHelper;
-import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
-import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.TransformParams;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.RecentsExtraCard;
+import com.android.systemui.shared.recents.model.Task;
/**
* {@link RecentsView} used in Launcher activity
*/
@TargetApi(Build.VERSION_CODES.O)
public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher>
- implements StateListener {
-
- private static final Rect sTempRect = new Rect();
+ implements StateListener<LauncherState> {
private final TransformParams mTransformParams = new TransformParams();
@@ -88,10 +84,6 @@
}
};
- private RotationHelper.ForcedRotationChangedListener mForcedRotationChangedListener =
- isForcedRotation -> LauncherRecentsView.this
- .disableMultipleLayoutRotations(!isForcedRotation);
-
public LauncherRecentsView(Context context) {
this(context, null);
}
@@ -101,12 +93,17 @@
}
public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setContentAlpha(0);
+ super(context, attrs, defStyleAttr, LauncherActivityInterface.INSTANCE);
mActivity.getStateManager().addStateListener(this);
}
@Override
+ public void init(OverviewActionsView actionsView) {
+ super.init(actionsView);
+ setContentAlpha(0);
+ }
+
+ @Override
public void startHome() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
switchToScreenshot(null,
@@ -128,31 +125,12 @@
}
}
- @Override
- public void draw(Canvas canvas) {
- maybeDrawEmptyMessage(canvas);
- super.draw(canvas);
- }
-
- @Override
- public void onViewAdded(View child) {
- super.onViewAdded(child);
- updateEmptyMessage();
- }
-
- @Override
- protected void onTaskStackUpdated() {
- // Lazily update the empty message only when the task stack is reapplied
- updateEmptyMessage();
- }
-
/**
* Animates adjacent tasks and translate hotseat off screen as well.
*/
@Override
- public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv,
- AppWindowAnimationHelper helper) {
- AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv, helper);
+ public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) {
+ AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv);
if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
// Hotseat doesn't move when opening recents with the button,
@@ -179,39 +157,12 @@
}
@Override
- protected void getTaskSize(DeviceProfile dp, Rect outRect) {
- LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
- }
-
- /**
- * @return The translationX to apply to this view so that the first task is just offscreen.
- */
- public float getOffscreenTranslationX(float recentsScale) {
- LauncherState.ScaleAndTranslation overviewScaleAndTranslation =
- NORMAL.getOverviewScaleAndTranslation(mActivity);
- float offscreen = mOrientationHandler.getTranslationValue(overviewScaleAndTranslation);
- // Offset since scale pushes tasks outwards.
- getTaskSize(sTempRect);
- int taskSize = mOrientationHandler.getPrimarySize(sTempRect);
- offscreen += taskSize * (recentsScale - 1) / 2;
- if (mRunningTaskTileHidden) {
- // The first task is hidden, so offset by its width.
- offscreen -= (taskSize + getPageSpacing()) * recentsScale;
- }
- if (isRtl()) {
- offscreen = -offscreen;
- }
- return offscreen;
- }
-
- @Override
protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (tv.isRunningTask()) {
mTransformParams.setProgress(1 - progress)
- .setCurrentRect(null)
.setSyncTransactionApplier(mSyncTransactionApplier);
- mAppWindowAnimationHelper.applyTransform(mTransformParams);
+ // TODO: Revisit live tiles
} else {
redrawLiveTile(true);
}
@@ -219,14 +170,21 @@
}
@Override
- protected void onTaskLaunched(boolean success) {
+ protected void onTaskLaunchAnimationEnd(boolean success) {
if (success) {
mActivity.getStateManager().goToState(NORMAL, false /* animate */);
} else {
LauncherState state = mActivity.getStateManager().getState();
mActivity.getAllAppsController().setState(state);
}
- super.onTaskLaunched(success);
+ super.onTaskLaunchAnimationEnd(success);
+ }
+
+ @Override
+ public void onTaskLaunched(Task task) {
+ UserHandle user = UserHandle.of(task.key.userId);
+ AppLaunchTracker.INSTANCE.get(getContext()).onStartApp(task.getTopComponent(), user,
+ AppLaunchTracker.CONTAINER_OVERVIEW);
}
@Override
@@ -243,18 +201,10 @@
}
@Override
- public void redrawLiveTile(boolean mightNeedToRefill) {
- AppWindowAnimationHelper.TransformParams transformParams = getLiveTileParams(mightNeedToRefill);
- if (transformParams != null) {
- mAppWindowAnimationHelper.applyTransform(transformParams);
- }
- }
-
- @Override
- public AppWindowAnimationHelper.TransformParams getLiveTileParams(
+ public TransformParams getLiveTileParams(
boolean mightNeedToRefill) {
if (!mEnableDrawingLiveTile || mRecentsAnimationController == null
- || mRecentsAnimationTargets == null || mAppWindowAnimationHelper == null) {
+ || mRecentsAnimationTargets == null) {
return null;
}
TaskView taskView = getRunningTaskView();
@@ -274,27 +224,19 @@
if (mightNeedToRefill && offsetY > 0) {
mTempRect.top -= offsetY;
}
- mTempRectF.set(mTempRect);
mTransformParams.setProgress(1f)
- .setCurrentRect(mTempRectF)
.setTargetAlpha(taskView.getAlpha())
.setSyncTransactionApplier(mSyncTransactionApplier)
- .setTargetSet(mRecentsAnimationTargets)
- .setLauncherOnTop(true);
+ .setTargetSet(mRecentsAnimationTargets);
}
return mTransformParams;
}
@Override
- protected boolean supportsVerticalLandscape() {
- return FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()
- && !mOrientationState.areMultipleLayoutOrientationsDisabled();
- }
-
- @Override
public void reset() {
super.reset();
+ setLayoutRotation(Surface.ROTATION_0, Surface.ROTATION_0);
// We are moving to home or some other UI with no recents. Switch back to the home client,
// the home predictions should have been updated when the activity was resumed.
PredictionUiStateManager.INSTANCE.get(getContext()).switchClient(Client.HOME);
@@ -312,7 +254,7 @@
// Clean-up logic that occurs when recents is no longer in use/visible.
reset();
}
- setOverlayEnabled(finalState == OVERVIEW);
+ setOverlayEnabled(finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK);
setFreezeViewVisibility(false);
}
@@ -344,7 +286,6 @@
super.onAttachedToWindow();
PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener(
mRecentsExtraCardPluginListener, RecentsExtraCard.class);
- mActivity.getRotationHelper().addForcedRotationCallback(mForcedRotationChangedListener);
}
@Override
@@ -352,7 +293,6 @@
super.onDetachedFromWindow();
PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(
mRecentsExtraCardPluginListener);
- mActivity.getRotationHelper().removeForcedRotationCallback(mForcedRotationChangedListener);
}
@Override
@@ -412,4 +352,16 @@
protected DepthController getDepthController() {
return mActivity.getDepthController();
}
+
+ @Override
+ public void setModalStateEnabled(boolean isModalState) {
+ super.setModalStateEnabled(isModalState);
+ if (isModalState) {
+ mActivity.getStateManager().goToState(LauncherState.OVERVIEW_MODAL_TASK);
+ } else {
+ if (mActivity.isInState(LauncherState.OVERVIEW_MODAL_TASK)) {
+ mActivity.getStateManager().goToState(LauncherState.OVERVIEW);
+ }
+ }
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
index 6a37e2b..a2da398 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -16,34 +16,81 @@
package com.android.quickstep.views;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SHARE;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+
import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
import android.util.AttributeSet;
-import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.widget.FrameLayout;
+import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
+import com.android.quickstep.util.LayoutUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* View for showing action buttons in Overview
*/
-public class OverviewActionsView extends FrameLayout {
+public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
+ implements OnClickListener, Insettable {
- private final View mScreenshotButton;
- private final View mShareButton;
+ private final Rect mInsets = new Rect();
- /**
- * Listener for taps on the various actions.
- */
- public interface Listener {
- /** User has initiated the share actions. */
- void onShare();
+ @IntDef(flag = true, value = {
+ HIDDEN_UNSUPPORTED_NAVIGATION,
+ HIDDEN_DISABLED_FEATURE,
+ HIDDEN_NON_ZERO_ROTATION,
+ HIDDEN_NO_TASKS,
+ HIDDEN_GESTURE_RUNNING,
+ HIDDEN_NO_RECENTS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ActionsHiddenFlags { }
- /** User has initiated the screenshot action. */
- void onScreenshot();
- }
+ public static final int HIDDEN_UNSUPPORTED_NAVIGATION = 1 << 0;
+ public static final int HIDDEN_DISABLED_FEATURE = 1 << 1;
+ public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 2;
+ public static final int HIDDEN_NO_TASKS = 1 << 3;
+ public static final int HIDDEN_GESTURE_RUNNING = 1 << 4;
+ public static final int HIDDEN_NO_RECENTS = 1 << 5;
+
+ @IntDef(flag = true, value = {
+ DISABLED_SCROLLING,
+ DISABLED_ROTATED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ActionsDisabledFlags { }
+
+ public static final int DISABLED_SCROLLING = 1 << 0;
+ public static final int DISABLED_ROTATED = 1 << 1;
+
+ private static final int INDEX_CONTENT_ALPHA = 0;
+ private static final int INDEX_VISIBILITY_ALPHA = 1;
+ private static final int INDEX_FULLSCREEN_ALPHA = 2;
+ private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
+
+ private final MultiValueAlpha mMultiValueAlpha;
+
+ @ActionsHiddenFlags
+ private int mHiddenFlags;
+
+ @ActionsDisabledFlags
+ protected int mDisabledFlags;
+
+ protected T mCallbacks;
public OverviewActionsView(Context context) {
this(context, null);
@@ -54,26 +101,121 @@
}
public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
+ super(context, attrs, defStyleAttr, 0);
+ mMultiValueAlpha = new MultiValueAlpha(this, 4);
}
- public OverviewActionsView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- LayoutInflater.from(context).inflate(R.layout.overview_actions, this, true);
- mShareButton = findViewById(R.id.action_share);
- mScreenshotButton = findViewById(R.id.action_screenshot);
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ View share = findViewById(R.id.action_share);
+ share.setOnClickListener(this);
+ findViewById(R.id.action_screenshot).setOnClickListener(this);
+ if (ENABLE_OVERVIEW_SHARE.get()) {
+ share.setVisibility(VISIBLE);
+ findViewById(R.id.share_space).setVisibility(VISIBLE);
+ }
}
/**
* Set listener for callbacks on action button taps.
*
- * @param listener for callbacks, or {@code null} to clear the listener.
+ * @param callbacks for callbacks, or {@code null} to clear the listener.
*/
- public void setListener(@Nullable OverviewActionsView.Listener listener) {
- mShareButton.setOnClickListener(
- listener == null ? null : view -> listener.onShare());
- mScreenshotButton.setOnClickListener(
- listener == null ? null : view -> listener.onScreenshot());
+ public void setCallbacks(T callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mCallbacks == null) {
+ return;
+ }
+ int id = view.getId();
+ if (id == R.id.action_share) {
+ mCallbacks.onShare();
+ } else if (id == R.id.action_screenshot) {
+ mCallbacks.onScreenshot();
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateHiddenFlags(HIDDEN_DISABLED_FEATURE, !ENABLE_OVERVIEW_ACTIONS.get());
+ updateHiddenFlags(HIDDEN_UNSUPPORTED_NAVIGATION, !removeShelfFromOverview(getContext()));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
+ }
+
+ public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
+ if (enable) {
+ mHiddenFlags |= visibilityFlags;
+ } else {
+ mHiddenFlags &= ~visibilityFlags;
+ }
+ boolean isHidden = mHiddenFlags != 0;
+ mMultiValueAlpha.getProperty(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1);
+ setVisibility(isHidden ? INVISIBLE : VISIBLE);
+ }
+
+ /**
+ * Updates the proper disabled flag to indicate whether OverviewActionsView should be enabled.
+ * Ignores DISABLED_ROTATED flag for determining enabled. Flag is used to enable/disable
+ * buttons individually, currently done for select button in subclass.
+ *
+ * @param disabledFlags The flag to update.
+ * @param enable Whether to enable the disable flag: True will cause view to be disabled.
+ */
+ public void updateDisabledFlags(@ActionsDisabledFlags int disabledFlags, boolean enable) {
+ if (enable) {
+ mDisabledFlags |= disabledFlags;
+ } else {
+ mDisabledFlags &= ~disabledFlags;
+ }
+ //
+ boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
+ LayoutUtils.setViewEnabled(this, isEnabled);
+ }
+
+ public AlphaProperty getContentAlpha() {
+ return mMultiValueAlpha.getProperty(INDEX_CONTENT_ALPHA);
+ }
+
+ public AlphaProperty getVisibilityAlpha() {
+ return mMultiValueAlpha.getProperty(INDEX_VISIBILITY_ALPHA);
+ }
+
+ public AlphaProperty getFullscreenAlpha() {
+ return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
+ }
+
+ /** Updates vertical margins for different navigation mode or configuration changes. */
+ public void updateVerticalMargin(Mode mode) {
+ int bottomMargin;
+ int orientation = getResources().getConfiguration().orientation;
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ bottomMargin = 0;
+ } else if (mode == Mode.THREE_BUTTONS) {
+ bottomMargin = getResources()
+ .getDimensionPixelSize(R.dimen.overview_actions_bottom_margin_three_button);
+ } else {
+ bottomMargin = getResources()
+ .getDimensionPixelSize(R.dimen.overview_actions_bottom_margin_gesture);
+ }
+ bottomMargin += mInsets.bottom;
+ LayoutParams params = (LayoutParams) getLayoutParams();
+ params.setMargins(
+ params.leftMargin, params.topMargin, params.rightMargin, bottomMargin);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index e3b3102..7b24b03 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -16,22 +16,27 @@
package com.android.quickstep.views;
+import static android.view.Surface.ROTATION_0;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_DISMISS_SWIPE_UP;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_LAUNCH_SWIPE_DOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
@@ -39,6 +44,10 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_GESTURE_RUNNING;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
@@ -50,10 +59,11 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -66,12 +76,10 @@
import android.util.FloatProperty;
import android.util.Property;
import android.util.SparseBooleanArray;
-import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.OrientationEventListener;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
@@ -85,9 +93,7 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
-import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -100,28 +106,32 @@
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.OverScroller;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.ViewPool;
+import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.RecentsAnimationController;
import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
-import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.ViewUtils;
-import com.android.quickstep.util.AppWindowAnimationHelper;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitScreenBounds;
+import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TransformParams;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
import com.android.systemui.shared.recents.model.Task;
@@ -129,7 +139,6 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.LauncherEventUtil;
import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
import java.util.ArrayList;
@@ -139,9 +148,10 @@
* A list of recent tasks.
*/
@TargetApi(Build.VERSION_CODES.P)
-public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
- TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
- InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener {
+public abstract class RecentsView<T extends StatefulActivity> extends PagedView implements
+ Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
+ InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener,
+ SplitScreenBounds.OnChangeListener {
private static final String TAG = RecentsView.class.getSimpleName();
@@ -171,17 +181,45 @@
}
};
- private OrientationEventListener mOrientationListener;
- private int mPreviousRotation;
+ public static final FloatProperty<RecentsView> TASK_MODALNESS =
+ new FloatProperty<RecentsView>("taskModalness") {
+ @Override
+ public void setValue(RecentsView recentsView, float v) {
+ recentsView.setTaskModalness(v);
+ }
+
+ @Override
+ public Float get(RecentsView recentsView) {
+ return recentsView.mTaskModalness;
+ }
+ };
+
+ public static final FloatProperty<RecentsView> ADJACENT_PAGE_OFFSET =
+ new FloatProperty<RecentsView>("adjacentPageOffset") {
+ @Override
+ public void setValue(RecentsView recentsView, float v) {
+ if (recentsView.mAdjacentPageOffset != v) {
+ recentsView.mAdjacentPageOffset = v;
+ recentsView.updatePageOffsets();
+ }
+ }
+
+ @Override
+ public Float get(RecentsView recentsView) {
+ return recentsView.mAdjacentPageOffset;
+ }
+ };
+
+ protected RecentsOrientedState mOrientationState;
+ protected final BaseActivityInterface mSizeStrategy;
protected RecentsAnimationController mRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
- protected AppWindowAnimationHelper mAppWindowAnimationHelper;
- protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
+ protected SurfaceTransactionApplier mSyncTransactionApplier;
protected int mTaskWidth;
protected int mTaskHeight;
protected boolean mEnableDrawingLiveTile = false;
protected final Rect mTempRect = new Rect();
- protected final RectF mTempRectF = new RectF();
+ private final PointF mTempPointF = new PointF();
private static final int DISMISS_TASK_DURATION = 300;
private static final int ADDITION_TASK_DURATION = 200;
@@ -192,11 +230,9 @@
private final float mFastFlingVelocity;
private final RecentsModel mModel;
private final int mTaskTopMargin;
- private final int mTaskBottomMargin;
private final ClearAllButton mClearAllButton;
private final Rect mClearAllButtonDeadZoneRect = new Rect();
private final Rect mTaskViewDeadZoneRect = new Rect();
- protected final AppWindowAnimationHelper mTempAppWindowAnimationHelper;
private final ScrollState mScrollState = new ScrollState();
// Keeps track of the previously known visible tasks for purposes of loading/unloading task data
@@ -211,6 +247,8 @@
private boolean mOverlayEnabled;
protected boolean mFreezeViewVisibility;
+ private float mAdjacentPageOffset = 0;
+
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
@@ -308,6 +346,12 @@
protected float mContentAlpha = 1;
@ViewDebug.ExportedProperty(category = "launcher")
protected float mFullscreenProgress = 0;
+ /**
+ * How modal is the current task to be displayed, 1 means the task is fully modal and no other
+ * tasks are show. 0 means the task is displays in context in the list with other tasks.
+ */
+ @ViewDebug.ExportedProperty(category = "launcher")
+ protected float mTaskModalness = 0;
// Keeps track of task id whose visual state should not be reset
private int mIgnoreResetTaskId = -1;
@@ -325,29 +369,37 @@
// Keeps track of the index where the first TaskView should be
private int mTaskViewStartIndex = 0;
- private View mActionsView;
- private boolean mGestureRunning = false;
+ private OverviewActionsView mActionsView;
private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
(inMultiWindowMode) -> {
- if (!inMultiWindowMode && mOverviewStateEnabled) {
- // TODO: Re-enable layout transitions for addition of the unpinned task
- reloadIfNeeded();
- }
- };
+ if (mOrientationState != null) {
+ mOrientationState.setMultiWindowMode(inMultiWindowMode);
+ setLayoutRotation(mOrientationState.getTouchRotation(),
+ mOrientationState.getDisplayRotation());
+ rotateAllChildTasks();
+ }
+ if (!inMultiWindowMode && mOverviewStateEnabled) {
+ // TODO: Re-enable layout transitions for addition of the unpinned task
+ reloadIfNeeded();
+ }
+ };
- public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public RecentsView(Context context, AttributeSet attrs, int defStyleAttr,
+ BaseActivityInterface sizeStrategy) {
super(context, attrs, defStyleAttr);
setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
setEnableFreeScroll(true);
+ mSizeStrategy = sizeStrategy;
+ mActivity = BaseActivity.fromContext(context);
+ mOrientationState = new RecentsOrientedState(
+ context, mSizeStrategy, this::animateRecentsRotationInPlace);
+ mOrientationState.setActivityConfiguration(context.getResources().getConfiguration());
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
- mActivity = (T) BaseActivity.fromContext(context);
mModel = RecentsModel.INSTANCE.get(context);
mIdp = InvariantDeviceProfile.INSTANCE.get(context);
- mTempAppWindowAnimationHelper =
- new AppWindowAnimationHelper(getPagedViewOrientedState(), context);
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
.inflate(R.layout.overview_clear_all_button, this, false);
@@ -359,7 +411,6 @@
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
mTaskTopMargin = getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
- mTaskBottomMargin = LayoutUtils.thumbnailBottomMargin(context);
mSquaredTouchSlop = squaredTouchSlop(context);
mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
@@ -375,26 +426,10 @@
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
setWillNotDraw(false);
updateEmptyMessage();
- disableMultipleLayoutRotations(!supportsVerticalLandscape());
+ mOrientationHandler = mOrientationState.getOrientationHandler();
// Initialize quickstep specific cache params here, as this is constructed only once
mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
-
- mOrientationListener = new OrientationEventListener(getContext()) {
- @Override
- public void onOrientationChanged(int i) {
- int rotation = RotationHelper.getRotationFromDegrees(i);
- if (mPreviousRotation != rotation) {
- animateRecentsRotationInPlace(rotation);
- if (rotation == 0) {
- showActionsView();
- } else {
- hideActionsView();
- }
- mPreviousRotation = rotation;
- }
- }
- };
}
public OverScroller getScroller() {
@@ -462,7 +497,13 @@
return;
}
mModel.getIconCache().clear();
- reset();
+ unloadVisibleTaskData();
+ loadVisibleTaskData();
+ }
+
+ public void init(OverviewActionsView actionsView) {
+ mActionsView = actionsView;
+ mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
}
@Override
@@ -472,13 +513,14 @@
mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
- mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this);
+ mSyncTransactionApplier = new SurfaceTransactionApplier(this);
RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
mIdp.addOnChangeListener(this);
mIPinnedStackAnimationListener.setActivity(mActivity);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
mIPinnedStackAnimationListener);
- setActionsView();
+ mOrientationState.initListeners();
+ SplitScreenBounds.INSTANCE.addOnChangeListener(this);
}
@Override
@@ -492,7 +534,9 @@
RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
mIdp.removeOnChangeListener(this);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
+ SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
mIPinnedStackAnimationListener.setActivity(null);
+ mOrientationState.destroyListeners();
}
@Override
@@ -504,6 +548,38 @@
TaskView taskView = (TaskView) child;
mHasVisibleTaskData.delete(taskView.getTask().key.id);
mTaskViewPool.recycle(taskView);
+ mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+ }
+ updateTaskStartIndex(child);
+ }
+
+ @Override
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
+ child.setAlpha(mContentAlpha);
+ // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
+ // child direction back to match system settings.
+ child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL);
+ updateTaskStartIndex(child);
+ mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false);
+ updateEmptyMessage();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ maybeDrawEmptyMessage(canvas);
+ super.draw(canvas);
+ }
+
+ private void updateTaskStartIndex(View affectingView) {
+ if (!(affectingView instanceof TaskView) && !(affectingView instanceof ClearAllButton)) {
+ int childCount = getChildCount();
+
+ mTaskViewStartIndex = 0;
+ while (mTaskViewStartIndex < childCount
+ && !(getChildAt(mTaskViewStartIndex) instanceof TaskView)) {
+ mTaskViewStartIndex++;
+ }
}
}
@@ -523,17 +599,9 @@
}
public void setOverviewStateEnabled(boolean enabled) {
- if (supportsVerticalLandscape()
- && !TestProtocol.sDisableSensorRotation // Ignore hardware dependency for tests
- && mOrientationListener.canDetectOrientation()) {
- if (enabled) {
- mOrientationListener.enable();
- } else {
- mOrientationListener.disable();
- }
- }
mOverviewStateEnabled = enabled;
updateTaskStackListenerState();
+ mOrientationState.setRotationWatcherEnabled(enabled);
if (!enabled) {
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
@@ -550,9 +618,29 @@
}
}
+ /**
+ * Whether the Clear All button is hidden or fully visible. Used to determine if center
+ * displayed page is a task or the Clear All button.
+ *
+ * @return True = Clear All button not fully visible, center page is a task. False = Clear All
+ * button fully visible, center page is Clear All button.
+ */
+ public boolean isClearAllHidden() {
+ return mClearAllButton.getAlpha() != 1f;
+ }
+
+ @Override
+ protected void onPageBeginTransition() {
+ super.onPageBeginTransition();
+ mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
+ }
+
@Override
protected void onPageEndTransition() {
super.onPageEndTransition();
+ if (isClearAllHidden()) {
+ mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
+ }
if (getNextPage() > 0) {
setSwipeDownShouldLaunchApp(true);
}
@@ -584,7 +672,7 @@
case MotionEvent.ACTION_DOWN:
// Touch down anywhere but the deadzone around the visible clear all button and
// between the task views will start home on touch up
- if (!isHandlingTouch()) {
+ if (!isHandlingTouch() && !isModal()) {
if (mShowEmptyMessage) {
mTouchDownToStartHome = true;
} else {
@@ -612,7 +700,7 @@
@Override
protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
// Enables swiping to the left or right only if the task overlay is not modal.
- if (getCurrentPageTaskView() == null || !getCurrentPageTaskView().isTaskOverlayModal()) {
+ if (!isModal()) {
super.determineScrollingStart(ev, touchSlopScale);
}
}
@@ -642,7 +730,6 @@
if (getTaskViewCount() != requiredTaskCount) {
if (indexOfChild(mClearAllButton) != -1) {
removeView(mClearAllButton);
- hideActionsView();
}
for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
addView(mTaskViewPool.getView());
@@ -652,7 +739,6 @@
}
if (requiredTaskCount > 0) {
addView(mClearAllButton);
- showActionsView();
}
}
@@ -661,7 +747,7 @@
final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
final Task task = tasks.get(i);
final TaskView taskView = (TaskView) getChildAt(pageIndex);
- taskView.bind(task, mLayoutRotation);
+ taskView.bind(task, mOrientationState);
}
if (mNextPage == INVALID_PAGE) {
@@ -685,6 +771,10 @@
updateEnabledOverlays();
}
+ private boolean isModal() {
+ return mTaskModalness > 0;
+ }
+
private void removeTasksViewsAndClearAllButton() {
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
removeView(getTaskViewAt(i));
@@ -692,7 +782,6 @@
if (indexOfChild(mClearAllButton) != -1) {
removeView(mClearAllButton);
}
- hideActionsView();
}
public int getTaskViewCount() {
@@ -703,29 +792,19 @@
return taskViewCount;
}
- /**
- * Updates UI for a modal task, including hiding other tasks.
- */
- public void updateUiForModalTask(TaskView taskView, boolean isTaskOverlayModal) {
- int currentIndex = indexOfChild(taskView);
- TaskView previousTask = getTaskViewAt(currentIndex - 1);
- TaskView nextTask = getTaskViewAt(currentIndex + 1);
- if (previousTask != null) {
- previousTask.setVisibility(isTaskOverlayModal ? View.INVISIBLE : View.VISIBLE);
- }
- if (nextTask != null) {
- nextTask.setVisibility(isTaskOverlayModal ? View.INVISIBLE : View.VISIBLE);
- }
+ protected void onTaskStackUpdated() {
+ // Lazily update the empty message only when the task stack is reapplied
+ updateEmptyMessage();
}
- protected void onTaskStackUpdated() { }
-
public void resetTaskVisuals() {
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
TaskView taskView = getTaskViewAt(i);
if (mIgnoreResetTaskId != taskView.getTask().key.id) {
- taskView.resetVisualProperties();
+ taskView.resetViewTransforms();
taskView.setStableAlpha(mContentAlpha);
+ taskView.setFullscreenProgress(mFullscreenProgress);
+ taskView.setModalness(mTaskModalness);
}
}
if (mRunningTaskTileHidden) {
@@ -740,6 +819,7 @@
updateCurveProperties();
// Update the set of visible task's data
loadVisibleTaskData();
+ setTaskModalness(0);
}
public void setFullscreenProgress(float fullscreenProgress) {
@@ -748,9 +828,9 @@
for (int i = 0; i < taskCount; i++) {
getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
}
- if (mActionsView != null) {
- mActionsView.setVisibility(fullscreenProgress == 0 ? VISIBLE : INVISIBLE);
- }
+ // Fade out the actions view quickly (0.1 range)
+ mActionsView.getFullscreenAlpha().setValue(
+ mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR));
}
private void updateTaskStackListenerState() {
@@ -767,22 +847,29 @@
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
+ resetPaddingFromTaskSize();
+ }
+
+ private void resetPaddingFromTaskSize() {
DeviceProfile dp = mActivity.getDeviceProfile();
- getTaskSize(dp, mTempRect);
+ getTaskSize(mTempRect);
mTaskWidth = mTempRect.width();
mTaskHeight = mTempRect.height();
mTempRect.top -= mTaskTopMargin;
- mTempRect.bottom += mTaskBottomMargin;
setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
dp.widthPx - mInsets.right - mTempRect.right,
dp.heightPx - mInsets.bottom - mTempRect.bottom);
}
- protected abstract void getTaskSize(DeviceProfile dp, Rect outRect);
-
public void getTaskSize(Rect outRect) {
- getTaskSize(mActivity.getDeviceProfile(), outRect);
+ mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
+ mOrientationHandler);
+ }
+
+ /** Gets the task size for modal state. */
+ public void getModalTaskSize(Rect outRect) {
+ mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
}
@Override
@@ -819,7 +906,8 @@
final int pageCount = getPageCount();
for (int i = 0; i < pageCount; i++) {
View page = getPageAt(i);
- mScrollState.updateInterpolation(mOrientationHandler.getChildStart(page), mPageSpacing);
+ mScrollState.updateInterpolation(mOrientationHandler.getChildStartWithTranslation(page),
+ mPageSpacing);
((PageCallbacks) page).onPageScroll(mScrollState);
}
}
@@ -909,12 +997,15 @@
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
- mAppWindowAnimationHelper = null;
unloadVisibleTaskData();
setCurrentPage(0);
mDwbToastShown = false;
mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
+ LayoutUtils.setViewEnabled(mActionsView, true);
+ if (mOrientationState.setGestureActive(false)) {
+ updateOrientationHandler();
+ }
}
public @Nullable TaskView getRunningTaskView() {
@@ -922,7 +1013,15 @@
}
public int getRunningTaskIndex() {
- TaskView tv = getRunningTaskView();
+ return getTaskIndexForId(mRunningTaskId);
+ }
+
+ /**
+ * Get the index of the task view whose id matches {@param taskId}.
+ * @return -1 if there is no task view for the task id, else the index of the task view.
+ */
+ public int getTaskIndexForId(int taskId) {
+ TaskView tv = getTaskView(taskId);
return tv == null ? -1 : indexOfChild(tv);
}
@@ -944,12 +1043,16 @@
*/
public void onGestureAnimationStart(int runningTaskId) {
// This needs to be called before the other states are set since it can create the task view
+ if (mOrientationState.setGestureActive(true)) {
+ updateOrientationHandler();
+ }
+
showCurrentTask(runningTaskId);
setEnableFreeScroll(false);
setEnableDrawingLiveTile(false);
setRunningTaskHidden(true);
setRunningTaskIconScaledDown(true);
- mGestureRunning = true;
+ mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, true);
}
/**
@@ -967,13 +1070,13 @@
}
private void animateRecentsRotationInPlace(int newRotation) {
- if (!supportsVerticalLandscape()) {
+ if (mOrientationState.canRecentsActivityRotate()) {
+ // Let system take care of the rotation
return;
}
-
AnimatorSet pa = setRecentsChangedOrientation(true);
pa.addListener(AnimationSuccessListener.forRunnable(() -> {
- updateLayoutRotation(newRotation);
+ setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
mActivity.getDragLayer().recreateControllers();
rotateAllChildTasks();
setRecentsChangedOrientation(false).start();
@@ -995,12 +1098,10 @@
return as;
}
- abstract protected boolean supportsVerticalLandscape();
private void rotateAllChildTasks() {
for (int i = 0; i < getTaskViewCount(); i++) {
- TaskView taskView = getTaskViewAt(i);
- taskView.setOverviewRotation(mLayoutRotation);
+ getTaskViewAt(i).setOrientationState(mOrientationState);
}
}
@@ -1008,15 +1109,26 @@
* Called when a gesture from an app has finished.
*/
public void onGestureAnimationEnd() {
+ if (mOrientationState.setGestureActive(false)) {
+ updateOrientationHandler();
+ }
+
+ setOnScrollChangeListener(null);
setEnableFreeScroll(true);
setEnableDrawingLiveTile(true);
- setOnScrollChangeListener(null);
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
setRunningTaskViewShowScreenshot(true);
}
setRunningTaskHidden(false);
animateUpRunningTaskIconScale();
- mGestureRunning = false;
+ animateActionsViewIn();
+ }
+
+ /**
+ * Returns true if we should add a dummy taskView for the running task id
+ */
+ protected boolean shouldAddDummyTaskView(int runningTaskId) {
+ return getTaskView(runningTaskId) == null;
}
/**
@@ -1026,14 +1138,13 @@
* is called. Also scrolls the view to this task.
*/
public void showCurrentTask(int runningTaskId) {
- if (getTaskView(runningTaskId) == null) {
- boolean wasEmpty = getTaskViewCount() == 0;
+ if (shouldAddDummyTaskView(runningTaskId)) {
+ boolean wasEmpty = getChildCount() == 0;
// Add an empty view for now until the task plan is loaded and applied
final TaskView taskView = mTaskViewPool.getView();
addView(taskView, mTaskViewStartIndex);
if (wasEmpty) {
addView(mClearAllButton);
- showActionsView();
}
// The temporary running task is only used for the duration between the start of the
// gesture and the task list is loaded and applied
@@ -1041,7 +1152,13 @@
new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0,
false, true, false, false, new ActivityManager.TaskDescription(), 0,
new ComponentName("", ""), false);
- taskView.bind(mTmpRunningTask, mLayoutRotation);
+ taskView.bind(mTmpRunningTask, mOrientationState);
+
+ // Measure and layout immediately so that the scroll values is updated instantly
+ // as the user might be quick-switching
+ measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
+ makeMeasureSpec(getMeasuredHeight(), EXACTLY));
+ layout(getLeft(), getTop(), getRight(), getBottom());
}
boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -1130,6 +1247,14 @@
}
}
+ private void animateActionsViewIn() {
+ mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, false);
+ ObjectAnimator anim = ObjectAnimator.ofFloat(
+ mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 0, 1);
+ anim.setDuration(TaskView.SCALE_ICON_DURATION);
+ anim.start();
+ }
+
public void animateUpRunningTaskIconScale() {
animateUpRunningTaskIconScale(0);
}
@@ -1207,7 +1332,7 @@
/**
* Updates linearInterpolation for the provided child position
*/
- public void updateInterpolation(int childStart, int pageSpacing) {
+ public void updateInterpolation(float childStart, int pageSpacing) {
float pageCenter = childStart + halfPageSize;
float distanceFromScreenCenter = screenCenter - pageCenter;
float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing;
@@ -1250,7 +1375,8 @@
ComponentKey compKey = TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key);
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
endState.logAction, Direction.UP, index, compKey);
- mActivity.getStatsLogManager().log(TASK_DISMISS_SWIPE_UP, taskView.buildProto());
+ mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
+ .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
}
}
@@ -1357,7 +1483,6 @@
if (getTaskViewCount() == 0) {
removeViewInLayout(mClearAllButton);
- hideActionsView();
startHome();
} else {
snapToPageImmediately(pageToSnapTo);
@@ -1410,7 +1535,7 @@
return true;
}
- private void runDismissAnimation(PendingAnimation pendingAnim) {
+ protected void runDismissAnimation(PendingAnimation pendingAnim) {
AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
controller.dispatchOnStart();
controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE));
@@ -1500,14 +1625,12 @@
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
mEmptyIcon.setAlpha(alphaInt);
+ mActionsView.getContentAlpha().setValue(mContentAlpha);
+
if (alpha > 0) {
setVisibility(VISIBLE);
- if (!mGestureRunning) {
- showActionsView();
- }
} else if (!mFreezeViewVisibility) {
setVisibility(GONE);
- hideActionsView();
}
}
@@ -1518,34 +1641,61 @@
public void setFreezeViewVisibility(boolean freezeViewVisibility) {
if (mFreezeViewVisibility != freezeViewVisibility) {
mFreezeViewVisibility = freezeViewVisibility;
-
if (!mFreezeViewVisibility) {
setVisibility(mContentAlpha > 0 ? VISIBLE : GONE);
- if (mContentAlpha > 0) {
- showActionsView();
- } else {
- hideActionsView();
- }
}
}
}
@Override
- public void setLayoutRotation(int touchRotation, int displayRotation) {
- if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
- return;
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ if (mActionsView != null) {
+ mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE);
}
-
- super.setLayoutRotation(touchRotation, displayRotation);
- mClearAllButton.onLayoutChanged();
- mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
- setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
}
@Override
- public void onViewAdded(View child) {
- super.onViewAdded(child);
- child.setAlpha(mContentAlpha);
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mOrientationState.setActivityConfiguration(newConfig)) {
+ updateOrientationHandler();
+ }
+ }
+
+ public void setLayoutRotation(int touchRotation, int displayRotation) {
+ if (mOrientationState.update(touchRotation, displayRotation)) {
+ updateOrientationHandler();
+ }
+ }
+
+ private void updateOrientationHandler() {
+ mOrientationHandler = mOrientationState.getOrientationHandler();
+ mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
+ setLayoutDirection(mIsRtl
+ ? View.LAYOUT_DIRECTION_RTL
+ : View.LAYOUT_DIRECTION_LTR);
+ mClearAllButton.setLayoutDirection(mIsRtl
+ ? View.LAYOUT_DIRECTION_LTR
+ : View.LAYOUT_DIRECTION_RTL);
+ mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
+ mActivity.getDragLayer().recreateControllers();
+ boolean isInLandscape = mOrientationState.getTouchRotation() != 0
+ || mOrientationState.getRecentsActivityRotation() != ROTATION_0;
+ mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
+ !mOrientationState.canRecentsActivityRotate() && isInLandscape);
+ resetPaddingFromTaskSize();
+ requestLayout();
+ // Reapply the current page to update page scrolls.
+ setCurrentPage(mCurrentPage);
+ }
+
+ public RecentsOrientedState getPagedViewOrientedState() {
+ return mOrientationState;
+ }
+
+ public PagedOrientationHandler getPagedOrientationHandler() {
+ return mOrientationHandler;
}
@Nullable
@@ -1554,11 +1704,6 @@
}
@Nullable
- public TaskView getPreviousTaskView() {
- return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() - 1);
- }
-
- @Nullable
public TaskView getCurrentPageTaskView() {
return getTaskViewAtByAbsoluteIndex(getCurrentPage());
}
@@ -1617,11 +1762,56 @@
updateEmptyStateUi(changed);
- // Set the pivot points to match the task preview center
- setPivotY(((mInsets.top + getPaddingTop() + mTaskTopMargin)
- + (getHeight() - mInsets.bottom - getPaddingBottom() - mTaskBottomMargin)) / 2);
- setPivotX(((mInsets.left + getPaddingLeft())
- + (getWidth() - mInsets.right - getPaddingRight())) / 2);
+ // Update the pivots such that when the task is scaled, it fills the full page
+ getTaskSize(mTempRect);
+ getPagedViewOrientedState().getFullScreenScaleAndPivot(
+ mTempRect, mActivity.getDeviceProfile(), mTempPointF);
+ setPivotX(mTempPointF.x);
+ setPivotY(mTempPointF.y);
+ setTaskModalness(mTaskModalness);
+ updatePageOffsets();
+ setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
+ : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
+
+ private void updatePageOffsets() {
+ float offset = mAdjacentPageOffset * getWidth();
+ float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness) * getWidth();
+ if (mIsRtl) {
+ offset = -offset;
+ modalOffset = -modalOffset;
+ }
+ int count = getChildCount();
+
+ TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden
+ ? null : getTaskView(mRunningTaskId);
+ int midPoint = runningTask == null ? -1 : indexOfChild(runningTask);
+ int currentPage = getCurrentPage();
+
+ for (int i = 0; i < count; i++) {
+ float translation = i == midPoint ? 0 : (i < midPoint ? -offset : offset);
+ float modalTranslation =
+ i == currentPage ? 0 : (i < currentPage ? -modalOffset : modalOffset);
+ getChildAt(i).setTranslationX(translation + modalTranslation);
+ }
+ updateCurveProperties();
+ }
+
+ /**
+ * TODO: Do not assume motion across X axis for adjacent page
+ */
+ public float getPageOffsetScale() {
+ return Math.max(getWidth(), 1);
+ }
+
+ /**
+ * Resets the visuals when exit modal state.
+ */
+ public void resetModalVisuals() {
+ TaskView taskView = getCurrentPageTaskView();
+ if (taskView != null) {
+ taskView.getThumbnail().getTaskOverlay().resetModalVisuals();
+ }
}
private void updateDeadZoneRects() {
@@ -1695,22 +1885,17 @@
* If launching one of the adjacent tasks, parallax the center task and other adjacent task
* to the right.
*/
- public AnimatorSet createAdjacentPageAnimForTaskLaunch(
- TaskView tv, AppWindowAnimationHelper appWindowAnimationHelper) {
+ public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) {
AnimatorSet anim = new AnimatorSet();
int taskIndex = indexOfChild(tv);
int centerTaskIndex = getCurrentPage();
boolean launchingCenterTask = taskIndex == centerTaskIndex;
- LauncherState.ScaleAndTranslation toScaleAndTranslation = appWindowAnimationHelper
- .getScaleAndTranslation();
- float toScale = toScaleAndTranslation.scale;
- float toTranslationY = toScaleAndTranslation.translationY;
+ float toScale = getMaxScaleForFullScreen();
if (launchingCenterTask) {
RecentsView recentsView = tv.getRecentsView();
anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale));
- anim.play(ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, toTranslationY));
anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
} else {
// We are launching an adjacent task, so parallax the center and other adjacent task.
@@ -1729,6 +1914,15 @@
return anim;
}
+ /**
+ * Returns the scale up required on the view, so that it coves the screen completely
+ */
+ public float getMaxScaleForFullScreen() {
+ getTaskSize(mTempRect);
+ return getPagedViewOrientedState().getFullScreenScaleAndPivot(
+ mTempRect, mActivity.getDeviceProfile(), mTempPointF);
+ }
+
public PendingAnimation createTaskLaunchAnimation(
TaskView tv, long duration, Interpolator interpolator) {
if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
@@ -1763,11 +1957,7 @@
}
});
- AppWindowAnimationHelper appWindowAnimationHelper = new AppWindowAnimationHelper(
- getPagedViewOrientedState(), mActivity);
- appWindowAnimationHelper.fromTaskThumbnailView(tv.getThumbnail(), this);
- appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
- AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper);
+ AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv);
DepthController depthController = getDepthController();
if (depthController != null) {
@@ -1776,14 +1966,14 @@
anim.play(depthAnimator);
}
anim.play(progressAnim);
- anim.setDuration(duration).setInterpolator(interpolator);
+ anim.setInterpolator(interpolator);
mPendingAnimation = new PendingAnimation(duration);
mPendingAnimation.add(anim);
mPendingAnimation.addEndListener((endState) -> {
if (endState.isSuccess) {
Consumer<Boolean> onLaunchResult = (result) -> {
- onTaskLaunched(result);
+ onTaskLaunchAnimationEnd(result);
if (!result) {
tv.notifyTaskLaunchFailed(TAG);
}
@@ -1794,11 +1984,11 @@
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
endState.logAction, Direction.DOWN, indexOfChild(tv),
TaskUtils.getLaunchComponentKeyForTask(task.key));
- mActivity.getStatsLogManager().log(TASK_LAUNCH_SWIPE_DOWN, tv.buildProto()
- );
+ mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
+ .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
}
} else {
- onTaskLaunched(false);
+ onTaskLaunchAnimationEnd(false);
}
mPendingAnimation = null;
});
@@ -1810,12 +2000,17 @@
public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
- protected void onTaskLaunched(boolean success) {
+ protected void onTaskLaunchAnimationEnd(boolean success) {
if (success) {
resetTaskVisuals();
}
}
+ /**
+ * Called when task activity is launched
+ */
+ public void onTaskLaunched(Task task){ }
+
@Override
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
@@ -1885,11 +2080,6 @@
mRecentsAnimationTargets = recentsAnimationTargets;
}
- // TODO: To be removed in a follow up CL
- public void setAppWindowAnimationHelper(AppWindowAnimationHelper appWindowAnimationHelper) {
- mAppWindowAnimationHelper = appWindowAnimationHelper;
- }
-
public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
mLiveTileOverlayAttached = liveTileOverlayAttached;
}
@@ -1970,24 +2160,34 @@
return mClearAllButton;
}
+ @Override
+ protected boolean onOverscroll(int amount) {
+ // overscroll should only be accepted on -1 direction (for clear all button)
+ if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false;
+ return super.onOverscroll(amount);
+ }
+
/**
- * @return How many pixels the running task is offset on the x-axis due to the current scrollX.
+ * @return How many pixels the running task is offset on the currently laid out dominant axis.
*/
- public float getScrollOffset() {
- if (getRunningTaskIndex() == -1) {
+ public int getScrollOffset() {
+ return getScrollOffset(getRunningTaskIndex());
+ }
+
+ /**
+ * @return How many pixels the page is offset on the currently laid out dominant axis.
+ */
+ public int getScrollOffset(int pageIndex) {
+ if (pageIndex == -1) {
return 0;
}
- int startScroll = getScrollForPage(getRunningTaskIndex());
- int offsetX = startScroll - mOrientationHandler.getPrimaryScroll(this);
- offsetX *= mOrientationHandler.getPrimaryScale(this);
- return offsetX;
+ return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this);
}
public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
float degreesRotated;
if (navbarRotation == 0) {
- degreesRotated = mOrientationState.areMultipleLayoutOrientationsDisabled() ? 0 :
- RotationHelper.getDegreesFromRotation(mLayoutRotation);
+ degreesRotated = mOrientationHandler.getDegreesRotated();
} else {
degreesRotated = -navbarRotation;
}
@@ -2000,27 +2200,20 @@
// PagedOrientationHandler
return e -> {
if (navbarRotation != 0
- && !mOrientationState.areMultipleLayoutOrientationsDisabled()) {
- RotationHelper.transformEventForNavBar(e, true);
+ && mOrientationState.isMultipleOrientationSupportedByDevice()
+ && !mOrientationState.getOrientationHandler().isLayoutNaturalToLauncher()) {
+ mOrientationState.flipVertical(e);
super.onTouchEvent(e);
- RotationHelper.transformEventForNavBar(e, false);
+ mOrientationState.flipVertical(e);
return;
}
- RotationHelper.transformEvent(-degreesRotated, e, true);
+ mOrientationState.transformEvent(-degreesRotated, e, true);
super.onTouchEvent(e);
- RotationHelper.transformEvent(-degreesRotated, e, false);
+ mOrientationState.transformEvent(-degreesRotated, e, false);
};
}
- public AppWindowAnimationHelper getClipAnimationHelper() {
- return mAppWindowAnimationHelper;
- }
-
- public AppWindowAnimationHelper getTempAppWindowAnimationHelper() {
- return mTempAppWindowAnimationHelper;
- }
-
- public AppWindowAnimationHelper.TransformParams getLiveTileParams(
+ public TransformParams getLiveTileParams(
boolean mightNeedToRefill) {
return null;
}
@@ -2056,25 +2249,20 @@
}
}
- @Override
- public void addView(View child, int index) {
- // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
- // child direction back to match system settings.
- child.setLayoutDirection(
- Utilities.isRtl(getResources())
- ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
- super.addView(child, index);
- if (isExtraCardView(child, index)) {
- mTaskViewStartIndex++;
+ /**
+ * The current task is fully modal (modalness = 1) when it is shown on its own in a modal
+ * way. Modalness 0 means the task is shown in context with all the other tasks.
+ */
+ private void setTaskModalness(float modalness) {
+ mTaskModalness = modalness;
+ updatePageOffsets();
+ if (getCurrentPageTaskView() != null) {
+ getCurrentPageTaskView().setModalness(modalness);
}
- }
-
- @Override
- public void removeView(View view) {
- if (isExtraCardView(view, indexOfChild(view))) {
- mTaskViewStartIndex--;
- }
- super.removeView(view);
+ // Only show actions view when it's modal for in-place landscape mode.
+ boolean inPlaceLandscape = !mOrientationState.canRecentsActivityRotate()
+ && mOrientationState.getTouchRotation() != ROTATION_0;
+ mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape);
}
@Nullable
@@ -2082,9 +2270,21 @@
return null;
}
- private boolean isExtraCardView(View view, int index) {
- return !(view instanceof TaskView) && !(view instanceof ClearAllButton)
- && index <= mTaskViewStartIndex;
+ @Override
+ public void onSecondaryWindowBoundsChanged() {
+ // Invalidate the task view size
+ setInsets(mInsets);
+ requestLayout();
+ }
+
+ /**
+ * Enables or disables modal state for RecentsView
+ * @param isModalState
+ */
+ public void setModalStateEnabled(boolean isModalState) { }
+
+ public BaseActivityInterface getSizeStrategy() {
+ return mSizeStrategy;
}
/**
@@ -2113,34 +2313,4 @@
mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
}
}
-
- private void showActionsView() {
- if (mActionsView != null && getTaskViewCount() > 0) {
- mActionsView.setVisibility(VISIBLE);
- }
- }
-
- private void hideActionsView() {
- if (mActionsView != null) {
- mActionsView.setVisibility(GONE);
- }
- }
-
- private void setActionsView() {
- if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get()
- && SysUINavigationMode.removeShelfFromOverview(mActivity)) {
- mActionsView = ((ViewGroup) getParent()).findViewById(R.id.overview_actions_view);
- if (mActionsView != null) {
- Rect rect = new Rect();
- getTaskSize(rect);
- InsettableFrameLayout.LayoutParams layoutParams =
- new InsettableFrameLayout.LayoutParams(rect.width(),
- getResources().getDimensionPixelSize(
- R.dimen.overview_actions_height));
- layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
- mActionsView.setLayoutParams(layoutParams);
- showActionsView();
- }
- }
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index 80022b4..ef66b7a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -40,6 +40,7 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.TaskOverlayFactory;
@@ -150,9 +151,26 @@
return (type & TYPE_TASK_MENU) != 0;
}
- public void setPosition(float x, float y) {
- setX(x);
- setY(y + mThumbnailTopMargin);
+ public void setPosition(float x, float y, PagedOrientationHandler pagedOrientationHandler) {
+ float adjustedY = y + mThumbnailTopMargin;
+ // Changing pivot to make computations easier
+ // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
+ // which would render the X and Y position set here incorrect
+ setPivotX(0);
+ setPivotY(0);
+ setRotation(pagedOrientationHandler.getDegreesRotated());
+ setX(pagedOrientationHandler.getTaskMenuX(x, mTaskView.getThumbnail()));
+ setY(pagedOrientationHandler.getTaskMenuY(adjustedY, mTaskView.getThumbnail()));
+ }
+
+ public void onRotationChanged() {
+ if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
+ mOpenCloseAnimator.end();
+ }
+ if (mIsOpen) {
+ mOptionLayout.removeAllViews();
+ populateAndLayoutMenu();
+ }
}
public static TaskMenuView showForTask(TaskView taskView) {
@@ -168,12 +186,16 @@
}
mActivity.getDragLayer().addView(this);
mTaskView = taskView;
- addMenuOptions(mTaskView);
- orientAroundTaskView(mTaskView);
+ populateAndLayoutMenu();
post(this::animateOpen);
return true;
}
+ private void populateAndLayoutMenu() {
+ addMenuOptions(mTaskView);
+ orientAroundTaskView(mTaskView);
+ }
+
private void addMenuOptions(TaskView taskView) {
Drawable icon = taskView.getTask().icon.getConstantState().newDrawable();
mTaskIcon.setDrawable(icon);
@@ -200,21 +222,28 @@
R.layout.task_view_menu_option, this, false);
menuOption.setIconAndLabelFor(
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
+ LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
+ mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp);
menuOptionView.setOnClickListener(menuOption);
mOptionLayout.addView(menuOptionView);
}
private void orientAroundTaskView(TaskView taskView) {
+ PagedOrientationHandler orientationHandler = taskView.getPagedOrientationHandler();
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
Rect insets = mActivity.getDragLayer().getInsets();
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
- params.width = taskView.getMeasuredWidth();
- params.gravity = Gravity.START;
+ params.width = orientationHandler.getTaskMenuWidth(taskView.getThumbnail());
+ // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
+ params.gravity = Gravity.LEFT;
setLayoutParams(params);
setScaleX(taskView.getScaleX());
setScaleY(taskView.getScaleY());
- setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top);
+ mOptionLayout.setOrientation(orientationHandler
+ .getTaskMenuLayoutOrientation(mOptionLayout));
+ setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top,
+ taskView.getPagedOrientationHandler());
}
private void animateOpen() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 178ff32..b2f937f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -27,6 +27,7 @@
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
@@ -34,23 +35,26 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
+import android.os.Build;
import android.util.AttributeSet;
import android.util.FloatProperty;
-import android.util.Log;
import android.util.Property;
import android.view.Surface;
import android.view.View;
-import android.view.ViewGroup;
+
+import androidx.annotation.RequiresApi;
import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
-import com.android.quickstep.util.TaskCornerRadius;
+import com.android.quickstep.views.TaskView.FullscreenDrawParams;
import com.android.systemui.plugins.OverviewScreenshotActions;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.recents.model.Task;
@@ -64,7 +68,9 @@
private static final ColorMatrix COLOR_MATRIX = new ColorMatrix();
private static final ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
- private static final RectF EMPTY_RECT_F = new RectF();
+
+ private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
+ new MainThreadInitializedObject<>(FullscreenDrawParams::new);
public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
new FloatProperty<TaskThumbnailView>("dimAlpha") {
@@ -87,11 +93,9 @@
private final Paint mClearPaint = new Paint();
private final Paint mDimmingPaintAfterClearing = new Paint();
- private final Matrix mMatrix = new Matrix();
-
- private float mClipBottom = -1;
// Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
- private RectF mClippedInsets = new RectF();
+ private final Rect mPreviewRect = new Rect();
+ private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper();
private TaskView.FullscreenDrawParams mFullscreenParams;
private Task mTask;
@@ -103,7 +107,6 @@
private float mSaturation = 1f;
private boolean mOverlayEnabled;
- private boolean mIsOrientationChanged;
private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
public TaskThumbnailView(Context context) {
@@ -123,9 +126,14 @@
mDimmingPaintAfterClearing.setColor(Color.BLACK);
mActivity = BaseActivity.fromContext(context);
mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
- mFullscreenParams = new TaskView.FullscreenDrawParams(TaskCornerRadius.get(context));
+ // Initialize with dummy value. It is overridden later by TaskView
+ mFullscreenParams = TEMP_PARAMS.get(context);
}
+ /**
+ * Updates the thumbnail to draw the provided task
+ * @param task
+ */
public void bind(Task task) {
mOverlay.reset();
mTask = task;
@@ -172,8 +180,7 @@
mOverlay.reset();
}
if (mOverviewScreenshotActionsPlugin != null) {
- mOverviewScreenshotActionsPlugin
- .setupActions((ViewGroup) getTaskView(), getThumbnail(), mActivity);
+ mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
}
updateThumbnailPaintFilter();
}
@@ -193,11 +200,6 @@
updateThumbnailPaintFilter();
}
- public void setSaturation(float saturation) {
- mSaturation = saturation;
- updateThumbnailPaintFilter();
- }
-
public TaskOverlay getTaskOverlay() {
return mOverlay;
}
@@ -213,6 +215,38 @@
return fallback;
}
+ /**
+ * Get the scaled insets that are being used to draw the task view. This is a subsection of
+ * the full snapshot.
+ * @return the insets in snapshot bitmap coordinates.
+ */
+ @RequiresApi(api = Build.VERSION_CODES.Q)
+ public Insets getScaledInsets() {
+ if (mThumbnailData == null) {
+ return Insets.NONE;
+ }
+
+ RectF bitmapRect = new RectF(
+ 0, 0,
+ mThumbnailData.thumbnail.getWidth(), mThumbnailData.thumbnail.getHeight());
+ RectF viewRect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
+
+ // The position helper matrix tells us how to transform the bitmap to fit the view, the
+ // inverse tells us where the view would be in the bitmaps coordinates. The insets are the
+ // difference between the bitmap bounds and the projected view bounds.
+ Matrix boundsToBitmapSpace = new Matrix();
+ mPreviewPositionHelper.getMatrix().invert(boundsToBitmapSpace);
+ RectF boundsInBitmapSpace = new RectF();
+ boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
+
+ return Insets.of(
+ Math.round(boundsInBitmapSpace.left),
+ Math.round(boundsInBitmapSpace.top),
+ Math.round(bitmapRect.right - boundsInBitmapSpace.right),
+ Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom));
+ }
+
+
public int getSysUiStatusNavFlags() {
if (mThumbnailData != null) {
int flags = 0;
@@ -231,8 +265,8 @@
protected void onDraw(Canvas canvas) {
RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets;
canvas.save();
- canvas.translate(currentDrawnInsets.left, currentDrawnInsets.top);
canvas.scale(mFullscreenParams.mScale, mFullscreenParams.mScale);
+ canvas.translate(currentDrawnInsets.left, currentDrawnInsets.top);
// Draw the insets if we're being drawn fullscreen (we do this for quick switch).
drawOnCanvas(canvas,
-currentDrawnInsets.left,
@@ -270,9 +304,8 @@
PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
}
- public RectF getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
- // Don't show insets in multi window mode.
- return isMultiWindowMode ? EMPTY_RECT_F : mClippedInsets;
+ public PreviewPositionHelper getPreviewPositionHelper() {
+ return mPreviewPositionHelper;
}
public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
@@ -294,16 +327,17 @@
// Draw the background in all cases, except when the thumbnail data is opaque
final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
|| mThumbnailData == null;
- if (drawBackgroundOnly || mClipBottom > 0 || mThumbnailData.isTranslucent) {
+ if (drawBackgroundOnly || mPreviewPositionHelper.mClipBottom > 0
+ || mThumbnailData.isTranslucent) {
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
if (drawBackgroundOnly) {
return;
}
}
- if (mClipBottom > 0) {
+ if (mPreviewPositionHelper.mClipBottom > 0) {
canvas.save();
- canvas.clipRect(x, y, width, mClipBottom);
+ canvas.clipRect(x, y, width, mPreviewPositionHelper.mClipBottom);
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
canvas.restore();
} else {
@@ -323,9 +357,9 @@
}
private void updateOverlay() {
- // The overlay doesn't really work when the screenshot is rotated, so don't add it.
- if (mOverlayEnabled && !mIsOrientationChanged && mBitmapShader != null && mThumbnailData != null) {
- mOverlay.initOverlay(mTask, mThumbnailData, mMatrix);
+ if (mOverlayEnabled && mBitmapShader != null && mThumbnailData != null) {
+ mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
+ mPreviewPositionHelper.mIsOrientationChanged);
} else {
mOverlay.reset();
}
@@ -346,76 +380,21 @@
}
private void updateThumbnailMatrix() {
- boolean isRotated = false;
- boolean isOrientationDifferent = false;
- mClipBottom = -1;
+ mPreviewPositionHelper.mClipBottom = -1;
+ mPreviewPositionHelper.mIsOrientationChanged = false;
if (mBitmapShader != null && mThumbnailData != null) {
- float scale = mThumbnailData.scale;
- Rect thumbnailInsets = mThumbnailData.insets;
- final float thumbnailWidth = mThumbnailData.thumbnail.getWidth() -
- (thumbnailInsets.left + thumbnailInsets.right) * scale;
- final float thumbnailHeight = mThumbnailData.thumbnail.getHeight() -
- (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
-
- final float thumbnailScale;
- int thumbnailRotation = mThumbnailData.rotation;
+ mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
+ mThumbnailData.thumbnail.getHeight());
int currentRotation = ConfigurationCompat.getWindowConfigurationRotation(
- getResources().getConfiguration());
- int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
- // Landscape vs portrait change
- boolean windowingModeSupportsRotation = !mActivity.isInMultiWindowMode()
- && mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
- isOrientationDifferent = isOrientationChange(deltaRotate)
- && windowingModeSupportsRotation;
- if (getMeasuredWidth() == 0) {
- // If we haven't measured , skip the thumbnail drawing and only draw the background
- // color
- thumbnailScale = 0f;
- } else {
- // Rotate the screenshot if not in multi-window mode
- isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
- // Scale the screenshot to always fit the width of the card.
+ mActivity.getResources().getConfiguration());
+ mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
+ getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
+ currentRotation);
- thumbnailScale = isOrientationDifferent
- ? getMeasuredWidth() / thumbnailHeight
- : getMeasuredWidth() / thumbnailWidth;
- }
-
- if (!isRotated) {
- // No Rotation
- mClippedInsets.offsetTo(thumbnailInsets.left * scale,
- thumbnailInsets.top * scale);
- mMatrix.setTranslate(-mClippedInsets.left, -mClippedInsets.top);
- } else {
- setThumbnailRotation(deltaRotate, thumbnailInsets, scale);
- }
-
- final float widthWithInsets;
- final float heightWithInsets;
- if (isOrientationDifferent) {
- widthWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale;
- heightWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale;
- } else {
- widthWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale;
- heightWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale;
- }
- mClippedInsets.left *= thumbnailScale;
- mClippedInsets.top *= thumbnailScale;
- mClippedInsets.right = widthWithInsets - mClippedInsets.left - getMeasuredWidth();
- mClippedInsets.bottom = heightWithInsets - mClippedInsets.top - getMeasuredHeight();
-
- mMatrix.postScale(thumbnailScale, thumbnailScale);
- mBitmapShader.setLocalMatrix(mMatrix);
-
- float bitmapHeight = Math.max((isOrientationDifferent ? thumbnailWidth : thumbnailHeight)
- * thumbnailScale, 0);
- if (Math.round(bitmapHeight) < getMeasuredHeight()) {
- mClipBottom = bitmapHeight;
- }
+ mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
mPaint.setShader(mBitmapShader);
}
-
- mIsOrientationChanged = isOrientationDifferent;
+ getTaskView().updateCurrentFullscreenParams(mPreviewPositionHelper);
invalidate();
// Update can be called from {@link #onSizeChanged} during layout, post handling of overlay
@@ -423,51 +402,6 @@
post(this::updateOverlay);
}
- private int getRotationDelta(int oldRotation, int newRotation) {
- int delta = newRotation - oldRotation;
- if (delta < 0) delta += 4;
- return delta;
- }
-
- /**
- * @param deltaRotation the number of 90 degree turns from the current orientation
- * @return {@code true} if the change in rotation results in a shift from landscape to portrait
- * or vice versa, {@code false} otherwise
- */
- private boolean isOrientationChange(int deltaRotation) {
- return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
- }
-
- private void setThumbnailRotation(int deltaRotate, Rect thumbnailInsets, float scale) {
- int newLeftInset = 0;
- int newTopInset = 0;
- int translateX = 0;
- int translateY = 0;
-
- mMatrix.setRotate(90 * deltaRotate);
- switch (deltaRotate) { /* Counter-clockwise */
- case Surface.ROTATION_90:
- newLeftInset = thumbnailInsets.bottom;
- newTopInset = thumbnailInsets.left;
- translateX = mThumbnailData.thumbnail.getHeight();
- break;
- case Surface.ROTATION_270:
- newLeftInset = thumbnailInsets.top;
- newTopInset = thumbnailInsets.right;
- translateY = mThumbnailData.thumbnail.getWidth();
- break;
- case Surface.ROTATION_180:
- newLeftInset = -thumbnailInsets.top;
- newTopInset = -thumbnailInsets.left;
- translateX = mThumbnailData.thumbnail.getWidth();
- translateY = mThumbnailData.thumbnail.getHeight();
- break;
- }
- mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
- mMatrix.postTranslate(translateX - mClippedInsets.left,
- translateY - mClippedInsets.top);
- }
-
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@@ -511,4 +445,178 @@
}
return mThumbnailData.thumbnail;
}
+
+ /**
+ * Returns whether the snapshot is real.
+ */
+ public boolean isRealSnapshot() {
+ if (mThumbnailData == null) {
+ return false;
+ }
+ return mThumbnailData.isRealSnapshot;
+ }
+
+ /**
+ * Utility class to position the thumbnail in the TaskView
+ */
+ public static class PreviewPositionHelper {
+
+ // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
+ private final RectF mClippedInsets = new RectF();
+ private final Matrix mMatrix = new Matrix();
+ private float mClipBottom = -1;
+ private boolean mIsOrientationChanged;
+
+ public Matrix getMatrix() {
+ return mMatrix;
+ }
+
+ /**
+ * Updates the matrix based on the provided parameters
+ */
+ public void updateThumbnailMatrix(Rect thumbnailPosition, ThumbnailData thumbnailData,
+ int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation) {
+ boolean isRotated = false;
+ boolean isOrientationDifferent;
+ mClipBottom = -1;
+
+ float scale = thumbnailData.scale;
+ Rect activityInsets = dp.getInsets();
+ Rect thumbnailInsets = getBoundedInsets(activityInsets, thumbnailData.insets);
+ final float thumbnailWidth = thumbnailPosition.width()
+ - (thumbnailInsets.left + thumbnailInsets.right) * scale;
+ final float thumbnailHeight = thumbnailPosition.height()
+ - (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
+
+ final float thumbnailScale;
+ int thumbnailRotation = thumbnailData.rotation;
+ int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
+
+ // Landscape vs portrait change
+ boolean windowingModeSupportsRotation = !dp.isMultiWindowMode
+ && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
+ isOrientationDifferent = isOrientationChange(deltaRotate)
+ && windowingModeSupportsRotation;
+ if (canvasWidth == 0) {
+ // If we haven't measured , skip the thumbnail drawing and only draw the background
+ // color
+ thumbnailScale = 0f;
+ } else {
+ // Rotate the screenshot if not in multi-window mode
+ isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
+ // Scale the screenshot to always fit the width of the card.
+ thumbnailScale = isOrientationDifferent
+ ? canvasWidth / thumbnailHeight
+ : canvasWidth / thumbnailWidth;
+ }
+
+ Rect splitScreenInsets = dp.getInsets();
+ if (!isRotated) {
+ // No Rotation
+ if (dp.isMultiWindowMode) {
+ mClippedInsets.offsetTo(splitScreenInsets.left * scale,
+ splitScreenInsets.top * scale);
+ } else {
+ mClippedInsets.offsetTo(thumbnailInsets.left * scale,
+ thumbnailInsets.top * scale);
+ }
+ mMatrix.setTranslate(
+ -thumbnailInsets.left * scale,
+ -thumbnailInsets.top * scale);
+ } else {
+ setThumbnailRotation(deltaRotate, thumbnailInsets, scale, thumbnailPosition);
+ }
+
+ final float widthWithInsets;
+ final float heightWithInsets;
+ if (isOrientationDifferent) {
+ widthWithInsets = thumbnailPosition.height() * thumbnailScale;
+ heightWithInsets = thumbnailPosition.width() * thumbnailScale;
+ } else {
+ widthWithInsets = thumbnailPosition.width() * thumbnailScale;
+ heightWithInsets = thumbnailPosition.height() * thumbnailScale;
+ }
+ mClippedInsets.left *= thumbnailScale;
+ mClippedInsets.top *= thumbnailScale;
+
+ if (dp.isMultiWindowMode) {
+ mClippedInsets.right = splitScreenInsets.right * scale * thumbnailScale;
+ mClippedInsets.bottom = splitScreenInsets.bottom * scale * thumbnailScale;
+ } else {
+ mClippedInsets.right = Math.max(0,
+ widthWithInsets - mClippedInsets.left - canvasWidth);
+ mClippedInsets.bottom = Math.max(0,
+ heightWithInsets - mClippedInsets.top - canvasHeight);
+ }
+
+ mMatrix.postScale(thumbnailScale, thumbnailScale);
+
+ float bitmapHeight = Math.max(0,
+ (isOrientationDifferent ? thumbnailWidth : thumbnailHeight) * thumbnailScale);
+ if (Math.round(bitmapHeight) < canvasHeight) {
+ mClipBottom = bitmapHeight;
+ }
+ mIsOrientationChanged = isOrientationDifferent;
+ }
+
+ private Rect getBoundedInsets(Rect activityInsets, Rect insets) {
+ return new Rect(Math.min(insets.left, activityInsets.left),
+ Math.min(insets.top, activityInsets.top),
+ Math.min(insets.right, activityInsets.right),
+ Math.min(insets.bottom, activityInsets.bottom));
+ }
+
+ private int getRotationDelta(int oldRotation, int newRotation) {
+ int delta = newRotation - oldRotation;
+ if (delta < 0) delta += 4;
+ return delta;
+ }
+
+ /**
+ * @param deltaRotation the number of 90 degree turns from the current orientation
+ * @return {@code true} if the change in rotation results in a shift from landscape to
+ * portrait or vice versa, {@code false} otherwise
+ */
+ private boolean isOrientationChange(int deltaRotation) {
+ return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+ }
+
+ private void setThumbnailRotation(int deltaRotate, Rect thumbnailInsets, float scale,
+ Rect thumbnailPosition) {
+ int newLeftInset = 0;
+ int newTopInset = 0;
+ int translateX = 0;
+ int translateY = 0;
+
+ mMatrix.setRotate(90 * deltaRotate);
+ switch (deltaRotate) { /* Counter-clockwise */
+ case Surface.ROTATION_90:
+ newLeftInset = thumbnailInsets.bottom;
+ newTopInset = thumbnailInsets.left;
+ translateX = thumbnailPosition.height();
+ break;
+ case Surface.ROTATION_270:
+ newLeftInset = thumbnailInsets.top;
+ newTopInset = thumbnailInsets.right;
+ translateY = thumbnailPosition.width();
+ break;
+ case Surface.ROTATION_180:
+ newLeftInset = -thumbnailInsets.top;
+ newTopInset = -thumbnailInsets.left;
+ translateX = thumbnailPosition.width();
+ translateY = thumbnailPosition.height();
+ break;
+ }
+ mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
+ mMatrix.postTranslate(translateX - mClippedInsets.left,
+ translateY - mClippedInsets.top);
+ }
+
+ /**
+ * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
+ */
+ public RectF getInsetsToDrawInFullscreen() {
+ return mClippedInsets;
+ }
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 7010f9a..222f6e6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -25,11 +25,14 @@
import static android.widget.Toast.LENGTH_SHORT;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.Utilities.comp;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_LAUNCH_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
+ .LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -38,13 +41,15 @@
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.InsetDrawable;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Process;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
@@ -56,15 +61,16 @@
import android.widget.Toast;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
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.logger.LauncherAtom;
import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
@@ -78,10 +84,11 @@
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.TaskCornerRadius;
import com.android.quickstep.views.RecentsView.PageCallbacks;
import com.android.quickstep.views.RecentsView.ScrollState;
+import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
@@ -119,19 +126,6 @@
private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
Collections.singletonList(new Rect());
- public static final FloatProperty<TaskView> FULLSCREEN_PROGRESS =
- new FloatProperty<TaskView>("fullscreenProgress") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setFullscreenProgress(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mFullscreenProgress;
- }
- };
-
private static final FloatProperty<TaskView> FOCUS_TRANSITION =
new FloatProperty<TaskView>("focusTransition") {
@Override
@@ -170,13 +164,12 @@
private float mCurveScale;
private float mFullscreenProgress;
private final FullscreenDrawParams mCurrentFullscreenParams;
- private final float mCornerRadius;
- private final float mWindowCornerRadius;
private final BaseDraggingActivity mActivity;
private ObjectAnimator mIconAndDimAnimator;
private float mIconScaleAnimStartProgress = 0;
private float mFocusTransitionProgress = 1;
+ private float mModalness = 0;
private float mStableAlpha = 1;
private boolean mShowScreenshot;
@@ -187,11 +180,12 @@
// Order in which the footers appear. Lower order appear below higher order.
public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0;
- public static final int INDEX_PROACTIVE_SUGGEST = 1;
private final FooterWrapper[] mFooters = new FooterWrapper[2];
private float mFooterVerticalOffset = 0;
private float mFooterAlpha = 1;
private int mStackHeight;
+ private View mContextualChipWrapper;
+ private View mContextualChip;
public TaskView(Context context) {
this(context, null);
@@ -221,26 +215,30 @@
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
TaskUtils.getLaunchComponentKeyForTask(getTask().key));
- mActivity.getStatsLogManager().log(TASK_LAUNCH_TAP, buildProto());
+ mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
+ .log(LAUNCHER_TASK_LAUNCH_TAP);
});
- mCornerRadius = TaskCornerRadius.get(context);
- mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
- mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
+
+ mCurrentFullscreenParams = new FullscreenDrawParams(context);
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams);
setOutlineProvider(mOutlineProvider);
}
- /* Builds proto for logging */
- protected LauncherAtom.ItemInfo buildProto() {
+ /**
+ * Builds proto for logging
+ */
+ public WorkspaceItemInfo getItemInfo() {
ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key);
- LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
- itemBuilder.setIsWork(componentKey.user != Process.myUserHandle());
- itemBuilder.setTask(LauncherAtom.Task.newBuilder()
- .setComponentName(componentKey.componentName.flattenToShortString())
- .setIndex(getRecentsView().indexOfChild(this)));
- return itemBuilder.build();
+ WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
+ dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
+ dummyInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
+ dummyInfo.user = componentKey.user;
+ dummyInfo.intent = new Intent().setComponent(componentKey.componentName);
+ dummyInfo.title = TaskUtils.getTitle(getContext(), getTask());
+ dummyInfo.screenId = getRecentsView().indexOfChild(this);
+ return dummyInfo;
}
@Override
@@ -248,31 +246,26 @@
super.onFinishInflate();
mSnapshotView = findViewById(R.id.snapshot);
mIconView = findViewById(R.id.icon);
- final Context context = getContext();
-
- TaskView.LayoutParams thumbnailParams = (LayoutParams) mSnapshotView.getLayoutParams();
- thumbnailParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(context);
- mSnapshotView.setLayoutParams(thumbnailParams);
}
- public boolean isTaskOverlayModal() {
- return mSnapshotView.getTaskOverlay().isOverlayModal();
- }
+ /**
+ * The modalness of this view is how it should be displayed when it is shown on its own in the
+ * modal state of overview.
+ *
+ * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own.
+ */
+ public void setModalness(float modalness) {
+ mModalness = modalness;
+ mIconView.setAlpha(comp(modalness));
+ if (mContextualChip != null) {
+ mContextualChip.setScaleX(comp(modalness));
+ mContextualChip.setScaleY(comp(modalness));
+ }
+ if (mContextualChipWrapper != null) {
+ mContextualChipWrapper.setAlpha(comp(modalness));
+ }
- /** Updates UI based on whether the task is modal. */
- public void updateUiForModalTask() {
- boolean isOverlayModal = isTaskOverlayModal();
- if (getRecentsView() != null) {
- getRecentsView().updateUiForModalTask(this, isOverlayModal);
- }
- // Hide footers when overlay is modal.
- if (isOverlayModal) {
- for (FooterWrapper footer : mFooters) {
- if (footer != null) {
- footer.animateHide();
- }
- }
- }
+ updateFooterVerticalOffset(mFooterVerticalOffset);
}
public TaskMenuView getMenuView() {
@@ -285,12 +278,15 @@
/**
* Updates this task view to the given {@param task}.
+ *
+ * TODO(b/142282126) Re-evaluate if we need to pass in isMultiWindowMode after
+ * that issue is fixed
*/
- public void bind(Task task, int recentsRotation) {
+ public void bind(Task task, RecentsOrientedState orientedState) {
cancelPendingLoadTasks();
mTask = task;
mSnapshotView.bind(task);
- setOverviewRotation(recentsRotation);
+ setOrientationState(orientedState);
}
public Task getTask() {
@@ -389,6 +385,7 @@
}
}, resultCallbackHandler);
}
+ getRecentsView().onTaskLaunched(mTask);
}
}
@@ -434,12 +431,17 @@
}
private boolean showTaskMenu(int action) {
- getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
- mMenuView = TaskMenuView.showForTask(this);
- UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE,
- LauncherLogProto.ItemType.TASK_ICON);
- if (mMenuView != null) {
- mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
+ if (!getRecentsView().isClearAllHidden()) {
+ getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
+ } else {
+ mMenuView = TaskMenuView.showForTask(this);
+ mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
+ .log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS);
+ UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE,
+ LauncherLogProto.ItemType.TASK_ICON);
+ if (mMenuView != null) {
+ mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
+ }
}
return mMenuView != null;
}
@@ -459,19 +461,18 @@
}
}
- void setOverviewRotation(int iconRotation) {
- PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
- boolean isRtl = orientationHandler.getRecentsRtlSetting(getResources());
+ public void setOrientationState(RecentsOrientedState orientationState) {
+ PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
- snapshotParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(getContext());
int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
- int rotation = RotationHelper.getDegreesFromRotation(iconRotation);
- switch (iconRotation) {
+ switch (orientationHandler.getRotation()) {
case Surface.ROTATION_90:
- iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
+ iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
iconParams.rightMargin = -thumbnailPadding;
- iconParams.leftMargin = iconParams.topMargin = iconParams.bottomMargin = 0;
+ iconParams.leftMargin = 0;
+ iconParams.topMargin = snapshotParams.topMargin / 2;
break;
case Surface.ROTATION_180:
iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
@@ -481,18 +482,21 @@
case Surface.ROTATION_270:
iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
iconParams.leftMargin = -thumbnailPadding;
- iconParams.rightMargin = iconParams.topMargin = iconParams.bottomMargin = 0;
+ iconParams.rightMargin = 0;
+ iconParams.topMargin = snapshotParams.topMargin / 2;
break;
case Surface.ROTATION_0:
default:
iconParams.gravity = TOP | CENTER_HORIZONTAL;
- iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin =
- iconParams.bottomMargin = 0;
+ iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
break;
}
- mSnapshotView.setLayoutParams(snapshotParams);
mIconView.setLayoutParams(iconParams);
- mIconView.setRotation(rotation);
+ mIconView.setRotation(orientationHandler.getDegreesRotated());
+
+ if (mMenuView != null) {
+ mMenuView.onRotationChanged();
+ }
}
private void setIconAndDimTransitionProgress(float progress, boolean invert) {
@@ -509,12 +513,7 @@
mIconView.setScaleX(scale);
mIconView.setScaleY(scale);
- mFooterVerticalOffset = 1.0f - scale;
- for (FooterWrapper footer : mFooters) {
- if (footer != null) {
- footer.updateFooterOffset();
- }
- }
+ updateFooterVerticalOffset(1.0f - scale);
}
public void setIconScaleAnimStartProgress(float startProgress) {
@@ -548,7 +547,7 @@
setIconAndDimTransitionProgress(iconScale, invert);
}
- private void resetViewTransforms() {
+ protected void resetViewTransforms() {
setCurveScale(1);
setTranslationX(0f);
setTranslationY(0f);
@@ -557,11 +556,6 @@
setIconScaleAndDim(1);
}
- public void resetVisualProperties() {
- resetViewTransforms();
- setFullscreenProgress(0);
- }
-
public void setStableAlpha(float parentAlpha) {
mStableAlpha = parentAlpha;
setAlpha(mStableAlpha);
@@ -579,11 +573,15 @@
@Override
public void onPageScroll(ScrollState scrollState) {
+ // Don't do anything if it's modal.
+ if (mModalness > 0) {
+ return;
+ }
+
float curveInterpolation =
CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
float curveScaleForCurveInterpolation = getCurveScaleForCurveInterpolation(
curveInterpolation);
-
mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
setCurveScale(curveScaleForCurveInterpolation);
@@ -595,7 +593,10 @@
}
if (mMenuView != null) {
- mMenuView.setPosition(getX() - getRecentsView().getScrollX(), getY());
+ PagedOrientationHandler pagedOrientationHandler = getPagedOrientationHandler();
+ RecentsView recentsView = getRecentsView();
+ mMenuView.setPosition(getX() - recentsView.getScrollX(),
+ getY() - recentsView.getScrollY(), pagedOrientationHandler);
mMenuView.setScaleX(getScaleX());
mMenuView.setScaleY(getScaleY());
}
@@ -652,6 +653,68 @@
return oldFooter;
}
+ /**
+ * Sets the contextual chip.
+ *
+ * @param view Wrapper view containing contextual chip.
+ */
+ public void setContextualChip(View view) {
+ if (mContextualChipWrapper != null) {
+ removeView(mContextualChipWrapper);
+ }
+ if (view != null) {
+ mContextualChipWrapper = view;
+ LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
+ int expectedChipHeight = getExpectedViewHeight(view);
+ float chipOffset = getResources().getDimension(R.dimen.chip_hint_vertical_offset);
+ layoutParams.bottomMargin = (int)
+ (((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin
+ - expectedChipHeight + chipOffset);
+ mContextualChip = ((FrameLayout) mContextualChipWrapper).getChildAt(0);
+ mContextualChip.setScaleX(0f);
+ mContextualChip.setScaleY(0f);
+ GradientDrawable scrimDrawable = (GradientDrawable) getResources().getDrawable(
+ R.drawable.chip_scrim_gradient, mActivity.getTheme());
+ float cornerRadius = getTaskCornerRadius();
+ scrimDrawable.setCornerRadii(
+ new float[]{0, 0, 0, 0, cornerRadius, cornerRadius, cornerRadius,
+ cornerRadius});
+ InsetDrawable scrimDrawableInset = new InsetDrawable(scrimDrawable, 0, 0, 0,
+ (int) (expectedChipHeight - chipOffset));
+ mContextualChipWrapper.setBackground(scrimDrawableInset);
+ mContextualChipWrapper.setPadding(0, 0, 0, 0);
+ mContextualChipWrapper.setAlpha(0f);
+ addView(view, getChildCount(), layoutParams);
+ if (mContextualChip != null) {
+ mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50);
+ }
+ if (mContextualChipWrapper != null) {
+ mContextualChipWrapper.animate().alpha(1f).setDuration(50);
+ }
+ }
+ }
+
+ public float getTaskCornerRadius() {
+ return TaskCornerRadius.get(mActivity);
+ }
+
+ /**
+ * Clears the contextual chip from TaskView.
+ *
+ * @return The contextual chip wrapper view to be recycled.
+ */
+ public View clearContextualChip() {
+ if (mContextualChipWrapper != null) {
+ removeView(mContextualChipWrapper);
+ }
+ View oldContextualChipWrapper = mContextualChipWrapper;
+ mContextualChipWrapper = null;
+ mContextualChip = null;
+ return oldContextualChipWrapper;
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -668,6 +731,12 @@
mStackHeight += footer.mView.getHeight();
}
}
+ updateFooterVerticalOffset(0);
+ }
+
+ private void updateFooterVerticalOffset(float offset) {
+ mFooterVerticalOffset = offset;
+
for (FooterWrapper footer : mFooters) {
if (footer != null) {
footer.updateFooterOffset();
@@ -684,21 +753,16 @@
return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
}
- public void setCurveScale(float curveScale) {
+ private void setCurveScale(float curveScale) {
mCurveScale = curveScale;
- onScaleChanged();
+ setScaleX(mCurveScale);
+ setScaleY(mCurveScale);
}
public float getCurveScale() {
return mCurveScale;
}
- private void onScaleChanged() {
- float scale = mCurveScale;
- setScaleX(scale);
- setScaleY(scale);
- }
-
@Override
public boolean hasOverlappingRendering() {
// TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
@@ -708,13 +772,11 @@
private static final class TaskOutlineProvider extends ViewOutlineProvider {
private final int mMarginTop;
- private final int mMarginBottom;
private FullscreenDrawParams mFullscreenParams;
TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams) {
mMarginTop = context.getResources().getDimensionPixelSize(
R.dimen.task_thumbnail_top_margin);
- mMarginBottom = LayoutUtils.thumbnailBottomMargin(context);
mFullscreenParams = fullscreenParams;
}
@@ -729,7 +791,7 @@
outline.setRoundRect(0,
(int) (mMarginTop * scale),
(int) ((insets.left + view.getWidth() + insets.right) * scale),
- (int) ((insets.top + view.getHeight() + insets.bottom - mMarginBottom) * scale),
+ (int) ((insets.top + view.getHeight() + insets.bottom) * scale),
mFullscreenParams.mCurrentDrawnCornerRadius);
}
}
@@ -752,14 +814,7 @@
mDelegate = mOldOutlineProvider == null
? ViewOutlineProvider.BACKGROUND : mOldOutlineProvider;
- int h = view.getLayoutParams().height;
- if (h > 0) {
- mExpectedHeight = h;
- } else {
- int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
- view.measure(m, m);
- mExpectedHeight = view.getMeasuredHeight();
- }
+ mExpectedHeight = getExpectedViewHeight(view);
mOldPaddingBottom = view.getPaddingBottom();
if (mOldOutlineProvider != null) {
@@ -780,7 +835,8 @@
}
void updateFooterOffset() {
- mAnimationOffset = Math.round(mStackHeight * mFooterVerticalOffset);
+ float offset = Utilities.or(mFooterVerticalOffset, mModalness);
+ mAnimationOffset = Math.round(mStackHeight * offset);
mView.setTranslationY(mAnimationOffset + mEntryAnimationOffset
+ mCurrentFullscreenParams.mCurrentDrawnInsets.bottom
+ mCurrentFullscreenParams.mCurrentDrawnInsets.top);
@@ -803,22 +859,19 @@
animator.setDuration(100);
animator.start();
}
+ }
- void animateHide() {
- ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
- animator.addUpdateListener(anim -> {
- mFooterVerticalOffset = anim.getAnimatedFraction();
- updateFooterOffset();
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- removeView(mView);
- }
- });
- animator.setDuration(100);
- animator.start();
+ private int getExpectedViewHeight(View view) {
+ int expectedHeight;
+ int h = view.getLayoutParams().height;
+ if (h > 0) {
+ expectedHeight = h;
+ } else {
+ int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
+ view.measure(m, m);
+ expectedHeight = view.getMeasuredHeight();
}
+ return expectedHeight;
}
@Override
@@ -876,6 +929,10 @@
return (RecentsView) getParent();
}
+ PagedOrientationHandler getPagedOrientationHandler() {
+ return getRecentsView().mOrientationState.getOrientationHandler();
+ }
+
public void notifyTaskLaunchFailed(String tag) {
String msg = "Failed to launch task";
if (mTask != null) {
@@ -892,9 +949,6 @@
*/
public void setFullscreenProgress(float progress) {
progress = Utilities.boundToRange(progress, 0, 1);
- if (progress == mFullscreenProgress) {
- return;
- }
mFullscreenProgress = progress;
boolean isFullscreen = mFullscreenProgress > 0;
mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
@@ -902,23 +956,7 @@
setClipToPadding(!isFullscreen);
TaskThumbnailView thumbnail = getThumbnail();
- boolean isMultiWindowMode = mActivity.getDeviceProfile().isMultiWindowMode;
- RectF insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode);
- float currentInsetsLeft = insets.left * mFullscreenProgress;
- float currentInsetsRight = insets.right * mFullscreenProgress;
- mCurrentFullscreenParams.setInsets(currentInsetsLeft,
- insets.top * mFullscreenProgress,
- currentInsetsRight,
- insets.bottom * mFullscreenProgress);
- float fullscreenCornerRadius = isMultiWindowMode ? 0 : mWindowCornerRadius;
- mCurrentFullscreenParams.setCornerRadius(Utilities.mapRange(mFullscreenProgress,
- mCornerRadius, fullscreenCornerRadius) / getRecentsView().getScaleX());
- // We scaled the thumbnail to fit the content (excluding insets) within task view width.
- // Now that we are drawing left/right insets again, we need to scale down to fit them.
- if (getWidth() > 0) {
- mCurrentFullscreenParams.setScale(getWidth()
- / (getWidth() + currentInsetsLeft + currentInsetsRight));
- }
+ updateCurrentFullscreenParams(thumbnail.getPreviewPositionHelper());
if (!getRecentsView().isTaskIconScaledDown(this)) {
// Some of the items in here are dependent on the current fullscreen params, but don't
@@ -931,6 +969,17 @@
invalidateOutline();
}
+ void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
+ if (getRecentsView() == null) {
+ return;
+ }
+ mCurrentFullscreenParams.setProgress(
+ mFullscreenProgress,
+ getRecentsView().getScaleX(),
+ getWidth(), mActivity.getDeviceProfile(),
+ previewPositionHelper);
+ }
+
public boolean isRunningTask() {
if (getRecentsView() == null) {
return false;
@@ -956,26 +1005,46 @@
/**
* We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
*/
- static class FullscreenDrawParams {
- RectF mCurrentDrawnInsets = new RectF();
- float mCurrentDrawnCornerRadius;
+ public static class FullscreenDrawParams {
+
+ private final float mCornerRadius;
+ private final float mWindowCornerRadius;
+
+ public RectF mCurrentDrawnInsets = new RectF();
+ public float mCurrentDrawnCornerRadius;
/** The current scale we apply to the thumbnail to adjust for new left/right insets. */
- float mScale = 1;
+ public float mScale = 1;
- public FullscreenDrawParams(float cornerRadius) {
- setCornerRadius(cornerRadius);
+ public FullscreenDrawParams(Context context) {
+ mCornerRadius = TaskCornerRadius.get(context);
+ mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
+
+ mCurrentDrawnCornerRadius = mCornerRadius;
}
- public void setInsets(float left, float top, float right, float bottom) {
- mCurrentDrawnInsets.set(left, top, right, bottom);
+ /**
+ * Sets the progress in range [0, 1]
+ */
+ public void setProgress(float fullscreenProgress, float parentScale, int previewWidth,
+ DeviceProfile dp, PreviewPositionHelper pph) {
+ RectF insets = pph.getInsetsToDrawInFullscreen();
+
+ float currentInsetsLeft = insets.left * fullscreenProgress;
+ float currentInsetsRight = insets.right * fullscreenProgress;
+ mCurrentDrawnInsets.set(currentInsetsLeft, insets.top * fullscreenProgress,
+ currentInsetsRight, insets.bottom * fullscreenProgress);
+ float fullscreenCornerRadius = dp.isMultiWindowMode ? 0 : mWindowCornerRadius;
+
+ mCurrentDrawnCornerRadius =
+ Utilities.mapRange(fullscreenProgress, mCornerRadius, fullscreenCornerRadius)
+ / parentScale;
+
+ // We scaled the thumbnail to fit the content (excluding insets) within task view width.
+ // Now that we are drawing left/right insets again, we need to scale down to fit them.
+ if (previewWidth > 0) {
+ mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
+ }
}
- public void setCornerRadius(float cornerRadius) {
- mCurrentDrawnCornerRadius = cornerRadius;
- }
-
- public void setScale(float scale) {
- mScale = scale;
- }
}
}
diff --git a/quickstep/res/drawable-v28/back_gesture_tutorial_action_button_background.xml b/quickstep/res/drawable-v28/gesture_tutorial_action_button_background.xml
similarity index 91%
rename from quickstep/res/drawable-v28/back_gesture_tutorial_action_button_background.xml
rename to quickstep/res/drawable-v28/gesture_tutorial_action_button_background.xml
index cd30ef7..57423c2 100644
--- a/quickstep/res/drawable-v28/back_gesture_tutorial_action_button_background.xml
+++ b/quickstep/res/drawable-v28/gesture_tutorial_action_button_background.xml
@@ -16,5 +16,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="?android:attr/dialogCornerRadius"/>
- <solid android:color="@color/back_gesture_tutorial_primary_color"/>
+ <solid android:color="@color/gesture_tutorial_primary_color"/>
</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/assistant_gesture.xml b/quickstep/res/drawable/assistant_gesture.xml
new file mode 100644
index 0000000..ba4331c
--- /dev/null
+++ b/quickstep/res/drawable/assistant_gesture.xml
@@ -0,0 +1,989 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="412dp"
+ android:height="890dp"
+ android:viewportWidth="412"
+ android:viewportHeight="890">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_4_G_N_2_N_3_N_4_T_1"
+ android:rotation="-10"
+ android:translateX="661.757"
+ android:translateY="1026.235">
+ <group
+ android:name="_R_G_L_4_G_N_2_N_3_N_4_T_0"
+ android:translateX="-148.438"
+ android:translateY="-239.65">
+ <group
+ android:name="_R_G_L_4_G_N_2_N_3_T_0"
+ android:translateX="-61.73500000000001"
+ android:translateY="38.257000000000005">
+ <group
+ android:name="_R_G_L_4_G_N_2_T_0"
+ android:pivotX="83.124"
+ android:pivotY="89.259"
+ android:rotation="-16"
+ android:translateX="-50.44799999999999"
+ android:translateY="-62.925">
+ <group
+ android:name="_R_G_L_4_G"
+ android:translateX="-18.21"
+ android:translateY="-17.394">
+ <path
+ android:name="_R_G_L_4_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#e8f0fe"
+ android:fillType="nonZero"
+ android:pathData=" M96.25 48.25 C96.25,74.76 74.76,96.25 48.25,96.25 C21.74,96.25 0.25,74.76 0.25,48.25 C0.25,21.74 21.74,0.25 48.25,0.25 C74.76,0.25 96.25,21.74 96.25,48.25c " />
+ </group>
+ </group>
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_3_G"
+ android:pivotX="48.25"
+ android:pivotY="48.25"
+ android:rotation="11"
+ android:translateX="227.046"
+ android:translateY="642.467">
+ <path
+ android:name="_R_G_L_3_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#e8f0fe"
+ android:fillType="nonZero"
+ android:pathData=" M96.25 48.25 C96.25,74.76 74.76,96.25 48.25,96.25 C21.74,96.25 0.25,74.76 0.25,48.25 C0.25,21.74 21.74,0.25 48.25,0.25 C74.76,0.25 96.25,21.74 96.25,48.25c " />
+ </group>
+ <group
+ android:name="_R_G_L_2_G_T_1"
+ android:rotation="-10"
+ android:translateX="661.757"
+ android:translateY="1026.235">
+ <group
+ android:name="_R_G_L_2_G"
+ android:translateX="-148.438"
+ android:translateY="-239.65">
+ <path
+ android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M14.25 224.54 C40.46,320.73 128.6,415.05 212.61,415.05 C296.63,415.05 188.65,199.66 188.65,99.96 C188.65,0.25 122.97,160.22 18.3,95.88 C1.02,85.21 0.25,173.17 14.25,224.54c " />
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_1_G_N_4_T_1"
+ android:rotation="-10"
+ android:translateX="661.757"
+ android:translateY="1026.235">
+ <group
+ android:name="_R_G_L_1_G_N_4_T_0"
+ android:translateX="-148.438"
+ android:translateY="-239.65">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="-61.73500000000001"
+ android:translateY="38.257000000000005">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M60.8 0.25 C60.8,0.25 76.32,20.36 105.56,60.3 C134.8,100.25 207.8,82.25 207.8,82.25 C207.8,82.25 211.01,150.18 211.01,150.18 C211.01,150.18 81.43,193.5 81.43,193.5 C81.43,193.5 31.8,174.25 31.8,174.25 C31.8,174.25 27.8,109.25 22.8,89.25 C17.8,69.25 6.15,49.9 2.17,41.17 C0.25,36.95 13.88,27.25 28.48,18.35 C44.09,8.84 60.8,0.25 60.8,0.25c " />
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_0_G_N_3_N_4_T_1"
+ android:rotation="-10"
+ android:translateX="661.757"
+ android:translateY="1026.235">
+ <group
+ android:name="_R_G_L_0_G_N_3_N_4_T_0"
+ android:translateX="-148.438"
+ android:translateY="-239.65">
+ <group
+ android:name="_R_G_L_0_G_N_3_T_0"
+ android:translateX="-61.73500000000001"
+ android:translateY="38.257000000000005">
+ <group
+ android:name="_R_G_L_0_G"
+ android:pivotX="83.124"
+ android:pivotY="89.259"
+ android:rotation="-16"
+ android:translateX="-50.44799999999999"
+ android:translateY="-62.925">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M19.19 14.59 C40.48,0.25 55.28,10.31 73.92,28.68 C92.56,47.05 106.44,53.33 112.22,64.44 C118,75.56 76.45,102.74 64.42,104.51 C52.38,106.27 30.98,101.06 19.76,76.77 C8.07,51.48 0.54,27.15 19.19,14.59c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:pathData=" M57.29 26.52 C59.8,29.86 61.05,33.88 55.15,38.41 C55.15,38.41 49.91,42.03 36.73,51.93 C34.96,53.26 32.72,55.37 30.64,55.46 C28.92,55.55 26.91,54.65 25.14,51.9 C19.1,42.49 15,36.09 17.17,30.02 "
+ android:strokeWidth="6"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9" />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:pathData=" M64.41 90.04 C64.41,90.04 69.67,62.88 99.15,59.26 "
+ android:strokeWidth="6"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9" />
+ </group>
+ </group>
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_4_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="233"
+ android:propertyName="fillAlpha"
+ android:startOffset="333"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_4_G_N_2_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-16"
+ android:valueTo="-16"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="783"
+ android:propertyName="rotation"
+ android:startOffset="450"
+ android:valueFrom="-16"
+ android:valueTo="11"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="rotation"
+ android:startOffset="1233"
+ android:valueFrom="11"
+ android:valueTo="11"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="rotation"
+ android:startOffset="1683"
+ android:valueFrom="11"
+ android:valueTo="-16"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_4_G_N_2_N_3_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="661.757"
+ android:valueTo="661.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="661.757"
+ android:valueTo="493.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="translateX"
+ android:startOffset="1233"
+ android:valueFrom="493.757"
+ android:valueTo="497.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="translateX"
+ android:startOffset="1683"
+ android:valueFrom="497.757"
+ android:valueTo="661.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.303,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_4_G_N_2_N_3_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="1026.235"
+ android:valueTo="1026.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="1026.235"
+ android:valueTo="933.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="translateY"
+ android:startOffset="1233"
+ android:valueFrom="933.235"
+ android:valueTo="939.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="translateY"
+ android:startOffset="1683"
+ android:valueFrom="939.235"
+ android:valueTo="1026.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.303,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_4_G_N_2_N_3_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-10"
+ android:valueTo="-10"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-10"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="rotation"
+ android:startOffset="1233"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="rotation"
+ android:startOffset="1683"
+ android:valueFrom="0"
+ android:valueTo="-10"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_4_G_N_2_N_3_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="0"
+ android:propertyName="scaleY"
+ android:startOffset="1233"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="383"
+ android:propertyName="fillAlpha"
+ android:startOffset="1233"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="0"
+ android:propertyName="scaleX"
+ android:startOffset="1233"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="661.757"
+ android:valueTo="661.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="661.757"
+ android:valueTo="493.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="translateX"
+ android:startOffset="1233"
+ android:valueFrom="493.757"
+ android:valueTo="497.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="translateX"
+ android:startOffset="1683"
+ android:valueFrom="497.757"
+ android:valueTo="661.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.303,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="1026.235"
+ android:valueTo="1026.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="1026.235"
+ android:valueTo="933.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="translateY"
+ android:startOffset="1233"
+ android:valueFrom="933.235"
+ android:valueTo="939.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="translateY"
+ android:startOffset="1683"
+ android:valueFrom="939.235"
+ android:valueTo="1026.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.303,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-10"
+ android:valueTo="-10"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-10"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="rotation"
+ android:startOffset="1233"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="rotation"
+ android:startOffset="1683"
+ android:valueFrom="0"
+ android:valueTo="-10"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="661.757"
+ android:valueTo="661.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="661.757"
+ android:valueTo="493.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="translateX"
+ android:startOffset="1233"
+ android:valueFrom="493.757"
+ android:valueTo="497.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="translateX"
+ android:startOffset="1683"
+ android:valueFrom="497.757"
+ android:valueTo="661.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.303,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="1026.235"
+ android:valueTo="1026.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="1026.235"
+ android:valueTo="933.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="translateY"
+ android:startOffset="1233"
+ android:valueFrom="933.235"
+ android:valueTo="939.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="translateY"
+ android:startOffset="1683"
+ android:valueFrom="939.235"
+ android:valueTo="1026.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.303,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-10"
+ android:valueTo="-10"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-10"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="rotation"
+ android:startOffset="1233"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="rotation"
+ android:startOffset="1683"
+ android:valueFrom="0"
+ android:valueTo="-10"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M19.19 14.59 C40.48,0.25 55.28,10.31 73.92,28.68 C92.56,47.05 106.44,53.33 112.22,64.44 C118,75.56 76.45,102.74 64.42,104.51 C52.38,106.27 30.98,101.06 19.76,76.77 C8.07,51.48 0.54,27.15 19.19,14.59c "
+ android:valueTo="M19.19 14.59 C40.48,0.25 55.28,10.31 73.92,28.68 C92.56,47.05 106.44,53.33 112.22,64.44 C118,75.56 76.45,102.74 64.42,104.51 C52.38,106.27 30.98,101.06 19.76,76.77 C8.07,51.48 0.54,27.15 19.19,14.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="783"
+ android:propertyName="pathData"
+ android:startOffset="450"
+ android:valueFrom="M19.19 14.59 C40.48,0.25 55.28,10.31 73.92,28.68 C92.56,47.05 106.44,53.33 112.22,64.44 C118,75.56 76.45,102.74 64.42,104.51 C52.38,106.27 30.98,101.06 19.76,76.77 C8.07,51.48 0.54,27.15 19.19,14.59c "
+ android:valueTo="M19.19 14.59 C40.48,0.25 55.28,10.31 73.92,28.68 C92.56,47.05 106.44,53.33 112.22,64.44 C118,75.56 103.84,114.98 73.16,119.19 C61.1,120.84 30.98,101.06 19.76,76.77 C8.07,51.48 0.54,27.15 19.19,14.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="pathData"
+ android:startOffset="1233"
+ android:valueFrom="M19.19 14.59 C40.48,0.25 55.28,10.31 73.92,28.68 C92.56,47.05 106.44,53.33 112.22,64.44 C118,75.56 103.84,114.98 73.16,119.19 C61.1,120.84 30.98,101.06 19.76,76.77 C8.07,51.48 0.54,27.15 19.19,14.59c "
+ android:valueTo="M19.19 14.59 C40.48,0.25 55.28,10.31 73.92,28.68 C92.56,47.05 106.44,53.33 112.22,64.44 C118,75.56 103.84,114.98 73.16,119.19 C61.1,120.84 30.98,101.06 19.76,76.77 C8.07,51.48 0.54,27.15 19.19,14.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="pathData"
+ android:startOffset="1683"
+ android:valueFrom="M19.19 14.59 C40.48,0.25 55.28,10.31 73.92,28.68 C92.56,47.05 106.44,53.33 112.22,64.44 C118,75.56 103.84,114.98 73.16,119.19 C61.1,120.84 30.98,101.06 19.76,76.77 C8.07,51.48 0.54,27.15 19.19,14.59c "
+ android:valueTo="M19.19 14.59 C40.48,0.25 55.28,10.31 73.92,28.68 C92.56,47.05 106.44,53.33 112.22,64.44 C118,75.56 76.45,102.74 64.42,104.51 C52.38,106.27 30.98,101.06 19.76,76.77 C8.07,51.48 0.54,27.15 19.19,14.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-16"
+ android:valueTo="-16"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="783"
+ android:propertyName="rotation"
+ android:startOffset="450"
+ android:valueFrom="-16"
+ android:valueTo="11"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="rotation"
+ android:startOffset="1233"
+ android:valueFrom="11"
+ android:valueTo="11"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="rotation"
+ android:startOffset="1683"
+ android:valueFrom="11"
+ android:valueTo="-16"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_N_3_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="661.757"
+ android:valueTo="661.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="661.757"
+ android:valueTo="493.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="translateX"
+ android:startOffset="1233"
+ android:valueFrom="493.757"
+ android:valueTo="497.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="translateX"
+ android:startOffset="1683"
+ android:valueFrom="497.757"
+ android:valueTo="661.757"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.303,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_N_3_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="1026.235"
+ android:valueTo="1026.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="1026.235"
+ android:valueTo="933.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="translateY"
+ android:startOffset="1233"
+ android:valueFrom="933.235"
+ android:valueTo="939.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="translateY"
+ android:startOffset="1683"
+ android:valueFrom="939.235"
+ android:valueTo="1026.235"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.303,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_N_3_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-10"
+ android:valueTo="-10"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="900"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-10"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="rotation"
+ android:startOffset="1233"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1400"
+ android:propertyName="rotation"
+ android:startOffset="1683"
+ android:valueFrom="0"
+ android:valueTo="-10"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.205,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="3167"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/back_gesture.xml b/quickstep/res/drawable/back_gesture.xml
index a5c57b4..18ad2cb 100644
--- a/quickstep/res/drawable/back_gesture.xml
+++ b/quickstep/res/drawable/back_gesture.xml
@@ -1,100 +1,121 @@
-<!--
- 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.
--->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
- android:width="206dp"
- android:height="435dp"
- android:viewportWidth="206"
- android:viewportHeight="435">
- <group android:name="edgeGroup"
- android:translateX="197"
- android:translateY="0">
- <path
- android:name="edge"
- android:fillAlpha="0"
- android:fillType="nonZero"
- android:fillColor="#1a73eb"
- android:pathData=" M0,0 h9 v435 h-9 z " />
- </group>
- <group
- android:name="trailGroup"
- android:translateX="226"
- android:translateY="200">
- <path
- android:name="trail"
- android:fillAlpha="1"
- android:fillType="nonZero"
- android:pathData=" M0,0 h55 v36 h-55 z ">
- <aapt:attr name="android:fillColor">
- <gradient
- android:startX="0"
- android:endX="55"
- android:type="linear">
- <item
- android:color="#991a73eb"
- android:offset="0" />
- <item
- android:color="#401a73eb"
- android:offset="0.5" />
- <item
- android:color="#001a73eb"
- android:offset="1" />
- </gradient>
- </aapt:attr>
- </path>
- </group>
+ android:width="412dp"
+ android:height="890dp"
+ android:viewportWidth="412"
+ android:viewportHeight="890">
<group android:name="_R_G">
<group
- android:name="_R_G_L_0_G_T_1"
- android:rotation="11"
- android:scaleX="0.9"
- android:scaleY="0.9"
- android:translateX="309"
- android:translateY="422.5">
+ android:name="_R_G_L_3_G_T_1"
+ android:rotation="29"
+ android:translateX="400.931"
+ android:translateY="449.112">
<group
- android:name="_R_G_L_0_G"
- android:translateX="-145"
- android:translateY="-208">
+ android:name="_R_G_L_3_G"
+ android:translateX="-51.449"
+ android:translateY="-51.449">
<path
- android:name="_R_G_L_0_G_D_0_P_0"
+ android:name="_R_G_L_3_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#e8f0fe"
+ android:fillType="nonZero"
+ android:pathData=" M98.99 58.07 C95.33,84.33 71.08,102.65 44.83,98.99 C18.57,95.33 0.25,71.08 3.91,44.83 C7.57,18.57 31.82,0.25 58.07,3.91 C84.33,7.57 102.65,31.82 98.99,58.07c " />
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_2_G_T_1"
+ android:rotation="22"
+ android:translateX="443.275"
+ android:translateY="741.789">
+ <group
+ android:name="_R_G_L_2_G"
+ android:translateX="-199.299"
+ android:translateY="-310.469">
+ <path
+ android:name="_R_G_L_2_G_D_0_P_0"
android:fillAlpha="1"
android:fillColor="#d2e3fc"
android:fillType="nonZero"
- android:pathData=" M12.5 -47 C-7.93,-41.24 -3,-20.5 -1.5,-7 C0,6.5 2.5,22 9,39.5 C13.52,51.67 17.06,63.52 19,113 C21,164 53.5,243.5 53.5,243.5 C53.5,243.5 59,275.5 123.5,326 C188,376.5 283.5,236 290.5,199 C297.5,162 194.5,80 149,73 C103.5,66 90.5,57.5 77,50 C63.5,42.5 57,27 54.5,13.5 C52,0 43.5,-15 40,-25 C36.5,-35 32,-52.5 12.5,-47c " />
- <path
- android:name="_R_G_L_0_G_D_1_P_0"
- android:pathData=" M4.45 -34.66 C4.45,-34.66 10.5,-12.66 10.5,-12.66 C11.24,-9.98 13.98,-8.38 16.67,-9.04 C16.67,-9.04 29.72,-12.27 29.72,-12.27 C32.39,-12.93 34.05,-15.59 33.47,-18.28 C33.47,-18.28 32.11,-24.57 32.11,-24.57 "
- android:strokeWidth="4"
- android:strokeAlpha="1"
- android:strokeColor="#a0c2f9" />
- <path
- android:name="_R_G_L_0_G_D_2_P_0"
- android:pathData=" M18.35 21.81 C21.41,17.24 36.97,10.77 44.63,13.55 "
- android:strokeWidth="4"
- android:strokeAlpha="1"
- android:strokeColor="#a0c2f9" />
+ android:pathData=" M98.53 346.74 C150.31,507.99 279.63,534.69 366.99,534.69 C454.35,534.69 342.13,310.99 342.13,207.18 C342.13,103.37 244.29,336.77 102.92,212.59 C84.64,201.99 83.91,293.37 98.53,346.74c " />
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_1_G_N_2_N_4_T_1"
+ android:rotation="22"
+ android:translateX="443.275"
+ android:translateY="741.789">
+ <group
+ android:name="_R_G_L_1_G_N_2_N_4_T_0"
+ android:translateX="-199.299"
+ android:translateY="-310.469">
+ <group
+ android:name="_R_G_L_1_G_N_2_T_0"
+ android:translateX="32.843"
+ android:translateY="70.37599999999998">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="63.901"
+ android:pivotY="99.512"
+ android:rotation="-7"
+ android:translateX="-28.42"
+ android:translateY="-66.62700000000001">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M24.09 7.19 C24.09,7.19 24.09,7.19 24.09,7.19 C37.34,0.25 53.74,5.21 61.53,18.2 C61.53,18.2 92.06,67.99 92.06,67.99 C99.55,85.1 94.55,109.83 77.93,118.03 C77.93,118.03 73.41,119.96 73.41,119.96 C54.93,128.77 33.79,118.95 27.61,98.93 C27.61,98.93 9.41,43.77 9.41,43.77 C4.59,29.86 10.87,13.76 24.09,7.19c " />
+ <path
+ android:name="_R_G_L_1_G_D_1_P_0"
+ android:pathData=" M62.63 26.42 C64.51,30.16 64.56,33.23 61.29,34.92 C61.29,34.92 57.3,38.02 54.34,39.55 C52.36,40.56 31.95,52.96 29.89,52.69 C28.18,52.46 25.13,43.84 23.56,39.57 C19.67,28.95 23.51,23 30.85,17.22 "
+ android:strokeWidth="6"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9" />
+ </group>
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_0_G_N_4_T_1"
+ android:rotation="22"
+ android:translateX="443.275"
+ android:translateY="741.789">
+ <group
+ android:name="_R_G_L_0_G_N_4_T_0"
+ android:translateX="-199.299"
+ android:translateY="-310.469">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="32.843"
+ android:translateY="70.37599999999998">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M46.18 2.25 C46.18,2.25 63.07,0.46 63.07,0.46 C71.25,13.11 74.51,28.4 84.08,41.9 C107.62,73.73 129.25,98.04 175.54,101.59 C278.31,96.7 179.52,210.03 162.67,266.4 C162.67,266.4 58.91,211.6 58.91,211.6 C56.64,197.88 36.96,112.53 21.91,82.02 C12.91,63.77 2.48,47.19 -0.01,35.78 C-0.24,33.88 0.25,27.89 12.82,15.25 C21.14,6.07 34.31,0.25 46.18,2.25c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:pathData=" M37.4 46.68 C37.4,46.68 53.75,25.93 70.25,28.88 "
+ android:strokeWidth="6"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9" />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:pathData=" M157.71 101.59 C157.71,101.59 165.04,114.63 190.3,113 "
+ android:strokeWidth="6"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9" />
+ </group>
</group>
</group>
</group>
<group android:name="time_group" />
</vector>
</aapt:attr>
- <target android:name="edge">
+ <target android:name="_R_G_L_3_G_D_0_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator
@@ -102,44 +123,27 @@
android:propertyName="fillAlpha"
android:startOffset="0"
android:valueFrom="0"
- android:valueTo="0.2"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="917"
- android:propertyName="fillAlpha"
- android:startOffset="333"
- android:valueFrom="0.2"
- android:valueTo="0.2"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="583"
- android:propertyName="fillAlpha"
- android:startOffset="1250"
- android:valueFrom="0.2"
android:valueTo="0"
android:valueType="floatType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
</aapt:attr>
</objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="trail">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
<objectAnimator
- android:duration="2000"
+ android:duration="167"
android:propertyName="fillAlpha"
- android:startOffset="0"
+ android:startOffset="333"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="fillAlpha"
+ android:startOffset="500"
android:valueFrom="1"
android:valueTo="1"
android:valueType="floatType">
@@ -148,9 +152,9 @@
</aapt:attr>
</objectAnimator>
<objectAnimator
- android:duration="850"
+ android:duration="367"
android:propertyName="fillAlpha"
- android:startOffset="2000"
+ android:startOffset="1000"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType">
@@ -161,51 +165,468 @@
</set>
</aapt:attr>
</target>
- <target android:name="trailGroup">
+ <target android:name="_R_G_L_3_G_T_1">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator
- android:duration="83"
+ android:duration="333"
android:propertyName="translateX"
- android:startOffset="1250"
- android:valueFrom="226"
- android:valueTo="226"
+ android:startOffset="0"
+ android:valueFrom="400.931"
+ android:valueTo="400.931"
android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
<objectAnimator
+ android:duration="667"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="400.931"
+ android:valueTo="232.931"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="443.275"
+ android:valueTo="443.275"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="443.275"
+ android:valueTo="403.275"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="translateX"
+ android:startOffset="1000"
+ android:valueFrom="403.275"
+ android:valueTo="403.275"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="translateX"
+ android:startOffset="1600"
+ android:valueFrom="403.275"
+ android:valueTo="443.275"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.349,0 0.21,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="translateY"
+ android:startOffset="1000"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="translateY"
+ android:startOffset="1600"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.349,0 0.21,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="22"
+ android:valueTo="22"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="22"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="rotation"
+ android:startOffset="1000"
+ android:valueFrom="0"
+ android:valueTo="2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.324,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="rotation"
+ android:startOffset="1600"
+ android:valueFrom="2"
+ android:valueTo="22"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.349,0 0.21,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
android:duration="1000"
- android:propertyName="translateX"
- android:startOffset="1333"
- android:valueFrom="226"
- android:valueTo="151"
- android:valueType="floatType">
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M24.09 7.19 C24.09,7.19 24.09,7.19 24.09,7.19 C37.34,0.25 53.74,5.21 61.53,18.2 C61.53,18.2 92.06,67.99 92.06,67.99 C99.55,85.1 94.55,109.83 77.93,118.03 C77.93,118.03 73.41,119.96 73.41,119.96 C54.93,128.77 33.79,118.95 27.61,98.93 C27.61,98.93 9.41,43.77 9.41,43.77 C4.59,29.86 10.87,13.76 24.09,7.19c "
+ android:valueTo="M24.09 7.19 C24.09,7.19 24.09,7.19 24.09,7.19 C37.34,0.25 53.74,5.21 61.53,18.2 C61.53,18.2 92.06,67.99 92.06,67.99 C99.55,85.1 94.55,109.83 77.93,118.03 C77.93,118.03 73.41,119.96 73.41,119.96 C54.93,128.77 33.79,118.95 27.61,98.93 C27.61,98.93 9.41,43.77 9.41,43.77 C4.59,29.86 10.87,13.76 24.09,7.19c "
+ android:valueType="pathType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
<objectAnimator
- android:duration="517"
- android:propertyName="translateX"
- android:startOffset="2333"
- android:valueFrom="151"
- android:valueTo="151"
- android:valueType="floatType">
+ android:duration="600"
+ android:propertyName="pathData"
+ android:startOffset="1000"
+ android:valueFrom="M24.09 7.19 C24.09,7.19 24.09,7.19 24.09,7.19 C37.34,0.25 53.74,5.21 61.53,18.2 C61.53,18.2 92.06,67.99 92.06,67.99 C99.55,85.1 94.55,109.83 77.93,118.03 C77.93,118.03 73.41,119.96 73.41,119.96 C54.93,128.77 33.79,118.95 27.61,98.93 C27.61,98.93 9.41,43.77 9.41,43.77 C4.59,29.86 10.87,13.76 24.09,7.19c "
+ android:valueTo="M24.09 7.19 C24.09,7.19 24.09,7.19 24.09,7.19 C37.34,0.25 53.74,5.21 61.53,18.2 C61.53,18.2 92.06,67.99 92.06,67.99 C99.55,85.1 94.55,109.83 77.93,118.03 C77.93,118.03 73.41,119.96 73.41,119.96 C54.93,128.77 33.79,118.95 27.61,98.93 C27.61,98.93 9.41,43.77 9.41,43.77 C4.59,29.86 10.87,13.76 24.09,7.19c "
+ android:valueType="pathType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
<objectAnimator
- android:duration="50"
- android:propertyName="translateX"
- android:startOffset="2850"
- android:valueFrom="226"
- android:valueTo="226"
+ android:duration="1067"
+ android:propertyName="pathData"
+ android:startOffset="1600"
+ android:valueFrom="M24.09 7.19 C24.09,7.19 24.09,7.19 24.09,7.19 C37.34,0.25 53.74,5.21 61.53,18.2 C61.53,18.2 92.06,67.99 92.06,67.99 C99.55,85.1 94.55,109.83 77.93,118.03 C77.93,118.03 73.41,119.96 73.41,119.96 C54.93,128.77 33.79,118.95 27.61,98.93 C27.61,98.93 9.41,43.77 9.41,43.77 C4.59,29.86 10.87,13.76 24.09,7.19c "
+ android:valueTo="M24.09 7.19 C24.09,7.19 24.09,7.19 24.09,7.19 C37.34,0.25 53.74,5.21 61.53,18.2 C61.53,18.2 92.06,67.99 92.06,67.99 C99.55,85.1 94.55,109.83 77.93,118.03 C77.93,118.03 73.41,119.96 73.41,119.96 C54.93,128.77 33.79,118.95 27.61,98.93 C27.61,98.93 9.41,43.77 9.41,43.77 C4.59,29.86 10.87,13.76 24.09,7.19c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M62.63 26.42 C64.51,30.16 64.56,33.23 61.29,34.92 C61.29,34.92 57.3,38.02 54.34,39.55 C52.36,40.56 31.95,52.96 29.89,52.69 C28.18,52.46 25.13,43.84 23.56,39.57 C19.67,28.95 23.51,23 30.85,17.22 "
+ android:valueTo="M62.63 26.42 C64.51,30.16 64.56,33.23 61.29,34.92 C61.29,34.92 57.3,38.02 54.34,39.55 C52.36,40.56 31.95,52.96 29.89,52.69 C28.18,52.46 25.13,43.84 23.56,39.57 C19.67,28.95 23.51,23 30.85,17.22 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M62.63 26.42 C64.51,30.16 64.56,33.23 61.29,34.92 C61.29,34.92 57.3,38.02 54.34,39.55 C52.36,40.56 31.95,52.96 29.89,52.69 C28.18,52.46 25.13,43.84 23.56,39.57 C19.67,28.95 23.51,23 30.85,17.22 "
+ android:valueTo="M58.31 29.65 C60.19,33.38 60.72,37.56 54.12,40.99 C54.12,40.99 48.32,43.62 33.61,51.06 C31.63,52.06 29.06,53.73 26.99,53.46 C25.28,53.24 23.46,52.01 22.2,48.99 C17.92,38.66 15,31.64 18.2,26.05 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="pathData"
+ android:startOffset="1000"
+ android:valueFrom="M58.31 29.65 C60.19,33.38 60.72,37.56 54.12,40.99 C54.12,40.99 48.32,43.62 33.61,51.06 C31.63,52.06 29.06,53.73 26.99,53.46 C25.28,53.24 23.46,52.01 22.2,48.99 C17.92,38.66 15,31.64 18.2,26.05 "
+ android:valueTo="M58.31 29.65 C60.19,33.38 60.72,37.56 54.12,40.99 C54.12,40.99 48.32,43.62 33.61,51.06 C31.63,52.06 29.06,53.73 26.99,53.46 C25.28,53.24 23.46,52.01 22.2,48.99 C17.92,38.66 15,31.64 18.2,26.05 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="pathData"
+ android:startOffset="1600"
+ android:valueFrom="M58.31 29.65 C60.19,33.38 60.72,37.56 54.12,40.99 C54.12,40.99 48.32,43.62 33.61,51.06 C31.63,52.06 29.06,53.73 26.99,53.46 C25.28,53.24 23.46,52.01 22.2,48.99 C17.92,38.66 15,31.64 18.2,26.05 "
+ android:valueTo="M62.63 26.42 C64.51,30.16 64.56,33.23 61.29,34.92 C61.29,34.92 57.3,38.02 54.34,39.55 C52.36,40.56 31.95,52.96 29.89,52.69 C28.18,52.46 25.13,43.84 23.56,39.57 C19.67,28.95 23.51,23 30.85,17.22 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-7"
+ android:valueTo="-7"
android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.315,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-7"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.315,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="rotation"
+ android:startOffset="1000"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="rotation"
+ android:startOffset="1600"
+ android:valueFrom="0"
+ android:valueTo="-7"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.349,0 0.21,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_2_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="443.275"
+ android:valueTo="443.275"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="443.275"
+ android:valueTo="403.275"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="translateX"
+ android:startOffset="1000"
+ android:valueFrom="403.275"
+ android:valueTo="403.275"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="translateX"
+ android:startOffset="1600"
+ android:valueFrom="403.275"
+ android:valueTo="443.275"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.349,0 0.21,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_2_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="translateY"
+ android:startOffset="1000"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="translateY"
+ android:startOffset="1600"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.349,0 0.21,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_2_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="22"
+ android:valueTo="22"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="22"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="rotation"
+ android:startOffset="1000"
+ android:valueFrom="0"
+ android:valueTo="2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.324,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="rotation"
+ android:startOffset="1600"
+ android:valueFrom="2"
+ android:valueTo="22"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.349,0 0.21,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
</set>
@@ -215,25 +636,47 @@
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator
- android:duration="1833"
- android:propertyName="fillAlpha"
- android:startOffset="1250"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M46.18 2.25 C46.18,2.25 63.07,0.46 63.07,0.46 C71.25,13.11 74.51,28.4 84.08,41.9 C107.62,73.73 129.25,98.04 175.54,101.59 C278.31,96.7 179.52,210.03 162.67,266.4 C162.67,266.4 58.91,211.6 58.91,211.6 C56.64,197.88 36.96,112.53 21.91,82.02 C12.91,63.77 2.48,47.19 -0.01,35.78 C-0.24,33.88 0.25,27.89 12.82,15.25 C21.14,6.07 34.31,0.25 46.18,2.25c "
+ android:valueTo="M46.18 2.25 C46.18,2.25 63.07,0.46 63.07,0.46 C71.25,13.11 74.51,28.4 84.08,41.9 C107.62,73.73 129.25,98.04 175.54,101.59 C278.31,96.7 179.52,210.03 162.67,266.4 C162.67,266.4 58.91,211.6 58.91,211.6 C56.64,197.88 36.96,112.53 21.91,82.02 C12.91,63.77 2.48,47.19 -0.01,35.78 C-0.24,33.88 0.25,27.89 12.82,15.25 C21.14,6.07 34.31,0.25 46.18,2.25c "
+ android:valueType="pathType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
<objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="3083"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
+ android:duration="667"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M46.18 2.25 C46.18,2.25 63.07,0.46 63.07,0.46 C71.25,13.11 74.51,28.4 84.08,41.9 C107.62,73.73 129.25,98.04 175.54,101.59 C278.31,96.7 179.52,210.03 162.67,266.4 C162.67,266.4 58.91,211.6 58.91,211.6 C56.64,197.88 36.96,112.53 21.91,82.02 C12.91,63.77 2.48,47.19 -0.01,35.78 C-0.24,33.88 0.25,27.89 12.82,15.25 C21.14,6.07 34.31,0.25 46.18,2.25c "
+ android:valueTo="M46.18 2.25 C46.18,2.25 63.07,0.46 63.07,0.46 C71.25,13.11 74.51,28.4 84.08,41.9 C107.62,73.73 129.25,98.04 175.54,101.59 C278.31,96.7 179.52,210.03 162.67,266.4 C162.67,266.4 58.91,211.6 58.91,211.6 C56.64,197.88 39,110.89 30.58,91.46 C22.47,72.8 10.93,51.25 2.97,40.51 C2.75,38.61 0.25,27.89 12.82,15.25 C21.14,6.07 34.31,0.25 46.18,2.25c "
+ android:valueType="pathType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="pathData"
+ android:startOffset="1000"
+ android:valueFrom="M46.18 2.25 C46.18,2.25 63.07,0.46 63.07,0.46 C71.25,13.11 74.51,28.4 84.08,41.9 C107.62,73.73 129.25,98.04 175.54,101.59 C278.31,96.7 179.52,210.03 162.67,266.4 C162.67,266.4 58.91,211.6 58.91,211.6 C56.64,197.88 39,110.89 30.58,91.46 C22.47,72.8 10.93,51.25 2.97,40.51 C2.75,38.61 0.25,27.89 12.82,15.25 C21.14,6.07 34.31,0.25 46.18,2.25c "
+ android:valueTo="M46.18 2.25 C46.18,2.25 63.07,0.46 63.07,0.46 C71.25,13.11 74.51,28.4 84.08,41.9 C107.62,73.73 129.25,98.04 175.54,101.59 C278.31,96.7 179.52,210.03 162.67,266.4 C162.67,266.4 58.91,211.6 58.91,211.6 C56.64,197.88 39,110.89 30.58,91.46 C22.47,72.8 10.93,51.25 2.97,40.51 C2.75,38.61 0.25,27.89 12.82,15.25 C21.14,6.07 34.31,0.25 46.18,2.25c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="pathData"
+ android:startOffset="1600"
+ android:valueFrom="M46.18 2.25 C46.18,2.25 63.07,0.46 63.07,0.46 C71.25,13.11 74.51,28.4 84.08,41.9 C107.62,73.73 129.25,98.04 175.54,101.59 C278.31,96.7 179.52,210.03 162.67,266.4 C162.67,266.4 58.91,211.6 58.91,211.6 C56.64,197.88 39,110.89 30.58,91.46 C22.47,72.8 10.93,51.25 2.97,40.51 C2.75,38.61 0.25,27.89 12.82,15.25 C21.14,6.07 34.31,0.25 46.18,2.25c "
+ android:valueTo="M46.18 2.25 C46.18,2.25 63.07,0.46 63.07,0.46 C71.25,13.11 74.51,28.4 84.08,41.9 C107.62,73.73 129.25,98.04 175.54,101.59 C278.31,96.7 179.52,210.03 162.67,266.4 C162.67,266.4 58.91,211.6 58.91,211.6 C56.64,197.88 36.96,112.53 21.91,82.02 C12.91,63.77 2.48,47.19 -0.01,35.78 C-0.24,33.88 0.25,27.89 12.82,15.25 C21.14,6.07 34.31,0.25 46.18,2.25c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.269,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
</set>
@@ -243,109 +686,197 @@
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator
- android:duration="1833"
- android:propertyName="strokeAlpha"
- android:startOffset="1250"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M37.4 46.68 C37.4,46.68 53.75,25.93 70.25,28.88 "
+ android:valueTo="M37.4 46.68 C37.4,46.68 53.75,25.93 70.25,28.88 "
+ android:valueType="pathType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
<objectAnimator
- android:duration="100"
- android:propertyName="strokeAlpha"
- android:startOffset="3083"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
+ android:duration="667"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M37.4 46.68 C37.4,46.68 53.75,25.93 70.25,28.88 "
+ android:valueTo="M29.25 53.2 C29.25,53.2 40.54,27.94 70.07,31.11 "
+ android:valueType="pathType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="pathData"
+ android:startOffset="1000"
+ android:valueFrom="M29.25 53.2 C29.25,53.2 40.54,27.94 70.07,31.11 "
+ android:valueTo="M29.25 53.2 C29.25,53.2 40.54,27.94 70.07,31.11 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="pathData"
+ android:startOffset="1600"
+ android:valueFrom="M29.25 53.2 C29.25,53.2 40.54,27.94 70.07,31.11 "
+ android:valueTo="M37.4 46.68 C37.4,46.68 53.75,25.93 70.25,28.88 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.269,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_0_G_D_2_P_0">
+ <target android:name="_R_G_L_0_G_N_4_T_1">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator
- android:duration="1833"
- android:propertyName="strokeAlpha"
- android:startOffset="1250"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="strokeAlpha"
- android:startOffset="3083"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_T_1">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="83"
+ android:duration="333"
android:propertyName="translateX"
- android:startOffset="1250"
- android:valueFrom="309"
- android:valueTo="309"
+ android:startOffset="0"
+ android:valueFrom="443.275"
+ android:valueTo="443.275"
android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
<objectAnimator
- android:duration="1417"
+ android:duration="667"
android:propertyName="translateX"
- android:startOffset="1333"
- android:valueFrom="309"
- android:valueTo="251"
+ android:startOffset="333"
+ android:valueFrom="443.275"
+ android:valueTo="403.275"
android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="translateX"
+ android:startOffset="1000"
+ android:valueFrom="403.275"
+ android:valueTo="403.275"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="translateX"
+ android:startOffset="1600"
+ android:valueFrom="403.275"
+ android:valueTo="443.275"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.349,0 0.21,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_0_G_T_1">
+ <target android:name="_R_G_L_0_G_N_4_T_1">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator
- android:duration="83"
- android:propertyName="rotation"
- android:startOffset="1250"
- android:valueFrom="11"
- android:valueTo="11"
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.277,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
<objectAnimator
- android:duration="1417"
+ android:duration="667"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="translateY"
+ android:startOffset="1000"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="translateY"
+ android:startOffset="1600"
+ android:valueFrom="741.789"
+ android:valueTo="741.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.349,0 0.21,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_N_4_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
android:propertyName="rotation"
- android:startOffset="1333"
- android:valueFrom="11"
+ android:startOffset="0"
+ android:valueFrom="22"
+ android:valueTo="22"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="22"
android:valueTo="0"
android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.277,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="rotation"
+ android:startOffset="1000"
+ android:valueFrom="0"
+ android:valueTo="2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.324,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1067"
+ android:propertyName="rotation"
+ android:startOffset="1600"
+ android:valueFrom="2"
+ android:valueTo="22"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.349,0 0.21,1 1.0,1.0" />
</aapt:attr>
</objectAnimator>
</set>
@@ -355,9 +886,9 @@
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator
- android:duration="2183"
+ android:duration="2667"
android:propertyName="translateX"
- android:startOffset="1250"
+ android:startOffset="0"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
diff --git a/quickstep/res/drawable/back_gesture_tutorial_action_button_background.xml b/quickstep/res/drawable/gesture_tutorial_action_button_background.xml
similarity index 91%
rename from quickstep/res/drawable/back_gesture_tutorial_action_button_background.xml
rename to quickstep/res/drawable/gesture_tutorial_action_button_background.xml
index d7b9102..3f3b288 100644
--- a/quickstep/res/drawable/back_gesture_tutorial_action_button_background.xml
+++ b/quickstep/res/drawable/gesture_tutorial_action_button_background.xml
@@ -16,5 +16,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/default_dialog_corner_radius"/>
- <solid android:color="@color/back_gesture_tutorial_primary_color"/>
+ <solid android:color="@color/gesture_tutorial_primary_color"/>
</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/back_gesture_tutorial_close_button.xml b/quickstep/res/drawable/gesture_tutorial_close_button.xml
similarity index 100%
rename from quickstep/res/drawable/back_gesture_tutorial_close_button.xml
rename to quickstep/res/drawable/gesture_tutorial_close_button.xml
diff --git a/quickstep/res/drawable/home_gesture.xml b/quickstep/res/drawable/home_gesture.xml
new file mode 100644
index 0000000..4de29d0
--- /dev/null
+++ b/quickstep/res/drawable/home_gesture.xml
@@ -0,0 +1,935 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="412dp"
+ android:height="890dp"
+ android:viewportWidth="412"
+ android:viewportHeight="890">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_3_G_T_1"
+ android:translateX="206"
+ android:translateY="880.283">
+ <group
+ android:name="_R_G_L_3_G"
+ android:translateX="-48.25"
+ android:translateY="-51.643">
+ <path
+ android:name="_R_G_L_3_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#e8f0fe"
+ android:fillType="nonZero"
+ android:pathData=" M96.25 48.25 C96.25,74.76 74.76,96.25 48.25,96.25 C21.74,96.25 0.25,74.76 0.25,48.25 C0.25,21.74 21.74,0.25 48.25,0.25 C74.76,0.25 96.25,21.74 96.25,48.25c " />
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_2_G_N_4_N_3_T_1"
+ android:rotation="-60"
+ android:translateX="513.995"
+ android:translateY="909.041">
+ <group
+ android:name="_R_G_L_2_G_N_4_N_3_T_0"
+ android:translateX="-125.282"
+ android:translateY="-222.031">
+ <group
+ android:name="_R_G_L_2_G_N_4_T_0"
+ android:translateX="-27.223000000000006"
+ android:translateY="-19.78200000000001">
+ <group
+ android:name="_R_G_L_2_G"
+ android:pivotX="49.356"
+ android:pivotY="100.997"
+ android:rotation="-4"
+ android:translateX="-12.881"
+ android:translateY="-68.965">
+ <path
+ android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M29.51 3.44 C29.51,3.44 29.51,3.44 29.51,3.44 C44.17,0.25 58.86,8.71 63.48,22.99 C63.48,22.99 95.13,76.48 95.13,76.48 C98.47,94.41 92.03,116.16 52.12,115.41 C52.12,115.41 31.32,103.09 31.32,103.09 C23.59,101.22 5.39,82.22 3.78,59.83 C3.78,59.83 3.05,36.42 3.05,36.42 C5.71,17.29 15.16,6.55 29.51,3.44c " />
+ <path
+ android:name="_R_G_L_2_G_D_1_P_0"
+ android:pathData=" M58.16 28.36 C59.49,32.32 59.42,36.53 52.4,38.99 C52.4,38.99 46.29,40.77 30.67,46.05 C28.57,46.76 25.79,48.06 23.78,47.5 C22.12,47.04 20.49,45.56 19.68,42.39 C16.89,31.56 15,24.2 18.96,19.11 "
+ android:strokeWidth="6"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9" />
+ </group>
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_1_G_N_3_T_1"
+ android:rotation="-60"
+ android:translateX="513.995"
+ android:translateY="909.041">
+ <group
+ android:name="_R_G_L_1_G_N_3_T_0"
+ android:translateX="-125.282"
+ android:translateY="-222.031">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="-27.223000000000006"
+ android:translateY="-19.78200000000001">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M62.05 -4.24 C62.05,-4.24 76.43,1.82 76.43,1.82 C91.76,16.39 83.61,36.19 86.21,52.29 C92.02,88.24 84.86,126.82 117.41,193.95 C87.71,242.97 78.03,245.45 48.32,294.5 C48.32,294.5 18.2,137.31 18.2,137.31 C19.12,123.7 17.54,107.98 13.81,87.39 C10.28,67.94 10.58,45.16 10,31.95 C10.04,30.08 8.74,12.88 15.64,8.72 C26.15,2.4 40.27,-9.2 62.05,-4.24c " />
+ <path
+ android:name="_R_G_L_1_G_D_1_P_0"
+ android:pathData=" M31.4 21.98 C31.4,21.98 41.87,1.7 68.18,3.97 "
+ android:strokeWidth="6"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9" />
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:rotation="-60"
+ android:translateX="513.995"
+ android:translateY="909.041">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="-125.282"
+ android:translateY="-222.031">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M5.87 164.71 C0.43,267.95 56.57,389.47 139.31,416.77 C222.04,444.07 215.7,295.51 248.09,197.33 C280.49,99.14 111.3,154.92 55.02,40.63 C41.48,24.52 8.77,109.58 5.87,164.71c " />
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_3_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="133"
+ android:propertyName="fillAlpha"
+ android:startOffset="333"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="450"
+ android:propertyName="fillAlpha"
+ android:startOffset="467"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="fillAlpha"
+ android:startOffset="917"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="880.283"
+ android:valueTo="880.283"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.247,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="883"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="880.283"
+ android:valueTo="486.283"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.247,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M29.51 3.44 C29.51,3.44 29.51,3.44 29.51,3.44 C44.17,0.25 58.86,8.71 63.48,22.99 C63.48,22.99 95.13,76.48 95.13,76.48 C98.47,94.41 92.03,116.16 52.12,115.41 C52.12,115.41 31.32,103.09 31.32,103.09 C23.59,101.22 5.39,82.22 3.78,59.83 C3.78,59.83 3.05,36.42 3.05,36.42 C5.71,17.29 15.16,6.55 29.51,3.44c "
+ android:valueTo="M29.51 3.44 C29.51,3.44 29.51,3.44 29.51,3.44 C44.17,0.25 58.86,8.71 63.48,22.99 C63.48,22.99 95.13,76.48 95.13,76.48 C98.47,94.41 92.03,116.16 52.12,115.41 C52.12,115.41 31.32,103.09 31.32,103.09 C23.59,101.22 5.39,82.22 3.78,59.83 C3.78,59.83 3.05,36.42 3.05,36.42 C5.71,17.29 15.16,6.55 29.51,3.44c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="583"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M29.51 3.44 C29.51,3.44 29.51,3.44 29.51,3.44 C44.17,0.25 58.86,8.71 63.48,22.99 C63.48,22.99 95.13,76.48 95.13,76.48 C98.47,94.41 92.03,116.16 52.12,115.41 C52.12,115.41 31.32,103.09 31.32,103.09 C23.59,101.22 5.39,82.22 3.78,59.83 C3.78,59.83 3.05,36.42 3.05,36.42 C5.71,17.29 15.16,6.55 29.51,3.44c "
+ android:valueTo="M29.51 3.44 C29.51,3.44 29.51,3.44 29.51,3.44 C44.17,0.25 58.86,8.71 63.48,22.99 C63.48,22.99 81.12,76.53 81.12,76.53 C84.46,94.46 74.07,117.8 56.14,121.15 C56.14,121.15 51.31,122.05 51.31,122.05 C31.42,125.74 12.82,111.19 11.57,91 C11.57,91 6.61,34.54 6.61,34.54 C5.34,19.87 15.16,6.55 29.51,3.44c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="pathData"
+ android:startOffset="917"
+ android:valueFrom="M29.51 3.44 C29.51,3.44 29.51,3.44 29.51,3.44 C44.17,0.25 58.86,8.71 63.48,22.99 C63.48,22.99 81.12,76.53 81.12,76.53 C84.46,94.46 74.07,117.8 56.14,121.15 C56.14,121.15 51.31,122.05 51.31,122.05 C31.42,125.74 12.82,111.19 11.57,91 C11.57,91 6.61,34.54 6.61,34.54 C5.34,19.87 15.16,6.55 29.51,3.44c "
+ android:valueTo="M29.51 3.44 C29.51,3.44 29.51,3.44 29.51,3.44 C44.17,0.25 58.86,8.71 63.48,22.99 C63.48,22.99 81.12,76.53 81.12,76.53 C84.46,94.46 74.07,117.8 56.14,121.15 C56.14,121.15 51.31,122.05 51.31,122.05 C31.42,125.74 12.82,111.19 11.57,91 C11.57,91 6.61,34.54 6.61,34.54 C5.34,19.87 15.16,6.55 29.51,3.44c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="pathData"
+ android:startOffset="1483"
+ android:valueFrom="M29.51 3.44 C29.51,3.44 29.51,3.44 29.51,3.44 C44.17,0.25 58.86,8.71 63.48,22.99 C63.48,22.99 81.12,76.53 81.12,76.53 C84.46,94.46 74.07,117.8 56.14,121.15 C56.14,121.15 51.31,122.05 51.31,122.05 C31.42,125.74 12.82,111.19 11.57,91 C11.57,91 6.61,34.54 6.61,34.54 C5.34,19.87 15.16,6.55 29.51,3.44c "
+ android:valueTo="M29.51 3.44 C29.51,3.44 29.51,3.44 29.51,3.44 C44.17,0.25 58.86,8.71 63.48,22.99 C63.48,22.99 95.13,76.48 95.13,76.48 C98.47,94.41 92.03,116.16 52.12,115.41 C52.12,115.41 31.32,103.09 31.32,103.09 C23.59,101.22 5.39,82.22 3.78,59.83 C3.78,59.83 3.05,36.42 3.05,36.42 C5.71,17.29 15.16,6.55 29.51,3.44c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-4"
+ android:valueTo="-4"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-4"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="2300"
+ android:propertyName="rotation"
+ android:startOffset="533"
+ android:valueFrom="0"
+ android:valueTo="-4"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_N_4_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="513.995"
+ android:valueTo="513.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.711,0 0.772,0.166 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="513.995"
+ android:valueTo="469.109"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.711,0 0.772,0.166 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="433"
+ android:propertyName="translateX"
+ android:startOffset="483"
+ android:valueFrom="469.109"
+ android:valueTo="343.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.094,0.355 0.269,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="translateX"
+ android:startOffset="917"
+ android:valueFrom="343.995"
+ android:valueTo="367.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.475,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="translateX"
+ android:startOffset="1483"
+ android:valueFrom="367.995"
+ android:valueTo="513.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.513,0 0.219,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_N_4_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="909.041"
+ android:valueTo="909.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="583"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="909.041"
+ android:valueTo="859.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="translateY"
+ android:startOffset="917"
+ android:valueFrom="859.041"
+ android:valueTo="883.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.475,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="translateY"
+ android:startOffset="1483"
+ android:valueFrom="883.041"
+ android:valueTo="909.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.513,0 0.219,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_N_4_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-60"
+ android:valueTo="-60"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="583"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-60"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="rotation"
+ android:startOffset="917"
+ android:valueFrom="0"
+ android:valueTo="-2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.545,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="rotation"
+ android:startOffset="1483"
+ android:valueFrom="-2"
+ android:valueTo="-60"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.513,0 0.219,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M62.05 -4.24 C62.05,-4.24 76.43,1.82 76.43,1.82 C91.76,16.39 83.61,36.19 86.21,52.29 C92.02,88.24 84.86,126.82 117.41,193.95 C87.71,242.97 78.03,245.45 48.32,294.5 C48.32,294.5 18.2,137.31 18.2,137.31 C19.12,123.7 17.54,107.98 13.81,87.39 C10.28,67.94 10.58,45.16 10,31.95 C10.04,30.08 8.74,12.88 15.64,8.72 C26.15,2.4 40.27,-9.2 62.05,-4.24c "
+ android:valueTo="M62.05 -4.24 C62.05,-4.24 76.43,1.82 76.43,1.82 C91.76,16.39 83.61,36.19 86.21,52.29 C92.02,88.24 84.86,126.82 117.41,193.95 C87.71,242.97 78.03,245.45 48.32,294.5 C48.32,294.5 18.2,137.31 18.2,137.31 C19.12,123.7 17.54,107.98 13.81,87.39 C10.28,67.94 10.58,45.16 10,31.95 C10.04,30.08 8.74,12.88 15.64,8.72 C26.15,2.4 40.27,-9.2 62.05,-4.24c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="583"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M62.05 -4.24 C62.05,-4.24 76.43,1.82 76.43,1.82 C91.76,16.39 83.61,36.19 86.21,52.29 C92.02,88.24 84.86,126.82 117.41,193.95 C87.71,242.97 78.03,245.45 48.32,294.5 C48.32,294.5 18.2,137.31 18.2,137.31 C19.12,123.7 17.54,107.98 13.81,87.39 C10.28,67.94 10.58,45.16 10,31.95 C10.04,30.08 8.74,12.88 15.64,8.72 C26.15,2.4 40.27,-9.2 62.05,-4.24c "
+ android:valueTo="M51.44 5.02 C51.44,5.02 68.24,7.56 68.24,7.56 C73.26,21.67 72.7,36.77 79.13,51.76 C94.55,87.66 106.15,89.19 144.51,111.49 C114.82,160.51 78.03,245.45 48.32,294.5 C48.32,294.5 18.2,137.31 18.2,137.31 C19.12,123.7 19.02,105.73 15.29,85.14 C11.76,65.68 5.52,42.3 0.25,30.23 C0.29,28.36 0.58,17.81 15.64,8.72 C26.15,2.39 40.15,0.25 51.44,5.02c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="pathData"
+ android:startOffset="917"
+ android:valueFrom="M51.44 5.02 C51.44,5.02 68.24,7.56 68.24,7.56 C73.26,21.67 72.7,36.77 79.13,51.76 C94.55,87.66 106.15,89.19 144.51,111.49 C114.82,160.51 78.03,245.45 48.32,294.5 C48.32,294.5 18.2,137.31 18.2,137.31 C19.12,123.7 19.02,105.73 15.29,85.14 C11.76,65.68 5.52,42.3 0.25,30.23 C0.29,28.36 0.58,17.81 15.64,8.72 C26.15,2.39 40.15,0.25 51.44,5.02c "
+ android:valueTo="M51.44 5.02 C51.44,5.02 68.24,7.56 68.24,7.56 C73.26,21.67 72.7,36.77 79.13,51.76 C94.55,87.66 106.15,89.19 144.51,111.49 C114.82,160.51 78.03,245.45 48.32,294.5 C48.32,294.5 18.2,137.31 18.2,137.31 C19.12,123.7 19.02,105.73 15.29,85.14 C11.76,65.68 5.52,42.3 0.25,30.23 C0.29,28.36 0.58,17.81 15.64,8.72 C26.15,2.39 40.15,0.25 51.44,5.02c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="pathData"
+ android:startOffset="1483"
+ android:valueFrom="M51.44 5.02 C51.44,5.02 68.24,7.56 68.24,7.56 C73.26,21.67 72.7,36.77 79.13,51.76 C94.55,87.66 106.15,89.19 144.51,111.49 C114.82,160.51 78.03,245.45 48.32,294.5 C48.32,294.5 18.2,137.31 18.2,137.31 C19.12,123.7 19.02,105.73 15.29,85.14 C11.76,65.68 5.52,42.3 0.25,30.23 C0.29,28.36 0.58,17.81 15.64,8.72 C26.15,2.39 40.15,0.25 51.44,5.02c "
+ android:valueTo="M62.05 -4.24 C62.05,-4.24 76.43,1.82 76.43,1.82 C91.76,16.39 83.61,36.19 86.21,52.29 C92.02,88.24 84.86,126.82 117.41,193.95 C87.71,242.97 78.03,245.45 48.32,294.5 C48.32,294.5 18.2,137.31 18.2,137.31 C19.12,123.7 17.54,107.98 13.81,87.39 C10.28,67.94 10.58,45.16 10,31.95 C10.04,30.08 8.74,12.88 15.64,8.72 C26.15,2.4 40.27,-9.2 62.05,-4.24c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M31.4 21.98 C31.4,21.98 41.87,1.7 68.18,3.97 "
+ android:valueTo="M31.4 21.98 C31.4,21.98 41.87,1.7 68.18,3.97 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="583"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M31.4 21.98 C31.4,21.98 41.87,1.7 68.18,3.97 "
+ android:valueTo="M17.9 26.98 C17.9,26.98 29.87,13.7 56.18,15.97 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="pathData"
+ android:startOffset="917"
+ android:valueFrom="M17.9 26.98 C17.9,26.98 29.87,13.7 56.18,15.97 "
+ android:valueTo="M17.9 26.98 C17.9,26.98 29.87,13.7 56.18,15.97 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="pathData"
+ android:startOffset="1483"
+ android:valueFrom="M17.9 26.98 C17.9,26.98 29.87,13.7 56.18,15.97 "
+ android:valueTo="M31.4 21.98 C31.4,21.98 41.87,1.7 68.18,3.97 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="513.995"
+ android:valueTo="513.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.711,0 0.772,0.166 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="513.995"
+ android:valueTo="469.109"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.711,0 0.772,0.166 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="433"
+ android:propertyName="translateX"
+ android:startOffset="483"
+ android:valueFrom="469.109"
+ android:valueTo="343.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.094,0.355 0.269,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="translateX"
+ android:startOffset="917"
+ android:valueFrom="343.995"
+ android:valueTo="367.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.475,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="translateX"
+ android:startOffset="1483"
+ android:valueFrom="367.995"
+ android:valueTo="513.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.513,0 0.219,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="909.041"
+ android:valueTo="909.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="583"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="909.041"
+ android:valueTo="859.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="translateY"
+ android:startOffset="917"
+ android:valueFrom="859.041"
+ android:valueTo="883.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.475,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="translateY"
+ android:startOffset="1483"
+ android:valueFrom="883.041"
+ android:valueTo="909.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.513,0 0.219,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-60"
+ android:valueTo="-60"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="583"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-60"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="rotation"
+ android:startOffset="917"
+ android:valueFrom="0"
+ android:valueTo="-2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.545,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="rotation"
+ android:startOffset="1483"
+ android:valueFrom="-2"
+ android:valueTo="-60"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.513,0 0.219,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M5.87 164.71 C0.43,267.95 56.57,389.47 139.31,416.77 C222.04,444.07 215.7,295.51 248.09,197.33 C280.49,99.14 111.3,154.92 55.02,40.63 C41.48,24.52 8.77,109.58 5.87,164.71c "
+ android:valueTo="M5.87 164.71 C0.43,267.95 56.57,389.47 139.31,416.77 C222.04,444.07 215.7,295.51 248.09,197.33 C280.49,99.14 111.3,154.92 55.02,40.63 C41.48,24.52 8.77,109.58 5.87,164.71c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M5.87 164.71 C0.43,267.95 56.57,389.47 139.31,416.77 C222.04,444.07 215.7,295.51 248.09,197.33 C280.49,99.14 111.3,154.92 55.02,40.63 C41.48,24.52 8.77,109.58 5.87,164.71c "
+ android:valueTo="M-9.23 115.79 C-30.45,216.96 -36.73,359.16 139.26,416.71 C222.07,443.78 220.51,278.25 244.3,175.03 C268.14,75.53 116.98,150.23 54.13,40.23 C40.58,24.12 5.18,47.09 -9.23,115.79c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="433"
+ android:propertyName="pathData"
+ android:startOffset="483"
+ android:valueFrom="M-9.23 115.79 C-30.45,216.96 -36.73,359.16 139.26,416.71 C222.07,443.78 220.51,278.25 244.3,175.03 C268.14,75.53 116.98,150.23 54.13,40.23 C40.58,24.12 5.18,47.09 -9.23,115.79c "
+ android:valueTo="M-8.56 118.7 C-9.54,222.08 56.39,389.22 139.13,416.51 C221.86,443.81 234.66,227.56 233.16,109.56 C231.84,6.17 133.65,136.44 51.49,39.06 C37.94,22.95 -7.96,56.24 -8.56,118.7c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="917"
+ android:propertyName="pathData"
+ android:startOffset="917"
+ android:valueFrom="M-8.56 118.7 C-9.54,222.08 56.39,389.22 139.13,416.51 C221.86,443.81 234.66,227.56 233.16,109.56 C231.84,6.17 133.65,136.44 51.49,39.06 C37.94,22.95 -7.96,56.24 -8.56,118.7c "
+ android:valueTo="M-8.56 118.7 C-9.54,222.08 56.39,389.22 139.13,416.51 C221.86,443.81 238.66,196.56 237.16,78.56 C235.84,-24.83 133.65,136.44 51.49,39.06 C37.94,22.95 -7.96,56.24 -8.56,118.7c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.212,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1000"
+ android:propertyName="pathData"
+ android:startOffset="1833"
+ android:valueFrom="M-8.56 118.7 C-9.54,222.08 56.39,389.22 139.13,416.51 C221.86,443.81 238.66,196.56 237.16,78.56 C235.84,-24.83 133.65,136.44 51.49,39.06 C37.94,22.95 -7.96,56.24 -8.56,118.7c "
+ android:valueTo="M5.87 164.71 C0.43,267.95 56.57,389.47 139.31,416.77 C222.04,444.07 215.7,295.51 248.09,197.33 C280.49,99.14 111.3,154.92 55.02,40.63 C41.48,24.52 8.77,109.58 5.87,164.71c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="513.995"
+ android:valueTo="513.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.711,0 0.772,0.166 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="513.995"
+ android:valueTo="469.109"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.711,0 0.772,0.166 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="433"
+ android:propertyName="translateX"
+ android:startOffset="483"
+ android:valueFrom="469.109"
+ android:valueTo="343.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.094,0.355 0.269,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="translateX"
+ android:startOffset="917"
+ android:valueFrom="343.995"
+ android:valueTo="367.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.475,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="translateX"
+ android:startOffset="1483"
+ android:valueFrom="367.995"
+ android:valueTo="513.995"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.513,0 0.219,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="909.041"
+ android:valueTo="909.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="583"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="909.041"
+ android:valueTo="859.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="translateY"
+ android:startOffset="917"
+ android:valueFrom="859.041"
+ android:valueTo="883.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.475,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="translateY"
+ android:startOffset="1483"
+ android:valueFrom="883.041"
+ android:valueTo="909.041"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.513,0 0.219,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-60"
+ android:valueTo="-60"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="583"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-60"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="567"
+ android:propertyName="rotation"
+ android:startOffset="917"
+ android:valueFrom="0"
+ android:valueTo="-2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.545,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="rotation"
+ android:startOffset="1483"
+ android:valueFrom="-2"
+ android:valueTo="-60"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.513,0 0.219,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="2850"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/overview_gesture.xml b/quickstep/res/drawable/overview_gesture.xml
new file mode 100644
index 0000000..68c48d4
--- /dev/null
+++ b/quickstep/res/drawable/overview_gesture.xml
@@ -0,0 +1,1173 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="412dp"
+ android:height="890dp"
+ android:viewportWidth="412"
+ android:viewportHeight="890">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_3_G_N_4_N_3_N_2_T_0"
+ android:translateX="297.398"
+ android:translateY="721.169">
+ <group
+ android:name="_R_G_L_3_G_N_4_N_3_T_1"
+ android:rotation="-45"
+ android:translateX="110.176"
+ android:translateY="177.218">
+ <group
+ android:name="_R_G_L_3_G_N_4_N_3_T_0"
+ android:translateX="-132.239"
+ android:translateY="-133.055">
+ <group
+ android:name="_R_G_L_3_G_N_4_T_0"
+ android:pivotX="71.634"
+ android:pivotY="92.684"
+ android:rotation="-2"
+ android:translateX="-36.948"
+ android:translateY="-58.704">
+ <group
+ android:name="_R_G_L_3_G"
+ android:pivotX="48.25"
+ android:pivotY="48.25"
+ android:rotation="-47"
+ android:translateX="-20.55"
+ android:translateY="-21.306">
+ <path
+ android:name="_R_G_L_3_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#e8f0fe"
+ android:fillType="nonZero"
+ android:pathData=" M96.25 48.25 C96.25,74.76 74.76,96.25 48.25,96.25 C21.74,96.25 0.25,74.76 0.25,48.25 C0.25,21.74 21.74,0.25 48.25,0.25 C74.76,0.25 96.25,21.74 96.25,48.25c " />
+ </group>
+ </group>
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_2_G"
+ android:pivotX="48.25"
+ android:pivotY="48.25"
+ android:rotation="-47"
+ android:translateX="152.837"
+ android:translateY="698.322">
+ <path
+ android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#e8f0fe"
+ android:fillType="nonZero"
+ android:pathData=" M96.25 48.25 C96.25,74.76 74.76,96.25 48.25,96.25 C21.74,96.25 0.25,74.76 0.25,48.25 C0.25,21.74 21.74,0.25 48.25,0.25 C74.76,0.25 96.25,21.74 96.25,48.25c " />
+ </group>
+ <group
+ android:name="_R_G_L_1_G_N_3_N_2_T_0"
+ android:translateX="297.398"
+ android:translateY="721.169">
+ <group
+ android:name="_R_G_L_1_G_N_3_T_1"
+ android:rotation="-45"
+ android:translateX="110.176"
+ android:translateY="177.218">
+ <group
+ android:name="_R_G_L_1_G_N_3_T_0"
+ android:translateX="-132.239"
+ android:translateY="-133.055">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="71.634"
+ android:pivotY="92.684"
+ android:rotation="-2"
+ android:translateX="-36.948"
+ android:translateY="-58.704">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M20.63 8.95 C20.63,8.95 20.63,8.95 20.63,8.95 C32.8,0.25 49.72,2.9 59.23,14.69 C59.23,14.69 96.98,58.55 96.98,58.55 C113.17,83.98 104.58,100.89 89.25,111.3 C89.25,111.3 85.05,113.83 85.05,113.83 C67.95,125.12 45.66,118.31 36.77,99.32 C36.77,99.32 11.14,47.2 11.14,47.2 C4.45,34.1 8.44,17.28 20.63,8.95c " />
+ <path
+ android:name="_R_G_L_1_G_D_1_P_0"
+ android:pathData=" M50.61 24.81 C53.83,30.61 58.53,37.85 51.26,44.87 C51.26,44.87 38.64,50.25 34.66,52.09 C32.65,53.03 31.97,54.36 29.89,54.38 C28.17,54.4 26.19,53.43 24.53,50.61 C18.85,40.97 15,34.43 17.4,28.44 "
+ android:strokeWidth="6"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9"/>
+ </group>
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_0_G_N_2_T_0"
+ android:translateX="297.398"
+ android:translateY="721.169">
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:rotation="-45"
+ android:translateX="110.176"
+ android:translateY="177.218">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="-132.239"
+ android:translateY="-133.055">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 136.7,91.72 185.58,117.71 C221.01,256.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:pathData=" M26.74 49.32 C26.74,49.32 34.17,35.86 51.22,27.28 "
+ android:strokeWidth="6"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9"/>
+ </group>
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_3_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="217"
+ android:propertyName="fillAlpha"
+ android:startOffset="333"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G_N_4_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-2"
+ android:valueTo="-2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-2"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1100"
+ android:propertyName="rotation"
+ android:startOffset="1167"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="rotation"
+ android:startOffset="2267"
+ android:valueFrom="0"
+ android:valueTo="-2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G_N_4_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="110.176"
+ android:valueTo="110.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="110.176"
+ android:valueTo="45.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="933"
+ android:propertyName="translateX"
+ android:startOffset="1167"
+ android:valueFrom="45.176"
+ android:valueTo="45.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="translateX"
+ android:startOffset="2100"
+ android:valueFrom="45.176"
+ android:valueTo="45.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="translateX"
+ android:startOffset="2267"
+ android:valueFrom="45.176"
+ android:valueTo="110.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.286,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G_N_4_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="177.218"
+ android:valueTo="177.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="177.218"
+ android:valueTo="190.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="translateY"
+ android:startOffset="1167"
+ android:valueFrom="190.218"
+ android:valueTo="190.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="1767"
+ android:valueFrom="190.218"
+ android:valueTo="201.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="translateY"
+ android:startOffset="2100"
+ android:valueFrom="201.218"
+ android:valueTo="201.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="translateY"
+ android:startOffset="2267"
+ android:valueFrom="201.218"
+ android:valueTo="177.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.286,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G_N_4_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-45"
+ android:valueTo="-45"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.618,0 0.348,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-45"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.618,0 0.348,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="rotation"
+ android:startOffset="1167"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.348,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="1767"
+ android:valueFrom="0"
+ android:valueTo="-1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="rotation"
+ android:startOffset="2100"
+ android:valueFrom="-1"
+ android:valueTo="-1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="rotation"
+ android:startOffset="2267"
+ android:valueFrom="-1"
+ android:valueTo="-45"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.286,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G_N_4_N_3_N_2_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="0"
+ android:propertyName="scaleY"
+ android:startOffset="1167"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="1767"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="fillAlpha"
+ android:startOffset="1767"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="0"
+ android:propertyName="scaleX"
+ android:startOffset="1167"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M20.63 8.95 C20.63,8.95 20.63,8.95 20.63,8.95 C32.8,0.25 49.72,2.9 59.23,14.69 C59.23,14.69 96.98,58.55 96.98,58.55 C113.17,83.98 104.58,100.89 89.25,111.3 C89.25,111.3 85.05,113.83 85.05,113.83 C67.95,125.12 45.66,118.31 36.77,99.32 C36.77,99.32 11.14,47.2 11.14,47.2 C4.45,34.1 8.44,17.28 20.63,8.95c "
+ android:valueTo="M20.63 8.95 C20.63,8.95 20.63,8.95 20.63,8.95 C32.8,0.25 49.72,2.9 59.23,14.69 C59.23,14.69 96.98,58.55 96.98,58.55 C113.17,83.98 104.58,100.89 89.25,111.3 C89.25,111.3 85.05,113.83 85.05,113.83 C67.95,125.12 45.66,118.31 36.77,99.32 C36.77,99.32 11.14,47.2 11.14,47.2 C4.45,34.1 8.44,17.28 20.63,8.95c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M20.63 8.95 C20.63,8.95 20.63,8.95 20.63,8.95 C32.8,0.25 49.72,2.9 59.23,14.69 C59.23,14.69 96.98,58.55 96.98,58.55 C113.17,83.98 104.58,100.89 89.25,111.3 C89.25,111.3 85.05,113.83 85.05,113.83 C67.95,125.12 45.66,118.31 36.77,99.32 C36.77,99.32 11.14,47.2 11.14,47.2 C4.45,34.1 8.44,17.28 20.63,8.95c "
+ android:valueTo="M20.63 8.95 C20.63,8.95 20.63,8.95 20.63,8.95 C32.8,0.25 49.72,2.9 59.23,14.69 C59.23,14.69 94.24,55.98 94.24,55.98 C109.11,75.87 104.58,100.89 89.25,111.3 C89.25,111.3 85.05,113.83 85.05,113.83 C67.95,125.12 45.66,118.31 36.77,99.32 C36.77,99.32 11.14,47.2 11.14,47.2 C4.45,34.1 8.44,17.28 20.63,8.95c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1100"
+ android:propertyName="pathData"
+ android:startOffset="1167"
+ android:valueFrom="M20.63 8.95 C20.63,8.95 20.63,8.95 20.63,8.95 C32.8,0.25 49.72,2.9 59.23,14.69 C59.23,14.69 94.24,55.98 94.24,55.98 C109.11,75.87 104.58,100.89 89.25,111.3 C89.25,111.3 85.05,113.83 85.05,113.83 C67.95,125.12 45.66,118.31 36.77,99.32 C36.77,99.32 11.14,47.2 11.14,47.2 C4.45,34.1 8.44,17.28 20.63,8.95c "
+ android:valueTo="M20.63 8.95 C20.63,8.95 20.63,8.95 20.63,8.95 C32.8,0.25 49.72,2.9 59.23,14.69 C59.23,14.69 94.24,55.98 94.24,55.98 C109.11,75.87 104.58,100.89 89.25,111.3 C89.25,111.3 85.05,113.83 85.05,113.83 C67.95,125.12 45.66,118.31 36.77,99.32 C36.77,99.32 11.14,47.2 11.14,47.2 C4.45,34.1 8.44,17.28 20.63,8.95c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="pathData"
+ android:startOffset="2267"
+ android:valueFrom="M20.63 8.95 C20.63,8.95 20.63,8.95 20.63,8.95 C32.8,0.25 49.72,2.9 59.23,14.69 C59.23,14.69 94.24,55.98 94.24,55.98 C109.11,75.87 104.58,100.89 89.25,111.3 C89.25,111.3 85.05,113.83 85.05,113.83 C67.95,125.12 45.66,118.31 36.77,99.32 C36.77,99.32 11.14,47.2 11.14,47.2 C4.45,34.1 8.44,17.28 20.63,8.95c "
+ android:valueTo="M20.63 8.95 C20.63,8.95 20.63,8.95 20.63,8.95 C32.8,0.25 49.72,2.9 59.23,14.69 C59.23,14.69 96.98,58.55 96.98,58.55 C113.17,83.98 104.58,100.89 89.25,111.3 C89.25,111.3 85.05,113.83 85.05,113.83 C67.95,125.12 45.66,118.31 36.77,99.32 C36.77,99.32 11.14,47.2 11.14,47.2 C4.45,34.1 8.44,17.28 20.63,8.95c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M50.61 24.81 C53.83,30.61 58.53,37.85 51.26,44.87 C51.26,44.87 38.64,50.25 34.66,52.09 C32.65,53.03 31.97,54.36 29.89,54.38 C28.17,54.4 26.19,53.43 24.53,50.61 C18.85,40.97 15,34.43 17.4,28.44 "
+ android:valueTo="M50.61 24.81 C53.83,30.61 58.53,37.85 51.26,44.87 C51.26,44.87 38.64,50.25 34.66,52.09 C32.65,53.03 31.97,54.36 29.89,54.38 C28.17,54.4 26.19,53.43 24.53,50.61 C18.85,40.97 15,34.43 17.4,28.44 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M50.61 24.81 C53.83,30.61 58.53,37.85 51.26,44.87 C51.26,44.87 38.64,50.25 34.66,52.09 C32.65,53.03 31.97,54.36 29.89,54.38 C28.17,54.4 26.19,53.43 24.53,50.61 C18.85,40.97 15,34.43 17.4,28.44 "
+ android:valueTo="M57.62 26.47 C60,29.91 61.1,33.98 55.03,38.28 C55.03,38.28 49.65,41.69 36.11,51.08 C34.29,52.35 31.97,54.36 29.89,54.38 C28.17,54.4 26.19,53.43 24.53,50.61 C18.85,40.97 15,34.43 17.4,28.44 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1100"
+ android:propertyName="pathData"
+ android:startOffset="1167"
+ android:valueFrom="M57.62 26.47 C60,29.91 61.1,33.98 55.03,38.28 C55.03,38.28 49.65,41.69 36.11,51.08 C34.29,52.35 31.97,54.36 29.89,54.38 C28.17,54.4 26.19,53.43 24.53,50.61 C18.85,40.97 15,34.43 17.4,28.44 "
+ android:valueTo="M57.62 26.47 C60,29.91 61.1,33.98 55.03,38.28 C55.03,38.28 49.65,41.69 36.11,51.08 C34.29,52.35 31.97,54.36 29.89,54.38 C28.17,54.4 26.19,53.43 24.53,50.61 C18.85,40.97 15,34.43 17.4,28.44 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="pathData"
+ android:startOffset="2267"
+ android:valueFrom="M57.62 26.47 C60,29.91 61.1,33.98 55.03,38.28 C55.03,38.28 49.65,41.69 36.11,51.08 C34.29,52.35 31.97,54.36 29.89,54.38 C28.17,54.4 26.19,53.43 24.53,50.61 C18.85,40.97 15,34.43 17.4,28.44 "
+ android:valueTo="M50.61 24.81 C53.83,30.61 58.53,37.85 51.26,44.87 C51.26,44.87 38.64,50.25 34.66,52.09 C32.65,53.03 31.97,54.36 29.89,54.38 C28.17,54.4 26.19,53.43 24.53,50.61 C18.85,40.97 15,34.43 17.4,28.44 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-2"
+ android:valueTo="-2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-2"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1100"
+ android:propertyName="rotation"
+ android:startOffset="1167"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="rotation"
+ android:startOffset="2267"
+ android:valueFrom="0"
+ android:valueTo="-2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="110.176"
+ android:valueTo="110.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="110.176"
+ android:valueTo="45.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="933"
+ android:propertyName="translateX"
+ android:startOffset="1167"
+ android:valueFrom="45.176"
+ android:valueTo="45.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="translateX"
+ android:startOffset="2100"
+ android:valueFrom="45.176"
+ android:valueTo="45.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="translateX"
+ android:startOffset="2267"
+ android:valueFrom="45.176"
+ android:valueTo="110.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.286,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="177.218"
+ android:valueTo="177.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="177.218"
+ android:valueTo="190.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="translateY"
+ android:startOffset="1167"
+ android:valueFrom="190.218"
+ android:valueTo="190.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="1767"
+ android:valueFrom="190.218"
+ android:valueTo="201.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="translateY"
+ android:startOffset="2100"
+ android:valueFrom="201.218"
+ android:valueTo="201.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="translateY"
+ android:startOffset="2267"
+ android:valueFrom="201.218"
+ android:valueTo="177.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.286,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_3_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-45"
+ android:valueTo="-45"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.618,0 0.348,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-45"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.618,0 0.348,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="rotation"
+ android:startOffset="1167"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.348,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="1767"
+ android:valueFrom="0"
+ android:valueTo="-1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="rotation"
+ android:startOffset="2100"
+ android:valueFrom="-1"
+ android:valueTo="-1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="rotation"
+ android:startOffset="2267"
+ android:valueFrom="-1"
+ android:valueTo="-45"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.286,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 136.7,91.72 185.58,117.71 C221.01,256.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c "
+ android:valueTo="M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 136.7,91.72 185.58,117.71 C221.01,256.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.425,0 0.463,0.469 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="700"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 136.7,91.72 185.58,117.71 C221.01,256.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c "
+ android:valueTo="M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 148.67,90.67 218.58,75.71 C254.01,214.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.425,0 0.463,0.469 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="733"
+ android:propertyName="pathData"
+ android:startOffset="1033"
+ android:valueFrom="M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 148.67,90.67 218.58,75.71 C254.01,214.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c "
+ android:valueTo="M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 148.67,90.67 218.58,75.71 C254.01,214.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.275,0.272 0.661,0.665 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="pathData"
+ android:startOffset="1767"
+ android:valueFrom="M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 148.67,90.67 218.58,75.71 C254.01,214.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c "
+ android:valueTo="M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 148.67,90.67 218.58,75.71 C254.01,214.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.165 0.661,0.665 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="733"
+ android:propertyName="pathData"
+ android:startOffset="2267"
+ android:valueFrom="M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 148.67,90.67 218.58,75.71 C254.01,214.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c "
+ android:valueTo="M42.21 4.34 C42.21,4.34 58.68,0.25 58.68,0.25 C68.53,11.64 74.12,25.6 85.21,38.39 C115.84,72.27 136.7,91.72 185.58,117.71 C221.01,256.74 312.66,319.67 166.82,292.86 C166.82,292.86 61.19,143.45 61.19,143.45 C57.04,130.17 50.08,112.95 39.06,94.86 C28.46,77.49 14.05,57.75 4.69,48.2 C4.2,46.35 0.25,36.08 10.96,21.83 C17.93,11.59 30.17,4.01 42.21,4.34c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.449,0.445 0.528,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M26.74 49.32 C26.74,49.32 34.17,35.86 51.22,27.28 "
+ android:valueTo="M26.74 49.32 C26.74,49.32 34.17,35.86 51.22,27.28 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.55,0 0.375,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="pathData"
+ android:startOffset="333"
+ android:valueFrom="M26.74 49.32 C26.74,49.32 34.17,35.86 51.22,27.28 "
+ android:valueTo="M32.48 57.18 C32.48,57.18 40.18,30.6 69.87,29.66 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.55,0 0.375,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="pathData"
+ android:startOffset="1167"
+ android:valueFrom="M32.48 57.18 C32.48,57.18 40.18,30.6 69.87,29.66 "
+ android:valueTo="M32.48 57.18 C32.48,57.18 40.18,30.6 69.87,29.66 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="1767"
+ android:valueFrom="M32.48 57.18 C32.48,57.18 40.18,30.6 69.87,29.66 "
+ android:valueTo="M32.48 57.18 C32.48,57.18 40.18,30.6 69.87,29.66 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="2100"
+ android:valueFrom="M32.48 57.18 C32.48,57.18 40.18,30.6 69.87,29.66 "
+ android:valueTo="M32.48 57.18 C32.48,57.18 40.18,30.6 69.87,29.66 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="pathData"
+ android:startOffset="2267"
+ android:valueFrom="M32.48 57.18 C32.48,57.18 40.18,30.6 69.87,29.66 "
+ android:valueTo="M26.74 49.32 C26.74,49.32 34.17,35.86 51.22,27.28 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="110.176"
+ android:valueTo="110.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="translateX"
+ android:startOffset="333"
+ android:valueFrom="110.176"
+ android:valueTo="45.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="933"
+ android:propertyName="translateX"
+ android:startOffset="1167"
+ android:valueFrom="45.176"
+ android:valueTo="45.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="translateX"
+ android:startOffset="2100"
+ android:valueFrom="45.176"
+ android:valueTo="45.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="translateX"
+ android:startOffset="2267"
+ android:valueFrom="45.176"
+ android:valueTo="110.176"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.286,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="177.218"
+ android:valueTo="177.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="translateY"
+ android:startOffset="333"
+ android:valueFrom="177.218"
+ android:valueTo="190.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.658,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="translateY"
+ android:startOffset="1167"
+ android:valueFrom="190.218"
+ android:valueTo="190.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.401,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateY"
+ android:startOffset="1767"
+ android:valueFrom="190.218"
+ android:valueTo="201.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="translateY"
+ android:startOffset="2100"
+ android:valueFrom="201.218"
+ android:valueTo="201.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="translateY"
+ android:startOffset="2267"
+ android:valueFrom="201.218"
+ android:valueTo="177.218"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.286,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-45"
+ android:valueTo="-45"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.618,0 0.348,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="833"
+ android:propertyName="rotation"
+ android:startOffset="333"
+ android:valueFrom="-45"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.618,0 0.348,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="rotation"
+ android:startOffset="1167"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.348,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="rotation"
+ android:startOffset="1767"
+ android:valueFrom="0"
+ android:valueTo="-1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="rotation"
+ android:startOffset="2100"
+ android:valueFrom="-1"
+ android:valueTo="-1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.539,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1233"
+ android:propertyName="rotation"
+ android:startOffset="2267"
+ android:valueFrom="-1"
+ android:valueTo="-45"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.286,0 0.489,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="3500"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/layout/back_gesture_tutorial_fragment.xml b/quickstep/res/layout/back_gesture_tutorial_fragment.xml
deleted file mode 100644
index d8c25bd..0000000
--- a/quickstep/res/layout/back_gesture_tutorial_fragment.xml
+++ /dev/null
@@ -1,119 +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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/back_gesture_tutorial_background_color">
-
- <ImageView
- android:id="@+id/back_gesture_tutorial_fragment_hand_coaching"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerCrop"/>
-
- <ImageButton
- android:id="@+id/back_gesture_tutorial_fragment_close_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="18dp"
- android:layout_marginTop="30dp"
- android:layout_marginStart="4dp"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:background="@android:color/transparent"
- android:accessibilityTraversalAfter="@id/back_gesture_tutorial_fragment_titles_container"
- android:contentDescription="@string/back_gesture_tutorial_close_button_content_description"
- android:src="@drawable/back_gesture_tutorial_close_button"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="70dp"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/back_gesture_tutorial_fragment_titles_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:focusable="true">
-
- <TextView
- android:id="@+id/back_gesture_tutorial_fragment_title_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_marginStart="@dimen/back_gesture_tutorial_title_margin_start_end"
- android:layout_marginEnd="@dimen/back_gesture_tutorial_title_margin_start_end"
- style="@style/TextAppearance.BackGestureTutorial.Title"/>
-
- <TextView
- android:id="@+id/back_gesture_tutorial_fragment_subtitle_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_marginTop="10dp"
- android:layout_marginStart="@dimen/back_gesture_tutorial_subtitle_margin_start_end"
- android:layout_marginEnd="@dimen/back_gesture_tutorial_subtitle_margin_start_end"
- style="@style/TextAppearance.BackGestureTutorial.Subtitle"/>
-
- </LinearLayout>
-
- <Space
- android:layout_width="wrap_content"
- android:layout_weight="1"
- android:layout_height="0dp"
- android:layout_marginTop="48dp"
- android:layout_gravity="center_horizontal"
- android:gravity="center_horizontal"
- android:orientation="vertical"/>
-
- <!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase
- of elevation and shadow) which is replaced by ripple effect in android:foreground -->
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="46dp"
- android:layout_marginBottom="48dp"
- android:layout_gravity="center_horizontal">
-
- <Button
- android:id="@+id/back_gesture_tutorial_fragment_action_button"
- android:layout_width="142dp"
- android:layout_height="49dp"
- android:layout_marginEnd="@dimen/back_gesture_tutorial_button_margin_start_end"
- android:layout_alignParentEnd="true"
- android:stateListAnimator="@null"
- android:background="@drawable/back_gesture_tutorial_action_button_background"
- android:foreground="?android:attr/selectableItemBackgroundBorderless"
- style="@style/TextAppearance.BackGestureTutorial.ButtonLabel"/>
-
- <Button
- android:id="@+id/back_gesture_tutorial_fragment_action_text_button"
- android:layout_width="142dp"
- android:layout_height="49dp"
- android:layout_marginStart="@dimen/back_gesture_tutorial_button_margin_start_end"
- android:layout_alignParentStart="true"
- android:stateListAnimator="@null"
- android:background="@null"
- android:foreground="?android:attr/selectableItemBackgroundBorderless"
- style="@style/TextAppearance.BackGestureTutorial.TextButtonLabel"/>
-
- </RelativeLayout>
-
- </LinearLayout>
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/back_gesture_tutorial_activity.xml b/quickstep/res/layout/gesture_tutorial_activity.xml
similarity index 92%
rename from quickstep/res/layout/back_gesture_tutorial_activity.xml
rename to quickstep/res/layout/gesture_tutorial_activity.xml
index e894e89..4dc8913 100644
--- a/quickstep/res/layout/back_gesture_tutorial_activity.xml
+++ b/quickstep/res/layout/gesture_tutorial_activity.xml
@@ -14,6 +14,6 @@
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/back_gesture_tutorial_fragment_container"
+ android:id="@+id/gesture_tutorial_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
new file mode 100644
index 0000000..459d65f
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -0,0 +1,121 @@
+<!--
+ 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?android:attr/colorBackground">
+
+ <View
+ android:id="@+id/gesture_tutorial_ripple_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/gesture_tutorial_ripple"/>
+
+ <View
+ android:id="@+id/gesture_tutorial_fake_task_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/gesture_tutorial_fake_task_view_color"
+ android:visibility="invisible" />
+
+ <ImageView
+ android:id="@+id/gesture_tutorial_fragment_hand_coaching"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"/>
+
+ <ImageButton
+ android:id="@+id/gesture_tutorial_fragment_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="18dp"
+ android:layout_marginTop="30dp"
+ android:layout_marginStart="4dp"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:background="@android:color/transparent"
+ android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_titles_container"
+ android:contentDescription="@string/gesture_tutorial_close_button_content_description"
+ android:tint="?android:attr/textColorPrimary"
+ android:src="@drawable/gesture_tutorial_close_button"/>
+
+ <LinearLayout
+ android:id="@+id/gesture_tutorial_fragment_titles_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="70dp"
+ android:layout_alignParentTop="true"
+ android:focusable="true"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/gesture_tutorial_fragment_title_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/gesture_tutorial_title_margin_start_end"
+ android:layout_marginEnd="@dimen/gesture_tutorial_title_margin_start_end"
+ style="@style/TextAppearance.GestureTutorial.Title"/>
+
+ <TextView
+ android:id="@+id/gesture_tutorial_fragment_subtitle_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginStart="@dimen/gesture_tutorial_subtitle_margin_start_end"
+ android:layout_marginEnd="@dimen/gesture_tutorial_subtitle_margin_start_end"
+ style="@style/TextAppearance.GestureTutorial.Subtitle"/>
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/gesture_tutorial_fragment_feedback_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:layout_centerHorizontal="true"
+ android:layout_above="@id/gesture_tutorial_fragment_action_button"
+ android:layout_marginStart="@dimen/gesture_tutorial_feedback_margin_start_end"
+ android:layout_marginEnd="@dimen/gesture_tutorial_feedback_margin_start_end"
+ style="@style/TextAppearance.GestureTutorial.Feedback"/>
+
+ <!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase
+ of elevation and shadow) which is replaced by ripple effect in android:foreground -->
+ <Button
+ android:id="@+id/gesture_tutorial_fragment_action_button"
+ android:layout_width="142dp"
+ android:layout_height="49dp"
+ android:layout_marginEnd="@dimen/gesture_tutorial_button_margin_start_end"
+ android:layout_marginBottom="48dp"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentBottom="true"
+ android:stateListAnimator="@null"
+ android:background="@drawable/gesture_tutorial_action_button_background"
+ android:foreground="?android:attr/selectableItemBackgroundBorderless"
+ style="@style/TextAppearance.GestureTutorial.ButtonLabel"/>
+
+ <Button
+ android:id="@+id/gesture_tutorial_fragment_action_text_button"
+ android:layout_width="142dp"
+ android:layout_height="49dp"
+ android:layout_marginStart="@dimen/gesture_tutorial_button_margin_start_end"
+ android:layout_marginBottom="48dp"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentBottom="true"
+ android:stateListAnimator="@null"
+ android:background="@null"
+ android:foreground="?android:attr/selectableItemBackgroundBorderless"
+ style="@style/TextAppearance.GestureTutorial.TextButtonLabel"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_actions.xml b/quickstep/res/layout/overview_actions.xml
deleted file mode 100644
index ad5efb6..0000000
--- a/quickstep/res/layout/overview_actions.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <LinearLayout
- android:id="@+id/action_buttons"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:orientation="horizontal">
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" >
- </Space>
- <Button
- android:id="@+id/action_screenshot"
- style="@style/OverviewActionButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawableTop="@drawable/ic_screenshot"
- android:text="@string/action_screenshot" />
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" >
- </Space>
-
- <Button
- android:id="@+id/action_share"
- style="@style/OverviewActionButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawableTop="@drawable/ic_share"
- android:text="@string/action_share" />
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" >
- </Space>
- </LinearLayout>
-
-</merge>
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index 328c20b..e05688e 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -14,10 +14,55 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.OverviewActionsView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone">
+<com.android.quickstep.views.OverviewActionsView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/overview_actions_height"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginLeft="@dimen/overview_actions_horizontal_margin"
+ android:layout_marginRight="@dimen/overview_actions_horizontal_margin">
+
+ <LinearLayout
+ android:id="@+id/action_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="horizontal">
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/action_screenshot"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableStart="@drawable/ic_screenshot"
+ android:text="@string/action_screenshot"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/action_share"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableStart="@drawable/ic_share"
+ android:text="@string/action_share"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor"
+ android:visibility="gone" />
+
+ <Space
+ android:id="@+id/share_space"
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1"
+ android:visibility="gone" />
+ </LinearLayout>
</com.android.quickstep.views.OverviewActionsView>
\ No newline at end of file
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 3583676..40da136 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -14,5 +14,6 @@
limitations under the License.
-->
<resources>
+ <color name="back_arrow_color_light">#FFFFFFFF</color>
<color name="back_arrow_color_dark">#99000000</color>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index a688f9a..0f2955b 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:tools="http://schemas.android.com/tools">
+<resources>
<string name="task_overlay_factory_class" translatable="false"/>
<!-- Activities which block home gesture -->
- <string-array name="gesture_blocking_activities" tools:ignore="InconsistentArrays">
+ <string-array name="gesture_blocking_activities" translatable="false">
<item>com.android.launcher3/com.android.quickstep.interaction.GestureSandboxActivity</item>
</string-array>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index dcc85d5..6737c5f 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -23,7 +23,10 @@
<dimen name="task_corner_radius_small">2dp</dimen>
<!-- Overrideable in overlay that provides the Overview Actions. -->
- <dimen name="overview_actions_height">110dp</dimen>
+ <dimen name="overview_actions_height">66dp</dimen>
+ <dimen name="overview_actions_bottom_margin_gesture">16dp</dimen>
+ <dimen name="overview_actions_bottom_margin_three_button">8dp</dimen>
+ <dimen name="overview_actions_horizontal_margin">16dp</dimen>
<dimen name="recents_page_spacing">10dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
@@ -59,7 +62,8 @@
<dimen name="task_card_menu_shadow_height">3dp</dimen>
<dimen name="task_card_menu_horizontal_padding">0dp</dimen>
<dimen name="portrait_task_card_horz_space">136dp</dimen>
- <dimen name="portrait_task_card_horz_space_big_overview">24dp</dimen>
+ <dimen name="portrait_task_card_horz_space_big_overview">96dp</dimen>
+ <dimen name="portrait_modal_task_card_horz_space">60dp</dimen>
<dimen name="landscape_task_card_horz_space">200dp</dimen>
<dimen name="multi_window_task_card_horz_space">100dp</dimen>
<!-- Copied from framework resource:
@@ -72,14 +76,30 @@
<dimen name="gestures_assistant_drag_threshold">55dp</dimen>
<dimen name="gestures_assistant_fling_threshold">55dp</dimen>
+ <!-- One-Handed Mode -->
+ <!-- Threshold for draging distance to enable one-handed mode -->
+ <dimen name="gestures_onehanded_drag_threshold">20dp</dimen>
+
<!-- Distance to move elements when swiping up to go home from launcher -->
<dimen name="home_pullback_distance">28dp</dimen>
+ <!-- Distance to move the tasks when swiping up while the device is locked -->
+ <dimen name="device_locked_y_offset">-80dp</dimen>
+
<!-- Overscroll Gesture -->
<dimen name="gestures_overscroll_fling_threshold">40dp</dimen>
+ <dimen name="gestures_overscroll_active_threshold">80dp</dimen>
+ <dimen name="gestures_overscroll_finish_threshold">136dp</dimen>
<!-- Tips Gesture Tutorial -->
- <dimen name="back_gesture_tutorial_title_margin_start_end">40dp</dimen>
- <dimen name="back_gesture_tutorial_subtitle_margin_start_end">16dp</dimen>
- <dimen name="back_gesture_tutorial_button_margin_start_end">18dp</dimen>
+ <dimen name="gesture_tutorial_title_margin_start_end">40dp</dimen>
+ <dimen name="gesture_tutorial_subtitle_margin_start_end">16dp</dimen>
+ <dimen name="gesture_tutorial_feedback_margin_start_end">24dp</dimen>
+ <dimen name="gesture_tutorial_button_margin_start_end">18dp</dimen>
+
+ <!-- All Apps Education tutorial -->
+ <dimen name="swipe_edu_padding">8dp</dimen>
+ <dimen name="swipe_edu_circle_size">64dp</dimen>
+ <dimen name="swipe_edu_width">80dp</dimen>
+ <dimen name="swipe_edu_max_height">184dp</dimen>
</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index f5552f0..86120e3 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -61,18 +61,14 @@
<string name="all_apps_prediction_tip">Your predicted apps</string>
<!-- Content description for a close button. [CHAR LIMIT=NONE] -->
- <string name="back_gesture_tutorial_close_button_content_description" translatable="false">Close</string>
-
-
- <!-- Hotseat migration notification title -->
- <string name="hotseat_edu_prompt_title">Easily access your most-used apps</string>
- <!-- Hotseat migration notification content -->
- <string name="hotseat_edu_prompt_content">Pixel predicts apps you\’ll need next, right on your Home screen. Tap to set up.</string>
+ <string name="gesture_tutorial_close_button_content_description" translatable="false">Close</string>
<!-- Hotseat educational strings for users who don't qualify for migration -->
<string name="hotseat_edu_title_migrate">Get app suggestions on the bottom row of your Home screen</string>
+ <string name="hotseat_edu_title_migrate_landscape">Get app suggestions on favorites row of your Home screen</string>
<string name="hotseat_edu_message_migrate">Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen. </string>
+ <string name="hotseat_edu_message_migrate_landscape">Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps in favorites row will move to your Home screen. </string>
<string name="hotseat_edu_message_migrate_alt">Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder.</string>
<!-- Button text to opt in for fully predicted hotseat -->
@@ -80,8 +76,8 @@
<!-- Button text to dismiss opt in for fully predicted hotseat -->
<string name="hotseat_edu_dismiss">No thanks</string>
- <!-- action shown to turn off predictions after onboarding -->
- <string name="hotseat_turn_off">Settings</string>
+ <!-- action shown to toggle predictions after onboarding -->
+ <string name="hotseat_prediction_settings">Settings</string>
<!-- tip shown if user has no items in hotseat to migrate -->
<string name="hotseat_auto_enrolled">Most-used apps appear here, and change based on routines</string>
@@ -89,6 +85,10 @@
<string name="hotseat_tip_no_empty_slots">Drag apps off the bottom row to get app suggestions</string>
<!-- tip shown if user declines migration and has some open spots for prediction -->
<string name="hotseat_tip_gaps_filled">App suggestions added to empty space</string>
+ <!-- tip shown when user migrates and predictions are enabled in hotseat -->
+ <string name="hotsaet_tip_prediction_enabled">App suggestions enabled</string>
+ <!-- tip shown when hotseat edu is requested while predictions are disabled -->
+ <string name="hotsaet_tip_prediction_disabled">App suggestions are disabled</string>
<!-- content description for hotseat items -->
<string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
@@ -97,25 +97,70 @@
<string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
<!-- Subtitle shown during interactive parts of Back gesture tutorial for right edge. [CHAR LIMIT=60] -->
<string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge" translatable="false">Start at the right edge and swipe toward the middle</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_swipe_too_far_from_right_edge" translatable="false">Make sure you swipe from the far right edge</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is cancelled. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_cancelled_right_edge" translatable="false">Make sure you swipe straight to the left and let go</string>
<!-- Title shown during interactive part of Back gesture tutorial for left edge. [CHAR LIMIT=30] -->
<string name="back_gesture_tutorial_playground_title_swipe_inward_left_edge" translatable="false">Try the other side</string>
<!-- Subtitle shown during interactive parts of Back gesture tutorial for left edge. [CHAR LIMIT=60] -->
<string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge" translatable="false">That\'s it! Now try swiping from the left edge.</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_swipe_too_far_from_left_edge" translatable="false">Make sure you swipe from the far left edge</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is cancelled. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_cancelled_left_edge" translatable="false">Make sure you swipe straight to the right and let go</string>
- <!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
- <string name="back_gesture_tutorial_confirm_title" translatable="false">All set</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial when the gesture is within the nav bar region. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_swipe_in_nav_bar" translatable="false">Make sure you don\'t swipe too close to the bottom of the screen</string>
<!-- Subtitle shown on the confirmation screen after successful gesture. [CHAR LIMIT=60] -->
<string name="back_gesture_tutorial_confirm_subtitle" translatable="false">To change the sensitivity of the back gesture, go to Settings</string>
- <!-- Button text shown on a button on the confirm screen. [CHAR LIMIT=14] -->
- <string name="back_gesture_tutorial_action_button_label" translatable="false">Done</string>
- <!-- Button text shown on a text button on the confirm screen. [CHAR LIMIT=14] -->
- <string name="back_gesture_tutorial_action_text_button_label" translatable="false">Settings</string>
+ <!-- Title shown during interactive part of Home gesture tutorial. [CHAR LIMIT=30] -->
+ <string name="home_gesture_tutorial_playground_title" translatable="false">Tutorial: Go Home</string>
+ <!-- Subtitle shown during interactive parts of Home gesture tutorial. [CHAR LIMIT=60] -->
+ <string name="home_gesture_tutorial_playground_subtitle" translatable="false">Try swiping upward from the bottom edge of the screen</string>
+ <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
+ <string name="home_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
+ <!-- Feedback shown during interactive parts of Home gesture tutorial when the Overview gesture is detected. [CHAR LIMIT=100] -->
+ <string name="home_gesture_feedback_overview_detected" translatable="false">Make sure you don\'t pause before letting go</string>
+ <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
+ <string name="home_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up</string>
+
+ <!-- Title shown during interactive part of Overview gesture tutorial. [CHAR LIMIT=30] -->
+ <string name="overview_gesture_tutorial_playground_title" translatable="false">Tutorial: Switch Apps</string>
+ <!-- Subtitle shown during interactive parts of Overview gesture tutorial. [CHAR LIMIT=60] -->
+ <string name="overview_gesture_tutorial_playground_subtitle" translatable="false">Swipe up from the bottom of the screen and hold</string>
+ <!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
+ <string name="overview_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
+ <!-- Feedback shown during interactive parts of Overview gesture tutorial when the Home gesture is detected. [CHAR LIMIT=100] -->
+ <string name="overview_gesture_feedback_home_detected" translatable="false">Try holding the window for longer before releasing</string>
+ <!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
+ <string name="overview_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up and pause</string>
+
+ <!-- Title shown during interactive part of Assistant gesture tutorial. [CHAR LIMIT=30] -->
+ <string name="assistant_gesture_tutorial_playground_title" translatable="false">Tutorial: Assistant</string>
+ <!-- Subtitle shown during interactive parts of Assistant gesture tutorial. [CHAR LIMIT=60] -->
+ <string name="assistant_gesture_tutorial_playground_subtitle" translatable="false">Try swiping diagonally from a bottom corner of the screen</string>
+ <!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture is started too far from the corner. [CHAR LIMIT=100] -->
+ <string name="assistant_gesture_feedback_swipe_too_far_from_corner" translatable="false">Make sure you swipe from a bottom corner of the screen</string>
+ <!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture doesn't go diagonally enough. [CHAR LIMIT=100] -->
+ <string name="assistant_gesture_feedback_swipe_not_diagonal" translatable="false">Make sure you swipe diagonally</string>
+ <!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture doesn't go far enough. [CHAR LIMIT=100] -->
+ <string name="assistant_gesture_feedback_swipe_not_long_enough" translatable="false">Try swiping further</string>
+
+ <!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
+ <string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
+ <!-- Button text shown on a button on the confirm screen to leave the tutorial. [CHAR LIMIT=14] -->
+ <string name="gesture_tutorial_action_button_label_done" translatable="false">Done</string>
+ <!-- Button text shown on a button to go to Settings. [CHAR LIMIT=14] -->
+ <string name="gesture_tutorial_action_button_label_settings" translatable="false">Settings</string>
<!-- ******* Overview ******* -->
<!-- Label for a button that causes the current overview app to be shared. [CHAR_LIMIT=40] -->
<string name="action_share">Share</string>
<!-- Label for a button that causes a screen shot of the current app to be taken. [CHAR_LIMIT=40] -->
<string name="action_screenshot">Screenshot</string>
+ <!-- Message shown when an action is blocked by a policy enforced by the app or the organization managing the device. [CHAR_LIMIT=NONE] -->
+ <string name="blocked_by_policy">This action isn\'t allowed by the app or your organization</string>
</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index bf107fb..8d054b4 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -26,47 +26,63 @@
<item name="android:layout_height">wrap_content</item>
</style>
- <style name="TextAppearance.BackGestureTutorial"
+ <style name="TextAppearance.GestureTutorial"
parent="android:TextAppearance.Material.Body1" />
- <style name="TextAppearance.BackGestureTutorial.CallToAction"
+ <style name="TextAppearance.GestureTutorial.CallToAction"
parent="android:TextAppearance.Material.Body2" />
- <style name="TextAppearance.BackGestureTutorial.Title"
- parent="TextAppearance.BackGestureTutorial">
+ <style name="TextAppearance.GestureTutorial.Title"
+ parent="TextAppearance.GestureTutorial">
<item name="android:gravity">center</item>
- <item name="android:textColor">@color/back_gesture_tutorial_title_color</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">28sp</item>
</style>
- <style name="TextAppearance.BackGestureTutorial.Subtitle"
- parent="TextAppearance.BackGestureTutorial">
+ <style name="TextAppearance.GestureTutorial.Subtitle"
+ parent="TextAppearance.GestureTutorial">
<item name="android:gravity">center</item>
- <item name="android:textColor">@color/back_gesture_tutorial_subtitle_color</item>
+ <item name="android:textColor">?android:attr/textColorTertiary</item>
<item name="android:letterSpacing">0.03</item>
<item name="android:textSize">21sp</item>
</style>
- <style name="TextAppearance.BackGestureTutorial.ButtonLabel"
- parent="TextAppearance.BackGestureTutorial.CallToAction">
+ <style name="TextAppearance.GestureTutorial.Feedback"
+ parent="TextAppearance.GestureTutorial">
<item name="android:gravity">center</item>
- <item name="android:textColor">@color/back_gesture_tutorial_action_button_label_color</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:letterSpacing">0.03</item>
+ <item name="android:textSize">21sp</item>
+ </style>
+
+ <style name="TextAppearance.GestureTutorial.ButtonLabel"
+ parent="TextAppearance.GestureTutorial.CallToAction">
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">@color/gesture_tutorial_action_button_label_color</item>
<item name="android:letterSpacing">0.02</item>
<item name="android:textSize">16sp</item>
<item name="android:textAllCaps">false</item>
</style>
- <style name="TextAppearance.BackGestureTutorial.TextButtonLabel"
- parent="TextAppearance.BackGestureTutorial.ButtonLabel">
- <item name="android:textColor">@color/back_gesture_tutorial_primary_color</item>
+ <style name="TextAppearance.GestureTutorial.TextButtonLabel"
+ parent="TextAppearance.GestureTutorial.ButtonLabel">
+ <item name="android:textColor">@color/gesture_tutorial_primary_color</item>
+ </style>
+
+ <!--
+ Can be applied to views to color things like ripples and list highlights the workspace text
+ color.
+ -->
+ <style name="ThemeControlHighlightWorkspaceColor">
+ <item name="android:colorControlHighlight">?attr/workspaceTextColor</item>
</style>
<style name="OverviewActionButton"
parent="@android:style/Widget.DeviceDefault.Button.Borderless">
- <item name="android:textColor">?attr/workspaceTextColor</item>
- <item name="android:drawableTint">?attr/workspaceTextColor</item>
+ <item name="android:textColor">@color/overview_button</item>
+ <item name="android:drawableTint">@color/overview_button</item>
<item name="android:tint">?attr/workspaceTextColor</item>
- <item name="android:drawablePadding">4dp</item>
+ <item name="android:drawablePadding">8dp</item>
<item name="android:textAllCaps">false</item>
</style>
</resources>
\ No newline at end of file
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 53f37c1..22d205a 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -59,6 +59,7 @@
mResources = mock(Resources.class);
when(mResources.getBoolean(anyInt())).thenReturn(true);
when(mResources.getDimension(anyInt())).thenReturn(10.0f);
+ when(mResources.getDimensionPixelSize(anyInt())).thenReturn(10);
DisplayMetrics mockDisplayMetrics = new DisplayMetrics();
mockDisplayMetrics.density = DENSITY_DISPLAY_METRICS;
when(mResources.getDisplayMetrics()).thenReturn(mockDisplayMetrics);
@@ -67,53 +68,114 @@
}
@Test
- public void disabledMultipeRegions_shouldOverrideFirstRegion() {
- mTouchTransformer.createOrAddTouchRegion(mInfo);
- DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90);
- mTouchTransformer.createOrAddTouchRegion(info2);
+ public void disabledMultipleRegions_shouldOverrideFirstRegion() {
+ float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+ float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
- float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
- MotionEvent inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
- mTouchTransformer.transform(inOldRegion);
- assertFalse(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY()));
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ tapAndAssertTrue(100, portraitRegionY,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+ tapAndAssertFalse(100, landscapeRegionY,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+ tapAndAssertTrue(0, portraitRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ tapAndAssertFalse(0, landscapeRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
// Override region
+ mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ tapAndAssertFalse(100, portraitRegionY,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+ tapAndAssertTrue(100, landscapeRegionY,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+ tapAndAssertFalse(0, portraitRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ tapAndAssertTrue(0, landscapeRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+
+ // Override region again
mTouchTransformer.createOrAddTouchRegion(mInfo);
- inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
- mTouchTransformer.transform(inOldRegion);
- assertTrue(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY()));
+ tapAndAssertTrue(100, portraitRegionY,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+ tapAndAssertFalse(100, landscapeRegionY,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+ tapAndAssertTrue(0, portraitRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ tapAndAssertFalse(0, landscapeRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
}
@Test
- public void allowMultipeRegions_shouldOverrideFirstRegion() {
- DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90);
- mTouchTransformer.createOrAddTouchRegion(info2);
+ public void enableMultipleRegions_shouldOverrideFirstRegion() {
+ float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+ float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+
+ mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ tapAndAssertFalse(100, portraitRegionY,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+ tapAndAssertTrue(100, landscapeRegionY,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+ tapAndAssertFalse(0, portraitRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ tapAndAssertTrue(0, landscapeRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
// We have to add 0 rotation second so that gets set as the current rotation, otherwise
// matrix transform will fail (tests only work in Portrait at the moment)
mTouchTransformer.enableMultipleRegions(true, mInfo);
mTouchTransformer.createOrAddTouchRegion(mInfo);
- float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
- MotionEvent inNewRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
- mTouchTransformer.transform(inNewRegion);
- assertTrue(mTouchTransformer.touchInValidSwipeRegions(inNewRegion.getX(), inNewRegion.getY()));
+ tapAndAssertTrue(100, portraitRegionY,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+ tapAndAssertFalse(100, landscapeRegionY,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
+ tapAndAssertTrue(0, portraitRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ tapAndAssertFalse(0, landscapeRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ }
+
+ @Test
+ public void enableMultipleRegions_assistantTriggersInMostRecent() {
+ float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+ float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+
+ mTouchTransformer.enableMultipleRegions(true, mInfo);
+ mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ tapAndAssertTrue(0, portraitRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ tapAndAssertFalse(0, landscapeRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ }
+
+ @Test
+ public void enableMultipleRegions_assistantTriggersInCurrentOrientationAfterDisable() {
+ float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+ float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+
+ mTouchTransformer.enableMultipleRegions(true, mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ mTouchTransformer.enableMultipleRegions(false, mInfo);
+ tapAndAssertTrue(0, portraitRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ tapAndAssertFalse(0, landscapeRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
}
@Test
public void applyTransform_taskNotFrozen_notInRegion() {
mTouchTransformer.createOrAddTouchRegion(mInfo);
- MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100);
- mTouchTransformer.transform(outOfRegion);
- assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY()));
+ tapAndAssertFalse(100, 100,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
}
@Test
public void applyTransform_taskFrozen_noRotate_outOfRegion() {
mTouchTransformer.createOrAddTouchRegion(mInfo);
mTouchTransformer.enableMultipleRegions(true, mInfo);
- MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100);
- mTouchTransformer.transform(outOfRegion);
- assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY()));
+ tapAndAssertFalse(100, 100,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
}
@Test
@@ -121,27 +183,24 @@
mTouchTransformer.createOrAddTouchRegion(mInfo);
mTouchTransformer.enableMultipleRegions(true, mInfo);
float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
- MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
- mTouchTransformer.transform(inRegion);
- assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+ tapAndAssertTrue(100, y,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
}
@Test
public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() {
mTouchTransformer.createOrAddTouchRegion(mInfo);
float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
- MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
- mTouchTransformer.transform(inRegion);
- assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+ tapAndAssertTrue(100, y,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
}
@Test
public void applyTransform_taskNotFrozen_90Rotate_inRegion() {
mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
float y = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
- MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
- mTouchTransformer.transform(inRegion);
- assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+ tapAndAssertTrue(100, y,
+ event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
}
@Test
@@ -160,15 +219,15 @@
MotionEvent inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 10);
mTouchTransformer.transform(inRegion1_down);
mTouchTransformer.transform(inRegion2);
- assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion1_down.getX(), inRegion1_down.getY()));
+ assertTrue(mTouchTransformer.touchInValidSwipeRegions(
+ inRegion1_down.getX(), inRegion1_down.getY()));
// We only process one gesture region until we see a MotionEvent.ACTION_UP
assertFalse(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
mTouchTransformer.transform(inRegion1_up);
// Set the new region with this MotionEvent.ACTION_DOWN
- inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 370);
- mTouchTransformer.transform(inRegion2);
+ inRegion2 = generateAndTransformMotionEvent(MotionEvent.ACTION_DOWN, 10, 370);
assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
}
@@ -191,4 +250,26 @@
private MotionEvent generateMotionEvent(int motionAction, float x, float y) {
return MotionEvent.obtain(0, 0, motionAction, x, y, 0);
}
+
+ private MotionEvent generateAndTransformMotionEvent(int motionAction, float x, float y) {
+ MotionEvent motionEvent = generateMotionEvent(motionAction, x, y);
+ mTouchTransformer.transform(motionEvent);
+ return motionEvent;
+ }
+
+ private void tapAndAssertTrue(float x, float y, MotionEventAssertion assertion) {
+ MotionEvent motionEvent = generateAndTransformMotionEvent(MotionEvent.ACTION_DOWN, x, y);
+ assertTrue(assertion.getCondition(motionEvent));
+ generateAndTransformMotionEvent(MotionEvent.ACTION_UP, x, y);
+ }
+
+ private void tapAndAssertFalse(float x, float y, MotionEventAssertion assertion) {
+ MotionEvent motionEvent = generateAndTransformMotionEvent(MotionEvent.ACTION_DOWN, x, y);
+ assertFalse(assertion.getCondition(motionEvent));
+ generateAndTransformMotionEvent(MotionEvent.ACTION_UP, x, y);
+ }
+
+ private interface MotionEventAssertion {
+ boolean getCondition(MotionEvent motionEvent);
+ }
}
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
new file mode 100644
index 0000000..93b64e6
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.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)
+public class RecentsActivityTest {
+
+ @Test
+ public void testRecentsActivityCreates() {
+ ActivityController<RecentsActivity> controller =
+ Robolectric.buildActivity(RecentsActivity.class);
+
+ RecentsActivity launcher = controller.setup().get();
+ doLayout(launcher);
+
+ // TODO: Ensure that LauncherAppState is not created
+ }
+
+ @Test
+ public void testRecets_showCurrentTask() {
+ ActivityController<RecentsActivity> controller =
+ Robolectric.buildActivity(RecentsActivity.class);
+
+ RecentsActivity activity = controller.setup().get();
+ doLayout(activity);
+
+ FallbackRecentsView frv = activity.getOverviewPanel();
+ frv.showCurrentTask(22);
+ 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/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
new file mode 100644
index 0000000..a31ba21
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.util;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.shadows.LShadowDisplay;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.LauncherActivityInterface;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowDisplayManager;
+
+@RunWith(RobolectricTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class TaskViewSimulatorTest {
+
+ @Test
+ public void taskProperlyScaled_portrait_noRotation_sameInsets1() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(1200, 2450)
+ .withInsets(new Rect(0, 80, 0, 120))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_portrait_noRotation_sameInsets2() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(1200, 2450)
+ .withInsets(new Rect(55, 80, 55, 120))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_noRotation_sameInsets1() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(2450, 1250)
+ .withInsets(new Rect(0, 80, 0, 40))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_noRotation_sameInsets2() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(2450, 1250)
+ .withInsets(new Rect(0, 80, 120, 0))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_noRotation_sameInsets3() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(2450, 1250)
+ .withInsets(new Rect(55, 80, 55, 120))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_rotated() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(1200, 2450)
+ .withInsets(new Rect(0, 80, 0, 120))
+ .withAppBounds(
+ new Rect(0, 0, 2450, 1200),
+ new Rect(0, 80, 0, 120),
+ Surface.ROTATION_90)
+ .verifyNoTransforms();
+ }
+
+ private static class TaskMatrixVerifier extends TransformParams {
+
+ private final Context mContext = RuntimeEnvironment.application;
+
+ private Rect mAppBounds = new Rect();
+ private Rect mLauncherInsets = new Rect();
+
+ private Rect mAppInsets;
+
+ private int mAppRotation = -1;
+ private DeviceProfile mDeviceProfile;
+
+ TaskMatrixVerifier withLauncherSize(int width, int height) {
+ ShadowDisplayManager.changeDisplay(DEFAULT_DISPLAY,
+ String.format("w%sdp-h%sdp-mdpi", width, height));
+ if (mAppBounds.isEmpty()) {
+ mAppBounds.set(0, 0, width, height);
+ }
+ return this;
+ }
+
+ TaskMatrixVerifier withInsets(Rect insets) {
+ LShadowDisplay shadowDisplay = Shadow.extract(
+ mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
+ shadowDisplay.setInsets(insets);
+ mLauncherInsets.set(insets);
+ return this;
+ }
+
+ TaskMatrixVerifier withAppBounds(Rect bounds, Rect insets, int appRotation) {
+ mAppBounds.set(bounds);
+ mAppInsets = insets;
+ mAppRotation = appRotation;
+ return this;
+ }
+
+ void verifyNoTransforms() {
+ mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext)
+ .getDeviceProfile(mContext);
+ mDeviceProfile.updateInsets(mLauncherInsets);
+
+ TaskViewSimulator tvs = new TaskViewSimulator(mContext,
+ LauncherActivityInterface.INSTANCE);
+ tvs.setDp(mDeviceProfile);
+
+ int launcherRotation = DefaultDisplay.INSTANCE.get(mContext).getInfo().rotation;
+ if (mAppRotation < 0) {
+ mAppRotation = launcherRotation;
+ }
+ tvs.setLayoutRotation(launcherRotation, mAppRotation);
+ if (mAppInsets == null) {
+ mAppInsets = new Rect(mLauncherInsets);
+ }
+ tvs.setPreviewBounds(mAppBounds, mAppInsets);
+
+ tvs.fullScreenProgress.value = 1;
+ tvs.recentsViewScale.value = tvs.getFullScreenScale();
+ tvs.apply(this);
+ }
+
+ @Override
+ public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
+ SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
+ proxy.onBuildTargetParams(builder, null, this);
+ return new SurfaceParams[] {builder.build()};
+ }
+
+ @Override
+ public void applySurfaceParams(SurfaceParams[] params) {
+ // Verify that the task position remains the same
+ RectF newAppBounds = new RectF(mAppBounds);
+ params[0].matrix.mapRect(newAppBounds);
+ Assert.assertThat(newAppBounds, new AlmostSame(mAppBounds));
+
+ System.err.println("Bounds mapped: " + mAppBounds + " => " + newAppBounds);
+ }
+ }
+
+ private static class AlmostSame extends TypeSafeMatcher<RectF> {
+
+ // Allow 1px error margin to account for float to int conversions
+ private final float mError = 1f;
+ private final Rect mExpected;
+
+ AlmostSame(Rect expected) {
+ mExpected = expected;
+ }
+
+ @Override
+ protected boolean matchesSafely(RectF item) {
+ return Math.abs(item.left - mExpected.left) < mError
+ && Math.abs(item.top - mExpected.top) < mError
+ && Math.abs(item.right - mExpected.right) < mError
+ && Math.abs(item.bottom - mExpected.bottom) < mError;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(mExpected);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index abdff0d..47ce320 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -17,14 +17,8 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
-import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT;
-import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT;
-import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
-import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
-import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
@@ -32,31 +26,32 @@
import android.animation.ValueAnimator;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.view.View;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.accessibility.SystemActions;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.proxy.ProxyActivityStarter;
import com.android.launcher3.proxy.StartActivityParams;
import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.uioverrides.RecentsViewStateController;
+import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.UiThreadHelper;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.QuickstepOnboardingPrefs;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -70,7 +65,6 @@
implements NavigationModeChangeListener {
private DepthController mDepthController = new DepthController(this);
- protected SystemActions mSystemActions;
/**
* Reusable command for applying the back button alpha on the background thread.
@@ -81,53 +75,15 @@
private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
- private View mActionsView;
+ private OverviewActionsView mActionsView;
+ protected HotseatPredictionController mHotseatPredictionController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mSystemActions = new SystemActions(this);
SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
-
- if (!getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
- getStateManager().addStateListener(new LauncherStateManager.StateListener() {
- @Override
- public void onStateTransitionStart(LauncherState toState) { }
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- boolean swipeUpEnabled = SysUINavigationMode.INSTANCE
- .get(BaseQuickstepLauncher.this).getMode().hasGestures;
- LauncherState prevState = getStateManager().getLastState();
-
- if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
- && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT
- <= getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) {
- getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
- getStateManager().removeStateListener(this);
- }
- }
- });
- }
-
- if (!getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) {
- getStateManager().addStateListener(new LauncherStateManager.StateListener() {
- @Override
- public void onStateTransitionStart(LauncherState toState) { }
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- LauncherState prevState = getStateManager().getLastState();
-
- if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT
- <= getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) {
- getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply();
- getStateManager().removeStateListener(this);
- }
- }
- });
- }
+ addMultiWindowModeChangedListener(mDepthController);
}
@Override
@@ -139,12 +95,9 @@
@Override
public void onNavigationModeChanged(Mode newMode) {
getDragLayer().recreateControllers();
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- mSystemActions.onActivityResult(requestCode);
+ if (mActionsView != null && isOverviewActionsEnabled()) {
+ mActionsView.updateVerticalMargin(newMode);
+ }
}
@Override
@@ -203,6 +156,7 @@
@Override
protected void onDeferredResumed() {
+ super.onDeferredResumed();
if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
// Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
@@ -210,34 +164,30 @@
// removes the task itself.
startActivity(ProxyActivityStarter.getLaunchIntent(this, null));
}
-
- // Register all system actions once they are available
- mSystemActions.register();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mSystemActions.unregister();
}
@Override
protected void setupViews() {
super.setupViews();
+
+ SysUINavigationMode.INSTANCE.get(this).updateMode();
mActionsView = findViewById(R.id.overview_actions_view);
+ ((RecentsView) getOverviewPanel()).init(mActionsView);
-
- if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this)) {
+ if (isOverviewActionsEnabled()) {
// Overview is above all other launcher elements, including qsb, so move it to the top.
getOverviewPanel().bringToFront();
- if (mActionsView != null) {
- mActionsView.bringToFront();
- }
+ mActionsView.bringToFront();
+ mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
}
}
- public View getActionsView() {
- return mActionsView;
+ private boolean isOverviewActionsEnabled() {
+ return FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this);
+ }
+
+ public <T extends OverviewActionsView> T getActionsView() {
+ return (T) mActionsView;
}
@Override
@@ -248,7 +198,7 @@
}
@Override
- protected StateHandler[] createStateHandlers() {
+ protected StateHandler<LauncherState>[] createStateHandlers() {
return new StateHandler[] {
getAllAppsController(),
getWorkspace(),
@@ -262,14 +212,8 @@
}
@Override
- protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
- if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) {
- PagedOrientationHandler layoutVertical =
- ((RecentsView)getOverviewPanel()).getPagedViewOrientedState().getOrientationHandler();
- return layoutVertical.getScaleAndTranslation(getDeviceProfile(),
- getOverviewPanel());
- }
- return super.getOverviewScaleAndTranslationForNormalState();
+ protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
+ return new QuickstepOnboardingPrefs(this, sharedPrefs);
}
@Override
@@ -295,6 +239,12 @@
}
@Override
+ public float[] getNormalOverviewScaleAndOffset() {
+ return SysUINavigationMode.getMode(this) == Mode.NO_BUTTON
+ ? new float[] {1, 1} : new float[] {1.1f, 0};
+ }
+
+ @Override
public void onDragLayerHierarchyChanged() {
onLauncherStateOrFocusChanged();
}
@@ -313,13 +263,10 @@
super.onActivityFlagsChanged(changeBits);
}
- /**
- * Sets the back button visibility based on the current state/window focus.
- */
- private void onLauncherStateOrFocusChanged() {
+ public boolean shouldBackButtonBeHidden(LauncherState toState) {
Mode mode = SysUINavigationMode.getMode(this);
boolean shouldBackButtonBeHidden = mode.hasGestures
- && getStateManager().getState().hideBackButton
+ && toState.hasFlag(FLAG_HIDE_BACK_BUTTON)
&& hasWindowFocus()
&& (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0;
if (shouldBackButtonBeHidden) {
@@ -327,6 +274,14 @@
shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(this,
TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
}
+ return shouldBackButtonBeHidden;
+ }
+
+ /**
+ * Sets the back button visibility based on the current state/window focus.
+ */
+ private void onLauncherStateOrFocusChanged() {
+ boolean shouldBackButtonBeHidden = shouldBackButtonBeHidden(getStateManager().getState());
UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA,
shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
if (getDragLayer() != null) {
@@ -352,4 +307,15 @@
public ShelfPeekAnim getShelfPeekAnim() {
return mShelfPeekAnim;
}
+
+ /**
+ * Returns Prediction controller for hybrid hotseat
+ */
+ public HotseatPredictionController getHotseatPredictionController() {
+ return mHotseatPredictionController;
+ }
+
+ public void setHintUserWillBeActive() {
+ addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 4ff0a72..02769c6 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -37,6 +37,8 @@
public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat,
WrappedAnimationRunnerImpl {
+ private static final String TAG = "LauncherAnimationRunner";
+
private final Handler mHandler;
private final boolean mStartAtFrontOfQueue;
private AnimationResult mAnimationResult;
@@ -151,7 +153,8 @@
// Because t=0 has the app icon in its original spot, we can skip the
// first frame and have the same movement one frame earlier.
- mAnimator.setCurrentPlayTime(getSingleFrameMs(context));
+ mAnimator.setCurrentPlayTime(
+ Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration()));
}
}
}
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index fbd7a8a..7fb0d43 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -23,7 +23,6 @@
import android.os.CancellationSignal;
import android.os.Handler;
-import com.android.launcher3.util.ActivityTracker;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -45,7 +44,7 @@
}
@Override
- public boolean init(Launcher launcher, boolean alreadyOnHome) {
+ public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
if (mRemoteAnimationProvider != null) {
QuickstepAppTransitionManagerImpl appTransitionManager =
(QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
@@ -71,7 +70,7 @@
}, cancellationSignal);
}
launcher.deferOverlayCallbacksUntilNextResumeOrStop();
- return super.init(launcher, alreadyOnHome);
+ return super.handleInit(launcher, alreadyOnHome);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index a30e102..10f789d 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -32,6 +32,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.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.statehandlers.DepthController.DEPTH;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -81,6 +82,7 @@
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.QuickStepContract;
@@ -88,7 +90,6 @@
import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -291,6 +292,15 @@
launcherContentAnimator.second.run();
}
});
+ } else {
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mLauncher.addOnResumeCallback(() ->
+ ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH,
+ mLauncher.getStateManager().getState().getDepth(mLauncher)).start());
+ }
+ });
}
}
@@ -445,9 +455,9 @@
RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
wallpaperTargets, MODE_OPENING);
- SyncRtSurfaceTransactionApplierCompat surfaceApplier =
- new SyncRtSurfaceTransactionApplierCompat(floatingView);
- openingTargets.addDependentTransactionApplier(surfaceApplier);
+ SurfaceTransactionApplier surfaceApplier =
+ new SurfaceTransactionApplier(floatingView);
+ openingTargets.addReleaseCheck(surfaceApplier);
// Scale the app icon to take up the entire screen. This simplifies the math when
// animating the app window position / scale.
@@ -484,7 +494,7 @@
: APP_LAUNCH_ALPHA_DOWN_DURATION;
RectF targetBounds = new RectF(windowTargetBounds);
- RectF currentBounds = new RectF();
+ RectF iconBounds = new RectF();
RectF temp = new RectF();
Point tmpPos = new Point();
@@ -522,7 +532,7 @@
appAnimator.addUpdateListener(new MultiValueUpdateListener() {
FloatProp mDx = new FloatProp(0, dX, 0, xDuration, AGGRESSIVE_EASE);
FloatProp mDy = new FloatProp(0, dY, 0, yDuration, AGGRESSIVE_EASE);
- FloatProp mIconScale = new FloatProp(initialStartScale, scale, 0, APP_LAUNCH_DURATION,
+ FloatProp mScale = new FloatProp(initialStartScale, scale, 0, APP_LAUNCH_DURATION,
EXAGGERATED_EASE);
FloatProp mIconAlpha = new FloatProp(1f, 0f, APP_LAUNCH_ALPHA_START_DELAY,
alphaDuration, LINEAR);
@@ -533,40 +543,48 @@
@Override
public void onUpdate(float percent) {
- // Calculate app icon size.
- float iconWidth = bounds.width() * mIconScale.value;
- float iconHeight = bounds.height() * mIconScale.value;
+ // Calculate the size.
+ float width = bounds.width() * mScale.value;
+ float height = bounds.height() * mScale.value;
- // Animate the window crop so that it starts off as a square.
- final int windowWidth;
- final int windowHeight;
+ // Animate the crop so that it starts off as a square.
+ final int cropWidth;
+ final int cropHeight;
if (mDeviceProfile.isVerticalBarLayout()) {
- windowWidth = (int) mCroppedSize.value;
- windowHeight = windowTargetBounds.height();
+ cropWidth = (int) mCroppedSize.value;
+ cropHeight = windowTargetBounds.height();
} else {
- windowWidth = windowTargetBounds.width();
- windowHeight = (int) mCroppedSize.value;
+ cropWidth = windowTargetBounds.width();
+ cropHeight = (int) mCroppedSize.value;
}
- crop.set(0, 0, windowWidth, windowHeight);
+ crop.set(0, 0, cropWidth, cropHeight);
- // Scale the app window to match the icon size.
- float scaleX = iconWidth / windowWidth;
- float scaleY = iconHeight / windowHeight;
+ // Scale the size to match the crop.
+ float scaleX = width / cropWidth;
+ float scaleY = height / cropHeight;
float scale = Math.min(1f, Math.max(scaleX, scaleY));
- float scaledWindowWidth = windowWidth * scale;
- float scaledWindowHeight = windowHeight * scale;
+ float scaledCropWidth = cropWidth * scale;
+ float scaledCropHeight = cropHeight * scale;
+ float offsetX = (scaledCropWidth - width) / 2;
+ float offsetY = (scaledCropHeight - height) / 2;
- float offsetX = (scaledWindowWidth - iconWidth) / 2;
- float offsetY = (scaledWindowHeight - iconHeight) / 2;
-
- // Calculate the window position
+ // Calculate the window position.
temp.set(bounds);
temp.offset(dragLayerBounds[0], dragLayerBounds[1]);
temp.offset(mDx.value, mDy.value);
- Utilities.scaleRectFAboutCenter(temp, mIconScale.value);
- float transX0 = temp.left - offsetX;
- float transY0 = temp.top - offsetY;
+ Utilities.scaleRectFAboutCenter(temp, mScale.value);
+ float windowTransX0 = temp.left - offsetX;
+ float windowTransY0 = temp.top - offsetY;
+
+ // Calculate the icon position.
+ iconBounds.set(bounds);
+ iconBounds.offset(mDx.value, mDy.value);
+ Utilities.scaleRectFAboutCenter(iconBounds, mScale.value);
+ iconBounds.left -= offsetX;
+ iconBounds.top -= offsetY;
+ iconBounds.right += offsetX;
+ iconBounds.bottom += offsetY;
float croppedHeight = (windowTargetBounds.height() - crop.height()) * scale;
float croppedWidth = (windowTargetBounds.width() - crop.width()) * scale;
@@ -575,34 +593,30 @@
RemoteAnimationTargetCompat target = appTargets[i];
SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
- tmpPos.set(target.position.x, target.position.y);
- if (target.localBounds != null) {
- final Rect localBounds = target.localBounds;
- tmpPos.set(target.localBounds.left, target.localBounds.top);
- }
-
if (target.mode == MODE_OPENING) {
matrix.setScale(scale, scale);
- matrix.postTranslate(transX0, transY0);
- matrix.mapRect(currentBounds, targetBounds);
- if (mDeviceProfile.isVerticalBarLayout()) {
- currentBounds.right -= croppedWidth;
- } else {
- currentBounds.bottom -= croppedHeight;
- }
- floatingView.update(currentBounds, mIconAlpha.value, percent, 0f,
+ matrix.postTranslate(windowTransX0, windowTransY0);
+
+ floatingView.update(iconBounds, mIconAlpha.value, percent, 0f,
mWindowRadius.value * scale, true /* isOpening */);
builder.withMatrix(matrix)
.withWindowCrop(crop)
.withAlpha(1f - mIconAlpha.value)
.withCornerRadius(mWindowRadius.value);
} else {
+ tmpPos.set(target.position.x, target.position.y);
+ if (target.localBounds != null) {
+ final Rect localBounds = target.localBounds;
+ tmpPos.set(target.localBounds.left, target.localBounds.top);
+ }
+
matrix.setTranslate(tmpPos.x, tmpPos.y);
+ final Rect crop = new Rect(target.screenSpaceBounds);
+ crop.offsetTo(0, 0);
builder.withMatrix(matrix)
- .withWindowCrop(target.screenSpaceBounds)
+ .withWindowCrop(crop)
.withAlpha(1f);
}
- builder.withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING));
params[i] = builder.build();
}
surfaceApplier.scheduleApply(params);
@@ -636,6 +650,9 @@
*/
@Override
public void registerRemoteAnimations() {
+ if (SEPARATE_RECENTS_ACTIVITY.get()) {
+ return;
+ }
if (hasControlRemoteAppTransitionPermission()) {
mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */);
@@ -666,6 +683,9 @@
*/
@Override
public void unregisterRemoteAnimations() {
+ if (SEPARATE_RECENTS_ACTIVITY.get()) {
+ return;
+ }
if (hasControlRemoteAppTransitionPermission()) {
new ActivityCompat(mLauncher).unregisterRemoteAnimations();
@@ -694,8 +714,7 @@
*/
private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets) {
- SyncRtSurfaceTransactionApplierCompat surfaceApplier =
- new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
+ SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
@@ -709,7 +728,6 @@
params[i] = new SurfaceParams.Builder(target.leash)
.withAlpha(1f)
.withWindowCrop(target.screenSpaceBounds)
- .withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING))
.withCornerRadius(cornerRadius)
.build();
}
@@ -724,8 +742,7 @@
*/
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets) {
- SyncRtSurfaceTransactionApplierCompat surfaceApplier =
- new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
+ SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
Matrix matrix = new Matrix();
Point tmpPos = new Point();
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
@@ -764,9 +781,10 @@
builder.withMatrix(matrix)
.withAlpha(1f);
}
+ final Rect crop = new Rect(target.screenSpaceBounds);
+ crop.offsetTo(0, 0);
params[i] = builder
- .withWindowCrop(target.screenSpaceBounds)
- .withLayer(RemoteAnimationProvider.getLayer(target, MODE_CLOSING))
+ .withWindowCrop(crop)
.build();
}
surfaceApplier.scheduleApply(params);
diff --git a/quickstep/src/com/android/launcher3/accessibility/SystemActions.java b/quickstep/src/com/android/launcher3/accessibility/SystemActions.java
deleted file mode 100644
index 669877f..0000000
--- a/quickstep/src/com/android/launcher3/accessibility/SystemActions.java
+++ /dev/null
@@ -1,90 +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.accessibility;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-
-import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.drawable.Icon;
-import android.view.accessibility.AccessibilityManager;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.R;
-
-/**
- * Manages the launcher system actions presented to accessibility services.
- */
-public class SystemActions {
-
- /**
- * System Action ID to show all apps. This ID should follow the ones in
- * com.android.systemui.accessibility.SystemActions.
- */
- private static final int SYSTEM_ACTION_ID_ALL_APPS = 100;
-
- private Launcher mLauncher;
- private AccessibilityManager mAccessibilityManager;
- private RemoteAction mAllAppsAction;
- private boolean mRegistered;
-
- public SystemActions(Launcher launcher) {
- mLauncher = launcher;
- mAccessibilityManager = (AccessibilityManager) launcher.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- mAllAppsAction = new RemoteAction(
- Icon.createWithResource(launcher, R.drawable.ic_apps),
- launcher.getString(R.string.all_apps_label),
- launcher.getString(R.string.all_apps_label),
- launcher.createPendingResult(SYSTEM_ACTION_ID_ALL_APPS, new Intent(),
- 0 /* flags */));
- }
-
- public void register() {
- if (mRegistered) {
- return;
- }
- mAccessibilityManager.registerSystemAction(mAllAppsAction, SYSTEM_ACTION_ID_ALL_APPS);
- mRegistered = true;
- }
-
- public void unregister() {
- if (!mRegistered) {
- return;
- }
- mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
- mRegistered = false;
- }
-
- public void onActivityResult(int requestCode) {
- if (requestCode == SYSTEM_ACTION_ID_ALL_APPS) {
- showAllApps();
- }
- }
-
- private void showAllApps() {
- LauncherStateManager stateManager = mLauncher.getStateManager();
- stateManager.goToState(NORMAL);
- stateManager.goToState(ALL_APPS);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 92c8573..f42b124 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -45,8 +45,8 @@
import androidx.annotation.MainThread;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.RemoteActionShortcut;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.MainThreadInitializedObject;
@@ -77,6 +77,7 @@
private static final String EXTRA_ACTION = "action";
private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
private static final String EXTRA_PACKAGES = "packages";
+ private static final String EXTRA_SUCCESS = "success";
public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
new MainThreadInitializedObject<>(WellbeingModel::new);
@@ -221,6 +222,7 @@
params.putInt(EXTRA_MAX_NUM_ACTIONS_SHOWN, 1);
// Perform wellbeing call .
remoteActionBundle = client.call(METHOD_GET_ACTIONS, null, params);
+ if (!remoteActionBundle.getBoolean(EXTRA_SUCCESS, true)) return false;
synchronized (mModelLock) {
// Remove the entries for requested packages, and then update the fist with what we
@@ -281,9 +283,9 @@
// Remove all existing messages
mWorkerHandler.removeCallbacksAndMessages(null);
final String[] packageNames = mContext.getSystemService(LauncherApps.class)
- .getActivityList(null, Process.myUserHandle()).stream()
- .map(li -> li.getApplicationInfo().packageName).distinct()
- .toArray(String[]::new);
+ .getActivityList(null, Process.myUserHandle()).stream()
+ .map(li -> li.getApplicationInfo().packageName).distinct()
+ .toArray(String[]::new);
if (!updateActions(packageNames)) {
scheduleRefreshRetry(msg);
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
index 983702a..13501a4 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
@@ -21,8 +21,8 @@
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.UiThreadHelper;
import com.android.quickstep.AnimatedFloat;
@@ -32,7 +32,7 @@
/**
* State handler for animating back button alpha
*/
-public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler {
+public class BackButtonAlphaHandler implements StateHandler<LauncherState> {
private final BaseQuickstepLauncher mLauncher;
private final AnimatedFloat mBackAlpha = new AnimatedFloat(this::updateBackAlpha);
@@ -59,7 +59,8 @@
}
mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastBackButtonAlpha();
- animation.setFloat(mBackAlpha, VALUE, toState.hideBackButton ? 0 : 1, LINEAR);
+ animation.setFloat(mBackAlpha, VALUE,
+ mLauncher.shouldBackButtonBeHidden(toState) ? 0 : 1, LINEAR);
}
private void updateBackAlpha() {
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 24ba89a..fe8f0c6 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -17,19 +17,26 @@
package com.android.launcher3.statehandlers;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.os.IBinder;
import android.util.FloatProperty;
import android.view.View;
import android.view.ViewTreeObserver;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.systemui.shared.system.BlurUtils;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SurfaceControlCompat;
import com.android.systemui.shared.system.TransactionCompat;
@@ -38,7 +45,8 @@
/**
* Controls blur and wallpaper zoom, for the Launcher surface only.
*/
-public class DepthController implements LauncherStateManager.StateHandler {
+public class DepthController implements StateHandler<LauncherState>,
+ BaseActivity.MultiWindowModeChangedListener {
public static final FloatProperty<DepthController> DEPTH =
new FloatProperty<DepthController>("depth") {
@@ -101,16 +109,37 @@
*/
private float mDepth;
+ // Workaround for animating the depth when multiwindow mode changes.
+ private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false;
+
+ private View.OnAttachStateChangeListener mOnAttachListener;
+
public DepthController(Launcher l) {
mLauncher = l;
}
private void ensureDependencies() {
- if (mWallpaperManager != null) {
- return;
+ if (mWallpaperManager == null) {
+ mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
+ mWallpaperManager = new WallpaperManagerCompat(mLauncher);
}
- mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
- mWallpaperManager = new WallpaperManagerCompat(mLauncher);
+ if (mLauncher.getRootView() != null && mOnAttachListener == null) {
+ mOnAttachListener = new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ // To handle the case where window token is invalid during last setDepth call.
+ IBinder windowToken = mLauncher.getRootView().getWindowToken();
+ if (windowToken != null) {
+ mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
+ }
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ }
+ };
+ mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
+ }
}
/**
@@ -150,7 +179,7 @@
@Override
public void setState(LauncherState toState) {
- if (mSurface == null) {
+ if (mSurface == null || mIgnoreStateChangesDuringMultiWindowAnimation) {
return;
}
@@ -163,28 +192,69 @@
@Override
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
PendingAnimation animation) {
- if (mSurface == null || config.onlyPlayAtomicComponent()) {
+ if (mSurface == null
+ || config.onlyPlayAtomicComponent()
+ || config.hasAnimationFlag(SKIP_DEPTH_CONTROLLER)
+ || mIgnoreStateChangesDuringMultiWindowAnimation) {
return;
}
float toDepth = toState.getDepth(mLauncher);
if (Float.compare(mDepth, toDepth) != 0) {
- animation.setFloat(this, DEPTH, toDepth, LINEAR);
+ animation.setFloat(this, DEPTH, toDepth, config.getInterpolator(ANIM_DEPTH, LINEAR));
}
}
private void setDepth(float depth) {
- mDepth = depth;
- if (mSurface == null || !mSurface.isValid()) {
+ depth = Utilities.boundToRange(depth, 0, 1);
+ // Round out the depth to dedupe frequent, non-perceptable updates
+ int depthI = (int) (depth * 256);
+ float depthF = depthI / 256f;
+ if (Float.compare(mDepth, depthF) == 0) {
return;
}
+
+ boolean supportsBlur = BlurUtils.supportsBlursOnWindows();
+ if (supportsBlur && (mSurface == null || !mSurface.isValid())) {
+ return;
+ }
+ mDepth = depthF;
ensureDependencies();
IBinder windowToken = mLauncher.getRootView().getWindowToken();
if (windowToken != null) {
mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
}
- new TransactionCompat()
- .setBackgroundBlurRadius(mSurface, (int) (mDepth * mMaxBlurRadius))
- .apply();
+
+ if (supportsBlur) {
+ final int blur;
+ if (mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) {
+ // All apps has a solid background. We don't need to draw blurs after it's fully
+ // visible. This will take us out of GPU composition, saving battery and increasing
+ // performance.
+ blur = 0;
+ } else {
+ blur = (int) (mDepth * mMaxBlurRadius);
+ }
+ new TransactionCompat()
+ .setBackgroundBlurRadius(mSurface, blur)
+ .apply();
+ }
+ }
+
+ @Override
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ mIgnoreStateChangesDuringMultiWindowAnimation = true;
+
+ ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, DEPTH,
+ mLauncher.getStateManager().getState().getDepth(mLauncher, isInMultiWindowMode))
+ .setDuration(300);
+ mwAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIgnoreStateChangesDuringMultiWindowAnimation = false;
+ }
+ });
+ mwAnimation.setAutoCancel(true);
+ mwAnimation.start();
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 123c988..ec3a490 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -17,32 +17,30 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import android.util.FloatProperty;
-import android.view.View;
import androidx.annotation.NonNull;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.RecentsView;
/**
* State handler for recents view. Manages UI changes and animations for recents view based off the
@@ -50,8 +48,8 @@
*
* @param <T> the recents view
*/
-public abstract class BaseRecentsViewStateController<T extends View>
- implements StateHandler {
+public abstract class BaseRecentsViewStateController<T extends RecentsView>
+ implements StateHandler<LauncherState> {
protected final T mRecentsView;
protected final BaseQuickstepLauncher mLauncher;
@@ -62,15 +60,14 @@
@Override
public void setState(@NonNull LauncherState state) {
- ScaleAndTranslation scaleAndTranslation = state
- .getOverviewScaleAndTranslation(mLauncher);
- SCALE_PROPERTY.set(mRecentsView, scaleAndTranslation.scale);
- mRecentsView.setTranslationX(scaleAndTranslation.translationX);
- mRecentsView.setTranslationY(scaleAndTranslation.translationY);
+ float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher);
+ SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
+ ADJACENT_PAGE_OFFSET.set(mRecentsView, scaleAndOffset[1]);
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
+ getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
}
@Override
@@ -95,21 +92,26 @@
*/
void setStateWithAnimationInternal(@NonNull final LauncherState toState,
@NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) {
- ScaleAndTranslation scaleAndTranslation = toState.getOverviewScaleAndTranslation(mLauncher);
- setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndTranslation.scale,
+ float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
+ setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0],
config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
- setter.setFloat(mRecentsView, VIEW_TRANSLATE_X, scaleAndTranslation.translationX,
+ setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
- setter.setFloat(mRecentsView, VIEW_TRANSLATE_Y, scaleAndTranslation.translationY,
- config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
+
+ setter.setFloat(
+ mRecentsView, getTaskModalnessProperty(),
+ toState.getOverviewModalness(),
+ config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
}
+ abstract FloatProperty getTaskModalnessProperty();
+
/**
* Get property for content alpha for the recents view.
*
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
deleted file mode 100644
index c7cce0b..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
+++ /dev/null
@@ -1,48 +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.uioverrides;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.Size;
-import android.view.View;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.graphics.LauncherPreviewRenderer;
-import com.android.systemui.shared.system.SurfaceViewRequestReceiver;
-
-/** Render preview using surface view. */
-public class PreviewSurfaceRenderer {
-
- /** Handle a received surface view request. */
- public static void render(Context context, Bundle bundle) {
- String gridName = bundle.getString("name");
- bundle.remove("name");
- if (gridName == null) {
- gridName = InvariantDeviceProfile.getCurrentGridName(context);
- }
- final InvariantDeviceProfile idp = new InvariantDeviceProfile(context, gridName);
-
- MAIN_EXECUTOR.execute(() -> {
- View view = new LauncherPreviewRenderer(context, idp).getRenderedView();
- new SurfaceViewRequestReceiver().onReceive(context, bundle, view,
- new Size(view.getMeasuredWidth(), view.getMeasuredHeight()));
- });
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 93e02a1..e7cd393 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -16,10 +16,11 @@
package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import android.content.Context;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.allapps.AllAppsContainerView;
@@ -31,7 +32,7 @@
*/
public class AllAppsState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
+ private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_CLOSE_POPUPS;
private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
@Override
@@ -45,23 +46,11 @@
}
@Override
- public int getTransitionDuration(Launcher launcher) {
+ public int getTransitionDuration(Context context) {
return 320;
}
@Override
- public void onStateEnabled(Launcher launcher) {
- AbstractFloatingView.closeAllOpenViews(launcher);
- dispatchWindowStateChanged(launcher);
- }
-
- @Override
- public void onStateDisabled(Launcher launcher) {
- super.onStateDisabled(launcher);
- AbstractFloatingView.closeAllOpenViews(launcher);
- }
-
- @Override
public String getDescription(Launcher launcher) {
AllAppsContainerView appsView = launcher.getAppsView();
return appsView.getDescription();
@@ -76,7 +65,7 @@
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW
.getWorkspaceScaleAndTranslation(launcher);
- if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
+ if (SysUINavigationMode.getMode(launcher) == NO_BUTTON && !ENABLE_OVERVIEW_ACTIONS.get()) {
float normalScale = 1;
// Scale down halfway to where we'd be in overview, to prepare for a potential pause.
scaleAndTranslation.scale = (scaleAndTranslation.scale + normalScale) / 2;
@@ -87,7 +76,7 @@
}
@Override
- public float getDepth(Context context) {
+ protected float getDepthUnchecked(Context context) {
return 1f;
}
@@ -102,9 +91,8 @@
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- float slightParallax = -launcher.getDeviceProfile().allAppsCellHeightPx * 0.3f;
- return new ScaleAndTranslation(0.9f, 0f, slightParallax);
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return new float[] {0.9f, 0};
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index cc3fd97..20ee61d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -16,24 +16,28 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
+import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
@@ -41,6 +45,7 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.uioverrides.states.OverviewState;
@@ -49,6 +54,7 @@
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.views.RecentsView;
/**
* Touch controller for handling various state transitions in portrait UI.
@@ -119,7 +125,7 @@
return false;
}
}
- if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, TYPE_ACCESSIBLE) != null) {
+ if (getTopOpenViewWithType(mLauncher, TYPE_ACCESSIBLE | TYPE_ALL_APPS_EDU) != null) {
return false;
}
return true;
@@ -127,13 +133,33 @@
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "PortraitStatesTouchController.getTargetState");
+ }
if (fromState == ALL_APPS && !isDragTowardPositive) {
// Should swipe down go to OVERVIEW instead?
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
+ "PortraitStatesTouchController.getTargetState 1");
+ }
return TouchInteractionService.isConnected() ?
mLauncher.getStateManager().getLastState() : NORMAL;
} else if (fromState == OVERVIEW) {
- return isDragTowardPositive ? ALL_APPS : NORMAL;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
+ "PortraitStatesTouchController.getTargetState 2");
+ }
+ LauncherState positiveDragTarget = ALL_APPS;
+ if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(mLauncher)) {
+ // Don't allow swiping up to all apps.
+ positiveDragTarget = OVERVIEW;
+ }
+ return isDragTowardPositive ? positiveDragTarget : NORMAL;
} else if (fromState == NORMAL && isDragTowardPositive) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
+ "PortraitStatesTouchController.getTargetState 3");
+ }
int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
return mAllowDragToOverview && TouchInteractionService.isConnected()
&& (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0
@@ -237,8 +263,9 @@
mCurrentAnimation = mPendingAnimation.createPlaybackController()
.setOnCancelRunnable(onCancelRunnable);
mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
+ RecentsView recentsView = mLauncher.getOverviewPanel();
totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
- mLauncher.getDeviceProfile());
+ mLauncher.getDeviceProfile(), recentsView.getPagedOrientationHandler());
} else {
mCurrentAnimation = mLauncher.getStateManager()
.createAnimationToNewWorkspace(mToState, config)
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index c3b90e3..f7e8781 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -38,11 +38,17 @@
}
};
+ private static final Runnable NO_OP = () -> { };
+
private final Runnable mUpdateCallback;
private ObjectAnimator mValueAnimator;
public float value;
+ public AnimatedFloat() {
+ this(NO_OP);
+ }
+
public AnimatedFloat(Runnable updateCallback) {
mUpdateCallback = updateCallback;
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 94ef15a..2b698bd 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -15,27 +15,46 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.BaseSwipeUpHandlerV2.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.SysUINavigationMode.getMode;
+import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
+import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+
+import android.animation.Animator;
import android.annotation.TargetApi;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Build;
-import android.util.Pair;
import android.view.MotionEvent;
-import android.view.View;
import android.view.animation.Interpolator;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import com.android.launcher3.BaseDraggingActivity;
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.statehandlers.DepthController;
+import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.WindowBounds;
+import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.SplitScreenBounds;
+import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -46,97 +65,243 @@
* Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
*/
@TargetApi(Build.VERSION_CODES.P)
-public interface BaseActivityInterface<T extends BaseDraggingActivity> {
+public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
+ ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>> {
- void onTransitionCancelled(boolean activityVisible);
+ public final boolean rotationSupportedByActivity;
- int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect);
+ private final STATE_TYPE mOverviewState, mBackgroundState;
- /**
- * @return The progress of the swipe where we start resisting the user, where 0 is fullscreen
- * and 1 is recents. These values should probably be greater than 1 to let the user swipe past
- * recents before we start resisting them.
- */
- default Pair<Float, Float> getSwipeUpPullbackStartAndMaxProgress() {
- return new Pair<>(1.4f, 1.8f);
+ protected BaseActivityInterface(boolean rotationSupportedByActivity,
+ STATE_TYPE overviewState, STATE_TYPE backgroundState) {
+ this.rotationSupportedByActivity = rotationSupportedByActivity;
+ mOverviewState = overviewState;
+ mBackgroundState = backgroundState;
}
- void onSwipeUpToRecentsComplete();
+ public void onTransitionCancelled(boolean activityVisible) {
+ ACTIVITY_TYPE activity = getCreatedActivity();
+ if (activity == null) {
+ return;
+ }
+ STATE_TYPE startState = activity.getStateManager().getRestState();
+ activity.getStateManager().goToState(startState, activityVisible);
+ }
- default void onSwipeUpToHomeComplete() { }
- void onAssistantVisibilityChanged(float visibility);
+ public abstract int getSwipeUpDestinationAndLength(
+ DeviceProfile dp, Context context, Rect outRect,
+ PagedOrientationHandler orientationHandler);
- @NonNull HomeAnimationFactory prepareHomeUI();
+ public void onSwipeUpToRecentsComplete() {
+ // Re apply state in case we did something funky during the transition.
+ ACTIVITY_TYPE activity = getCreatedActivity();
+ if (activity == null) {
+ return;
+ }
+ activity.getStateManager().reapplyState();
+ }
- AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity,
- Consumer<AnimatorPlaybackController> callback);
+ public abstract void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState);
- ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener);
+ public abstract void onAssistantVisibilityChanged(float visibility);
+
+ public abstract AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
+ boolean activityVisible, Consumer<AnimatorPlaybackController> callback);
+
+ public abstract ActivityInitListener createActivityInitListener(
+ Predicate<Boolean> onInitListener);
/**
* Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
*/
- default void setOnDeferredActivityLaunchCallback(Runnable r) {}
+ public void setOnDeferredActivityLaunchCallback(Runnable r) {}
@Nullable
- T getCreatedActivity();
+ public abstract ACTIVITY_TYPE getCreatedActivity();
@Nullable
- default DepthController getDepthController() {
+ public DepthController getDepthController() {
return null;
}
- default boolean isResumed() {
- BaseDraggingActivity activity = getCreatedActivity();
+ public final boolean isResumed() {
+ ACTIVITY_TYPE activity = getCreatedActivity();
return activity != null && activity.hasBeenResumed();
}
+ public final boolean isStarted() {
+ ACTIVITY_TYPE activity = getCreatedActivity();
+ return activity != null && activity.isStarted();
+ }
+
@UiThread
@Nullable
- <T extends View> T getVisibleRecentsView();
+ public abstract <T extends RecentsView> T getVisibleRecentsView();
@UiThread
- boolean switchToRecentsIfVisible(Runnable onCompleteCallback);
+ public abstract boolean switchToRecentsIfVisible(Runnable onCompleteCallback);
- Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target);
+ public abstract Rect getOverviewWindowBounds(
+ Rect homeBounds, RemoteAnimationTargetCompat target);
- boolean shouldMinimizeSplitScreen();
+ public abstract boolean allowMinimizeSplitScreen();
- default boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
- return true;
+ public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+ return deviceState.isInDeferredGestureRegion(ev);
}
+ public abstract void onExitOverview(RecentsAnimationDeviceState deviceState,
+ Runnable exitRunnable);
+
/**
* Updates the prediction state to the overview state.
*/
- default void updateOverviewPredictionState() {
- // By default overview predictions are not supported
+ public void updateOverviewPredictionState() {
+ // By public overview predictions are not supported
}
/**
* Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
*/
- int getContainerType();
+ public abstract int getContainerType();
- boolean isInLiveTileMode();
+ public abstract boolean isInLiveTileMode();
- void onLaunchTaskFailed();
+ public abstract void onLaunchTaskFailed();
- void onLaunchTaskSuccess();
+ public void onLaunchTaskSuccess() {
+ ACTIVITY_TYPE activity = getCreatedActivity();
+ if (activity == null) {
+ return;
+ }
+ activity.getStateManager().moveToRestState();
+ }
- default void closeOverlay() { }
+ public void closeOverlay() { }
- default void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData,
- Runnable runnable) {}
+ public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, Runnable runnable) {
+ ACTIVITY_TYPE activity = getCreatedActivity();
+ if (activity == null) {
+ return;
+ }
+ RecentsView recentsView = activity.getOverviewPanel();
+ if (recentsView == null) {
+ if (runnable != null) {
+ runnable.run();
+ }
+ return;
+ }
+ recentsView.switchToScreenshot(thumbnailData, runnable);
+ }
- interface AnimationFactory {
+ /**
+ * Calculates the taskView size for the provided device configuration
+ */
+ public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
+ PagedOrientationHandler orientedState) {
+ calculateTaskSize(context, dp, getExtraSpace(context, dp, orientedState),
+ outRect, orientedState);
+ }
- default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { }
+ protected abstract float getExtraSpace(Context context, DeviceProfile dp,
+ PagedOrientationHandler orientedState);
+
+ private void calculateTaskSize(
+ Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect,
+ PagedOrientationHandler orientationHandler) {
+ Resources res = context.getResources();
+ final boolean showLargeTaskSize = showOverviewActions(context) ||
+ hideShelfInTwoButtonLandscape(context, orientationHandler);
+
+ final int paddingResId;
+ if (dp.isMultiWindowMode) {
+ paddingResId = R.dimen.multi_window_task_card_horz_space;
+ } else if (dp.isVerticalBarLayout()) {
+ paddingResId = R.dimen.landscape_task_card_horz_space;
+ } else if (showLargeTaskSize) {
+ paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
+ } else {
+ paddingResId = R.dimen.portrait_task_card_horz_space;
+ }
+ float paddingHorz = res.getDimension(paddingResId);
+ float paddingVert = showLargeTaskSize
+ ? 0 : res.getDimension(R.dimen.task_card_vert_space);
+
+ calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert,
+ res.getDimension(R.dimen.task_thumbnail_top_margin), outRect);
+ }
+
+ private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
+ float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin,
+ Rect outRect) {
+ float taskWidth, taskHeight;
+ Rect insets = dp.getInsets();
+ if (dp.isMultiWindowMode) {
+ WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
+ taskWidth = bounds.availableSize.x;
+ taskHeight = bounds.availableSize.y;
+ } else {
+ taskWidth = dp.availableWidthPx;
+ taskHeight = dp.availableHeightPx;
+ }
+
+ // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
+ // we override the insets ourselves.
+ int launcherVisibleWidth = dp.widthPx - insets.left - insets.right;
+ int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
+
+ float availableHeight = launcherVisibleHeight
+ - topIconMargin - extraVerticalSpace - paddingVert;
+ float availableWidth = launcherVisibleWidth - paddingHorz;
+
+ float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
+ float outWidth = scale * taskWidth;
+ float outHeight = scale * taskHeight;
+
+ // Center in the visible space
+ float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
+ float y = insets.top + Math.max(topIconMargin,
+ (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
+ outRect.set(Math.round(x), Math.round(y),
+ Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
+ }
+
+ /**
+ * Calculates the modal taskView size for the provided device configuration
+ */
+ public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
+ float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode
+ ? R.dimen.multi_window_task_card_horz_space
+ : dp.isVerticalBarLayout()
+ ? R.dimen.landscape_task_card_horz_space
+ : R.dimen.portrait_modal_task_card_horz_space);
+ float extraVerticalSpace = getOverviewActionsHeight(context);
+ float paddingVert = 0;
+ float topIconMargin = 0;
+ calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert,
+ topIconMargin, outRect);
+ }
+
+ /** Gets the space that the overview actions will take, including margins. */
+ public final float getOverviewActionsHeight(Context context) {
+ Resources res = context.getResources();
+ float actionsBottomMargin = 0;
+ if (getMode(context) == Mode.THREE_BUTTONS) {
+ actionsBottomMargin = res.getDimensionPixelSize(
+ R.dimen.overview_actions_bottom_margin_three_button);
+ } else {
+ actionsBottomMargin = res.getDimensionPixelSize(
+ R.dimen.overview_actions_bottom_margin_gesture);
+ }
+ float overviewActionsHeight = actionsBottomMargin
+ + res.getDimensionPixelSize(R.dimen.overview_actions_height);
+ return overviewActionsHeight;
+ }
+
+ public interface AnimationFactory {
void createActivityInterface(long transitionLength);
- default void adjustActivityControllerInterpolators() { }
-
default void onTransitionCancelled() { }
default void setShelfState(ShelfPeekAnim.ShelfAnimState animState,
@@ -150,34 +315,98 @@
default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
}
- interface HomeAnimationFactory {
+ class DefaultAnimationFactory implements AnimationFactory {
- /** Return the floating view that will animate in sync with the closing window. */
- default @Nullable View getFloatingView() {
- return null;
+ protected final ACTIVITY_TYPE mActivity;
+ private final STATE_TYPE mStartState;
+ private final Consumer<AnimatorPlaybackController> mCallback;
+
+ private boolean mIsAttachedToWindow;
+
+ DefaultAnimationFactory(Consumer<AnimatorPlaybackController> callback) {
+ mCallback = callback;
+
+ mActivity = getCreatedActivity();
+ mStartState = mActivity.getStateManager().getState();
}
- @NonNull RectF getWindowTargetRect();
-
- @NonNull AnimatorPlaybackController createActivityAnimationToHome();
-
- default void playAtomicAnimation(float velocity) {
- // No-op
+ protected ACTIVITY_TYPE initUI() {
+ STATE_TYPE resetState = mStartState;
+ if (mStartState.shouldDisableRestore()) {
+ resetState = mActivity.getStateManager().getRestState();
+ }
+ mActivity.getStateManager().setRestState(resetState);
+ mActivity.getStateManager().goToState(mBackgroundState, false);
+ return mActivity;
}
- static RectF getDefaultWindowTargetRect(PagedOrientationHandler orientationHandler,
- DeviceProfile dp) {
- 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;
- // Fallback to animate to center of screen.
- return new RectF(targetX - halfIconSize, targetY - halfIconSize,
- targetX + halfIconSize, targetY + halfIconSize);
+ @Override
+ public void createActivityInterface(long transitionLength) {
+ PendingAnimation pa = new PendingAnimation(transitionLength * 2);
+ createBackgroundToOverviewAnim(mActivity, pa);
+ AnimatorPlaybackController controller = pa.createPlaybackController();
+ mActivity.getStateManager().setCurrentUserControlledAnimation(controller);
+
+ // Since we are changing the start position of the UI, reapply the state, at the end
+ controller.setEndAction(() -> mActivity.getStateManager().goToState(
+ controller.getInterpolatedProgress() > 0.5 ? mOverviewState : mBackgroundState,
+ false));
+ mCallback.accept(controller);
+
+ // Creating the activity controller animation sometimes reapplies the launcher state
+ // (because we set the animation as the current state animation), so we reapply the
+ // attached state here as well to ensure recents is shown/hidden appropriately.
+ if (SysUINavigationMode.getMode(mActivity) == Mode.NO_BUTTON) {
+ setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
+ }
}
+ @Override
+ public void onTransitionCancelled() {
+ mActivity.getStateManager().goToState(mStartState, false /* animate */);
+ }
+
+ @Override
+ public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
+ if (mIsAttachedToWindow == attached && animate) {
+ return;
+ }
+ mIsAttachedToWindow = attached;
+ RecentsView recentsView = mActivity.getOverviewPanel();
+ Animator fadeAnim = mActivity.getStateManager()
+ .createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
+
+ float fromTranslation = attached ? 1 : 0;
+ float toTranslation = attached ? 0 : 1;
+ mActivity.getStateManager()
+ .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
+ if (!recentsView.isShown() && animate) {
+ ADJACENT_PAGE_OFFSET.set(recentsView, fromTranslation);
+ } else {
+ fromTranslation = ADJACENT_PAGE_OFFSET.get(recentsView);
+ }
+ if (!animate) {
+ ADJACENT_PAGE_OFFSET.set(recentsView, toTranslation);
+ } else {
+ mActivity.getStateManager().createStateElementAnimation(
+ INDEX_RECENTS_TRANSLATE_X_ANIM,
+ fromTranslation, toTranslation).start();
+ }
+
+ fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
+ fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start();
+ }
+
+ protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
+ // Scale down recents from being full screen to being in overview.
+ RecentsView recentsView = activity.getOverviewPanel();
+ pa.addFloat(recentsView, SCALE_PROPERTY,
+ recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
+ pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
+ }
+ }
+
+ protected static boolean showOverviewActions(Context context) {
+ return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context);
}
}
diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
deleted file mode 100644
index 1b9158b..0000000
--- a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
-import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.util.ActivityTracker;
-import com.android.launcher3.util.SystemUiController;
-import com.android.launcher3.util.Themes;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * A base fallback recents activity that provides support for device profile changes, activity
- * lifecycle tracking, and basic input handling from recents.
- *
- * This class is only used as a fallback in case the default launcher does not have a recents
- * implementation.
- */
-public abstract class BaseRecentsActivity extends BaseDraggingActivity {
-
- public static final ActivityTracker<BaseRecentsActivity> ACTIVITY_TRACKER =
- new ActivityTracker<>();
- private Configuration mOldConfig;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mOldConfig = new Configuration(getResources().getConfiguration());
- initDeviceProfile();
- initViews();
-
- getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
- Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
- ACTIVITY_TRACKER.handleCreate(this);
- }
-
- /**
- * Init drag layer and overview panel views.
- */
- abstract protected void initViews();
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- int diff = newConfig.diff(mOldConfig);
- if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
- onHandleConfigChanged();
- }
- mOldConfig.setTo(newConfig);
- super.onConfigurationChanged(newConfig);
- }
-
- /**
- * Logic for when device configuration changes (rotation, screen size change, multi-window,
- * etc.)
- */
- protected void onHandleConfigChanged() {
- mUserEventDispatcher = null;
- initDeviceProfile();
-
- AbstractFloatingView.closeOpenViews(this, true,
- AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
- dispatchDeviceProfileChanged();
-
- reapplyUi();
- }
-
- /**
- * Initialize/update the device profile.
- */
- private void initDeviceProfile() {
- mDeviceProfile = createDeviceProfile();
- onDeviceProfileInitiated();
- }
-
- /**
- * Generate the device profile to use in this activity.
- * @return device profile
- */
- protected DeviceProfile createDeviceProfile() {
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
-
- // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
- // activity.
- return dp.copy(this);
- }
-
-
- @Override
- protected void onStop() {
- super.onStop();
-
- // Workaround for b/78520668, explicitly trim memory once UI is hidden
- onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
- }
-
- @Override
- public void onEnterAnimationComplete() {
- super.onEnterAnimationComplete();
- // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
- // as a part of quickstep, so that high-res thumbnails can load the next time we enter
- // overview
- RecentsModel.INSTANCE.get(this).getThumbnailCache()
- .getHighResLoadingState().setVisible(true);
- }
-
- @Override
- public void onTrimMemory(int level) {
- super.onTrimMemory(level);
- RecentsModel.INSTANCE.get(this).onTrimMemory(level);
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- ACTIVITY_TRACKER.handleNewIntent(this, intent);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- ACTIVITY_TRACKER.onActivityDestroyed(this);
- }
-
- @Override
- public void onBackPressed() {
- // TODO: Launch the task we came from
- startHome();
- }
-
- public void startHome() {
- startActivity(new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- }
-
- @Override
- public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
- super.dump(prefix, fd, writer, args);
- writer.println(prefix + "Misc:");
- dumpMisc(prefix + "\t", writer);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 544f420..00b5eb9 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -17,21 +17,27 @@
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Intent;
+import android.os.Build;
-import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
/**
* Manages the state for an active system gesture, listens for events from the system and Launcher,
* and fires events when the states change.
*/
+@TargetApi(Build.VERSION_CODES.R)
public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener {
/**
@@ -44,7 +50,7 @@
NEW_TASK(false, ContainerType.APP, true),
- LAST_TASK(false, ContainerType.APP, false);
+ LAST_TASK(false, ContainerType.APP, true);
GestureEndTarget(boolean isLauncher, int containerType,
boolean recentsAttachedToAppWindow) {
@@ -106,14 +112,14 @@
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");
- // Called when the new task appeared from quick switching.
- public static final int STATE_TASK_APPEARED_DURING_SWITCH =
- getFlagForIndex("STATE_TASK_APPEARED_DURING_SWITCH");
-
// Needed to interact with the current activity
private final Intent mHomeIntent;
private final Intent mOverviewIntent;
@@ -123,9 +129,9 @@
private ActivityManager.RunningTaskInfo mRunningTask;
private GestureEndTarget mEndTarget;
- private RemoteAnimationTargetCompat mAnimationTarget;
- // TODO: This can be removed once we stop finishing the animation when starting a new task
- private int mFinishingRecentsAnimationTaskId = -1;
+ private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
+ private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
+ private int mLastStartedTaskId = -1;
public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
mHomeIntent = componentObserver.getHomeIntent();
@@ -143,7 +149,9 @@
mGestureId = other.mGestureId;
mRunningTask = other.mRunningTask;
mEndTarget = other.mEndTarget;
- mFinishingRecentsAnimationTaskId = other.mFinishingRecentsAnimationTaskId;
+ mLastAppearedTaskTarget = other.mLastAppearedTaskTarget;
+ mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds;
+ mLastStartedTaskId = other.mLastStartedTaskId;
}
public GestureState() {
@@ -193,7 +201,7 @@
/**
* @return the interface to the activity handing the UI updates for this gesture.
*/
- public <T extends BaseDraggingActivity> BaseActivityInterface<T> getActivityInterface() {
+ public <T extends StatefulActivity<?>> BaseActivityInterface<?, T> getActivityInterface() {
return mActivityInterface;
}
@@ -226,20 +234,52 @@
}
/**
+ * Updates the last task that appeared during this gesture.
+ */
+ public void updateLastAppearedTaskTarget(RemoteAnimationTargetCompat lastAppearedTaskTarget) {
+ mLastAppearedTaskTarget = lastAppearedTaskTarget;
+ if (lastAppearedTaskTarget != null) {
+ mPreviouslyAppearedTaskIds.add(lastAppearedTaskTarget.taskId);
+ }
+ }
+
+ /**
+ * @return The id of the task that appeared during this gesture.
+ */
+ public int getLastAppearedTaskId() {
+ return mLastAppearedTaskTarget != null ? mLastAppearedTaskTarget.taskId : -1;
+ }
+
+ public void updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds) {
+ mPreviouslyAppearedTaskIds = previouslyAppearedTaskIds;
+ }
+
+ public Set<Integer> getPreviouslyAppearedTaskIds() {
+ return mPreviouslyAppearedTaskIds;
+ }
+
+ /**
+ * Updates the last task that we started via startActivityFromRecents() during this gesture.
+ */
+ public void updateLastStartedTaskId(int lastStartedTaskId) {
+ mLastStartedTaskId = lastStartedTaskId;
+ }
+
+ /**
+ * @return The id of the task that was most recently started during this gesture, or -1 if
+ * no task has been started yet (i.e. we haven't settled on a new task).
+ */
+ public int getLastStartedTaskId() {
+ return mLastStartedTaskId;
+ }
+
+ /**
* @return the end target for this gesture (if known).
*/
public GestureEndTarget getEndTarget() {
return mEndTarget;
}
- public void setAnimationTarget(RemoteAnimationTargetCompat target) {
- mAnimationTarget = target;
- }
-
- public RemoteAnimationTargetCompat getAnimationTarget() {
- return mAnimationTarget;
- }
-
/**
* Sets the end target of this gesture and immediately notifies the state changes.
*/
@@ -254,35 +294,15 @@
public void setEndTarget(GestureEndTarget target, boolean isAtomic) {
mEndTarget = target;
mStateCallback.setState(STATE_END_TARGET_SET);
+ ActiveGestureLog.INSTANCE.addLog("setEndTarget " + mEndTarget);
if (isAtomic) {
mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
}
/**
- * @return the id for the task that was about to be launched following the finish of the recents
- * animation. Only defined between when the finish-recents call was made and the launch
- * activity call is made.
- */
- public int getFinishingRecentsAnimationTaskId() {
- return mFinishingRecentsAnimationTaskId;
- }
-
- /**
- * Sets the id for the task will be launched after the recents animation is finished. Once the
- * animation has finished then the id will be reset to -1.
- */
- public void setFinishingRecentsAnimationTaskId(int taskId) {
- mFinishingRecentsAnimationTaskId = taskId;
- mStateCallback.runOnceAtState(STATE_RECENTS_ANIMATION_FINISHED, () -> {
- mFinishingRecentsAnimationTaskId = -1;
- });
- }
-
- /**
* @return whether the current gesture is still running a recents animation to a state in the
* Launcher or Recents activity.
- * Updates the running task for the gesture to be the given {@param runningTask}.
*/
public boolean isRunningAnimationToLauncher() {
return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher;
@@ -314,18 +334,13 @@
mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
}
- @Override
- public void onTaskAppeared(RemoteAnimationTargetCompat app) {
- mAnimationTarget = app;
- mStateCallback.setState(STATE_TASK_APPEARED_DURING_SWITCH);
- }
-
public void dump(PrintWriter pw) {
pw.println("GestureState:");
pw.println(" gestureID=" + mGestureId);
pw.println(" runningTask=" + mRunningTask);
pw.println(" endTarget=" + mEndTarget);
- pw.println(" finishingRecentsAnimationTaskId=" + mFinishingRecentsAnimationTaskId);
+ pw.println(" lastAppearedTaskTargetId=" + getLastAppearedTaskId());
+ pw.println(" lastStartedTaskId=" + mLastStartedTaskId);
pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning());
}
}
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 818d836..67711c0 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -34,6 +34,8 @@
int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
int TYPE_RESET_GESTURE = 1 << 8;
int TYPE_OVERSCROLL = 1 << 9;
+ int TYPE_SYSUI_OVERLAY = 1 << 10;
+ int TYPE_ONE_HANDED = 1 << 11;
String[] NAMES = new String[] {
"TYPE_NO_OP", // 0
@@ -46,6 +48,8 @@
"TYPE_OVERVIEW_WITHOUT_FOCUS", // 7
"TYPE_RESET_GESTURE", // 8
"TYPE_OVERSCROLL", // 9
+ "TYPE_SYSUI_OVERLAY", // 10
+ "TYPE_ONE_HANDED", // 11
};
InputConsumer NO_OP = () -> TYPE_NO_OP;
@@ -70,10 +74,15 @@
}
/**
- * Returns true if the given input consumer is in the hierarchy of this input consumer.
+ * Handle and specific setup necessary based on the orientation of the device
*/
- default boolean isInConsumerHierarchy(InputConsumer candidate) {
- return this == candidate;
+ default void notifyOrientationSetup() {}
+
+ /**
+ * Returns the active input consumer is in the hierarchy of this input consumer.
+ */
+ default InputConsumer getActiveConsumerInHierarchy() {
+ return this;
}
/**
@@ -96,15 +105,15 @@
}
default String getName() {
- String name = "";
+ StringBuilder name = new StringBuilder();
for (int i = 0; i < NAMES.length; i++) {
if ((getType() & (1 << i)) != 0) {
if (name.length() > 0) {
- name += ":";
+ name.append(":");
}
- name += NAMES[i];
+ name.append(NAMES[i]);
}
}
- return name;
+ return name.toString();
}
}
diff --git a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
index 3e9872a..7638541 100644
--- a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
+++ b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
@@ -21,7 +21,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import com.android.launcher3.AppInfo;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
/**
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 3e73f49..0710a0a 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -22,6 +22,9 @@
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;
@@ -33,7 +36,7 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
-import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
import java.io.PrintWriter;
@@ -52,35 +55,83 @@
private static final boolean DEBUG = false;
private static final int MAX_ORIENTATIONS = 4;
+ private static final int QUICKSTEP_ROTATION_UNINITIALIZED = -1;
+
+ private final Matrix mTmpMatrix = new Matrix();
+ private final float[] mTmpPoint = new float[2];
+
private SparseArray<OrientationRectF> mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS);
private final RectF mAssistantLeftRegion = new RectF();
private final RectF mAssistantRightRegion = new RectF();
- private int mCurrentRotation;
+ private final RectF mOneHandedModeRegion = new RectF();
+ private int mCurrentDisplayRotation;
+ private int mNavBarGesturalHeight;
private boolean mEnableMultipleRegions;
private Resources mResources;
private OrientationRectF mLastRectTouched;
+ /**
+ * The rotation of the last touched nav bar, whether that be through the last region the user
+ * touched down on or valid rotation user turned their device to.
+ * Note this is different than
+ * {@link #mQuickStepStartingRotation} as it always updates its value on every touch whereas
+ * mQuickstepStartingRotation only updates when device rotation matches touch rotation.
+ */
+ private int mActiveTouchRotation;
private SysUINavigationMode.Mode mMode;
private QuickStepContractInfo mContractInfo;
- private int mQuickStepStartingRotation = -1;
+
+ /**
+ * Represents if we're currently in a swipe "session" of sorts. If value is
+ * QUICKSTEP_ROTATION_UNINITIALIZED, then user has not tapped on an active nav region.
+ * Otherwise it will be the rotation of the display when the user first interacted with the
+ * active nav bar region.
+ * The "session" ends when {@link #enableMultipleRegions(boolean, DefaultDisplay.Info)} is
+ * called - usually from a timeout or if user starts interacting w/ the foreground app.
+ *
+ * This is different than {@link #mLastRectTouched} as it can get reset by the system whereas
+ * the rect is purely used for tracking touch interactions and usually this "session" will
+ * outlast the touch interaction.
+ */
+ private int mQuickStepStartingRotation = QUICKSTEP_ROTATION_UNINITIALIZED;
/** For testability */
interface QuickStepContractInfo {
float getWindowCornerRadius();
}
+
OrientationTouchTransformer(Resources resources, SysUINavigationMode.Mode mode,
QuickStepContractInfo contractInfo) {
mResources = resources;
mMode = mode;
mContractInfo = contractInfo;
+ mNavBarGesturalHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
}
- void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info) {
+ private void refreshTouchRegion(DefaultDisplay.Info info, Resources newRes) {
+ // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
+ // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
+ // It tries to cache and reuse swipe regions whenever possible based only on rotation
+ mResources = newRes;
+ mSwipeTouchRegions.clear();
+ resetSwipeRegions(info);
+ }
+
+ void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info,
+ Resources newRes) {
if (mMode == newMode) {
return;
}
this.mMode = newMode;
- resetSwipeRegions(info);
+ refreshTouchRegion(info, newRes);
+ }
+
+ void setGesturalHeight(int newGesturalHeight, DefaultDisplay.Info info, Resources newRes) {
+ if (mNavBarGesturalHeight == newGesturalHeight) {
+ return;
+ }
+ mNavBarGesturalHeight = newGesturalHeight;
+ refreshTouchRegion(info, newRes);
}
/**
@@ -92,20 +143,21 @@
* @see #enableMultipleRegions(boolean, DefaultDisplay.Info)
*/
void createOrAddTouchRegion(DefaultDisplay.Info info) {
- mCurrentRotation = info.rotation;
- if (mQuickStepStartingRotation > -1 && mCurrentRotation == mQuickStepStartingRotation) {
- // Ignore nav bars in other rotations except for the one we started out in
+ mCurrentDisplayRotation = info.rotation;
+ if (mQuickStepStartingRotation > QUICKSTEP_ROTATION_UNINITIALIZED
+ && mCurrentDisplayRotation == mQuickStepStartingRotation) {
+ // User already was swiping and the current screen is same rotation as the starting one
+ // Remove active nav bars in other rotations except for the one we started out in
resetSwipeRegions(info);
return;
}
-
- OrientationRectF region = mSwipeTouchRegions.get(mCurrentRotation);
+ OrientationRectF region = mSwipeTouchRegions.get(mCurrentDisplayRotation);
if (region != null) {
return;
}
if (mEnableMultipleRegions) {
- mSwipeTouchRegions.put(mCurrentRotation, createRegionForDisplay(info));
+ mSwipeTouchRegions.put(mCurrentDisplayRotation, createRegionForDisplay(info));
} else {
resetSwipeRegions(info);
}
@@ -116,20 +168,32 @@
* ALSO, you BETTER call this with {@param enableMultipleRegions} set to false once you're done.
*
* @param enableMultipleRegions Set to true to start tracking multiple nav bar regions
- * @param info The current displayInfo
+ * @param info The current displayInfo which will be the start of the quickswitch gesture
*/
void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) {
- mEnableMultipleRegions = enableMultipleRegions;
- if (!enableMultipleRegions) {
- mQuickStepStartingRotation = -1;
- resetSwipeRegions(info);
+ mEnableMultipleRegions = enableMultipleRegions &&
+ mMode != SysUINavigationMode.Mode.TWO_BUTTONS;
+ if (mEnableMultipleRegions) {
+ mQuickStepStartingRotation = info.rotation;
} else {
- if (mLastRectTouched != null) {
- // mLastRectTouched can be null if gesture type is changed (ex. from settings)
- // but nav bar hasn't been interacted with yet.
- mQuickStepStartingRotation = mLastRectTouched.mRotation;
- }
+ mActiveTouchRotation = 0;
+ mQuickStepStartingRotation = QUICKSTEP_ROTATION_UNINITIALIZED;
}
+ resetSwipeRegions(info);
+ }
+
+ /**
+ * Call when removing multiple regions to swipe from, but still in active quickswitch mode (task
+ * list is still frozen).
+ * Ex. This would be called when user has quickswitched to the same app rotation that
+ * they started quickswitching in, indicating that extra nav regions can be ignored. Calling
+ * this will update the value of {@link #mActiveTouchRotation}
+ *
+ * @param displayInfo The display whos rotation will be used as the current active rotation
+ */
+ void setSingleActiveRegion(DefaultDisplay.Info displayInfo) {
+ mActiveTouchRotation = displayInfo.rotation;
+ resetSwipeRegions(displayInfo);
}
/**
@@ -140,40 +204,41 @@
*/
private void resetSwipeRegions(DefaultDisplay.Info region) {
if (DEBUG) {
- Log.d(TAG, "clearing all regions except rotation: " + mCurrentRotation);
+ Log.d(TAG, "clearing all regions except rotation: " + mCurrentDisplayRotation);
}
- mCurrentRotation = region.rotation;
+ mCurrentDisplayRotation = region.rotation;
+ OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplayRotation);
+ if (regionToKeep == null) {
+ regionToKeep = createRegionForDisplay(region);
+ }
mSwipeTouchRegions.clear();
- mSwipeTouchRegions.put(mCurrentRotation, createRegionForDisplay(region));
+ mSwipeTouchRegions.put(mCurrentDisplayRotation, regionToKeep);
+ updateAssistantRegions(regionToKeep);
+ }
+
+ private void resetSwipeRegions() {
+ OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplayRotation);
+ mSwipeTouchRegions.clear();
+ if (regionToKeep != null) {
+ mSwipeTouchRegions.put(mCurrentDisplayRotation, regionToKeep);
+ updateAssistantRegions(regionToKeep);
+ }
}
private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) {
if (DEBUG) {
- Log.d(TAG, "creating rotation region for: " + mCurrentRotation);
+ Log.d(TAG, "creating rotation region for: " + mCurrentDisplayRotation);
}
Point size = display.realSize;
int rotation = display.rotation;
+ int touchHeight = mNavBarGesturalHeight;
OrientationRectF orientationRectF =
new OrientationRectF(0, 0, size.x, size.y, rotation);
if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
- int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
orientationRectF.top = orientationRectF.bottom - touchHeight;
-
- final int assistantWidth = mResources
- .getDimensionPixelSize(R.dimen.gestures_assistant_width);
- final float assistantHeight = Math.max(touchHeight,
- mContractInfo.getWindowCornerRadius());
- mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = orientationRectF.bottom;
- mAssistantLeftRegion.top = mAssistantRightRegion.top =
- orientationRectF.bottom - assistantHeight;
-
- mAssistantLeftRegion.left = 0;
- mAssistantLeftRegion.right = assistantWidth;
-
- mAssistantRightRegion.right = orientationRectF.right;
- mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
+ updateAssistantRegions(orientationRectF);
} else {
mAssistantLeftRegion.setEmpty();
mAssistantRightRegion.setEmpty();
@@ -187,25 +252,49 @@
+ getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
break;
default:
- orientationRectF.top = orientationRectF.bottom
- - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+ orientationRectF.top = orientationRectF.bottom - touchHeight;
}
}
+ // One handed gestural only active on portrait mode
+ mOneHandedModeRegion.set(0, orientationRectF.bottom - touchHeight, size.x, size.y);
return orientationRectF;
}
+ private void updateAssistantRegions(OrientationRectF orientationRectF) {
+ int navbarHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+ int assistantWidth = mResources.getDimensionPixelSize(R.dimen.gestures_assistant_width);
+ float assistantHeight = Math.max(navbarHeight, mContractInfo.getWindowCornerRadius());
+ mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = orientationRectF.bottom;
+ mAssistantLeftRegion.top = mAssistantRightRegion.top =
+ orientationRectF.bottom - assistantHeight;
+
+ mAssistantLeftRegion.left = 0;
+ mAssistantLeftRegion.right = assistantWidth;
+
+ mAssistantRightRegion.right = orientationRectF.right;
+ mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
+ }
+
boolean touchInAssistantRegion(MotionEvent ev) {
return mAssistantLeftRegion.contains(ev.getX(), ev.getY())
|| mAssistantRightRegion.contains(ev.getX(), ev.getY());
}
+ boolean touchInOneHandedModeRegion(MotionEvent ev) {
+ return mOneHandedModeRegion.contains(ev.getX(), ev.getY());
+ }
+
private int getNavbarSize(String resName) {
return ResourceUtils.getNavbarSize(resName, mResources);
}
boolean touchInValidSwipeRegions(float x, float y) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "touchInValidSwipeRegions " + x + "," + y + " in "
+ + mLastRectTouched);
+ }
if (mLastRectTouched != null) {
return mLastRectTouched.contains(x, y);
}
@@ -213,11 +302,11 @@
}
int getCurrentActiveRotation() {
- if (mLastRectTouched == null) {
- return 0;
- } else {
- return mLastRectTouched.mRotation;
- }
+ return mActiveTouchRotation;
+ }
+
+ int getQuickStepStartingRotation() {
+ return mQuickStepStartingRotation;
}
public void transform(MotionEvent event) {
@@ -247,11 +336,25 @@
for (int i = 0; i < MAX_ORIENTATIONS; i++) {
OrientationRectF rect = mSwipeTouchRegions.get(i);
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "transform:DOWN, rect=" + rect);
+ }
if (rect == null) {
continue;
}
if (rect.applyTransform(event, false)) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "setting mLastRectTouched");
+ }
mLastRectTouched = rect;
+ mActiveTouchRotation = rect.mRotation;
+ if (mEnableMultipleRegions
+ && mCurrentDisplayRotation == mActiveTouchRotation) {
+ // TODO(b/154580671) might make this block unnecessary
+ // Start a touch session for the default nav region for the display
+ mQuickStepStartingRotation = mLastRectTouched.mRotation;
+ resetSwipeRegions();
+ }
if (DEBUG) {
Log.d(TAG, "set active region: " + rect);
}
@@ -274,17 +377,12 @@
regions.append(rectF.mRotation).append(" ");
}
pw.println(regions.toString());
+ pw.println(" mNavBarGesturalHeight=" + mNavBarGesturalHeight);
+ pw.println(" mOneHandedModeRegion=" + mOneHandedModeRegion);
}
private class OrientationRectF extends RectF {
- /**
- * Delta to subtract width and height by because if we report the translated touch
- * bounds as the width and height, calling {@link RectF#contains(float, float)} will
- * be false
- */
- private float maxDelta = 0.001f;
-
private int mRotation;
private float mHeight;
private float mWidth;
@@ -292,8 +390,8 @@
OrientationRectF(float left, float top, float right, float bottom, int rotation) {
super(left, top, right, bottom);
this.mRotation = rotation;
- mHeight = bottom - maxDelta;
- mWidth = right - maxDelta;
+ mHeight = bottom;
+ mWidth = right;
}
@Override
@@ -303,40 +401,39 @@
return s;
}
- boolean applyTransform(MotionEvent event, boolean forceTransform) {
- // TODO(b/149658423): See if we can use RotationHelper.getRotationMatrix here
- MotionEvent tmp = MotionEvent.obtain(event);
- Matrix outMatrix = new Matrix();
- int delta = RotationHelper.deltaRotation(mCurrentRotation, mRotation);
- switch (delta) {
- case Surface.ROTATION_0:
- outMatrix.reset();
- break;
- case Surface.ROTATION_90:
- outMatrix.setRotate(270);
- outMatrix.postTranslate(0, mHeight);
- break;
- case Surface.ROTATION_180:
- outMatrix.setRotate(180);
- outMatrix.postTranslate(mHeight, mWidth);
- break;
- case Surface.ROTATION_270:
- outMatrix.setRotate(90);
- outMatrix.postTranslate(mWidth, 0);
- break;
- }
+ @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;
+ }
- tmp.transform(outMatrix);
+ boolean applyTransform(MotionEvent event, boolean forceTransform) {
+ mTmpMatrix.reset();
+ postDisplayRotation(deltaRotation(mCurrentDisplayRotation, mRotation),
+ mHeight, mWidth, mTmpMatrix);
+ if (forceTransform) {
+ if (DEBUG) {
+ Log.d(TAG, "Transforming rotation due to forceTransform, "
+ + "mCurrentRotation: " + mCurrentDisplayRotation
+ + "mRotation: " + mRotation);
+ }
+ 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: " + tmp.getX() + ", " + tmp.getY()
+ + " new: " + mTmpPoint[0] + ", " + mTmpPoint[1]
+ " rect: " + this + " forceTransform: " + forceTransform
- + " contains: " + contains(tmp.getX(), tmp.getY()));
+ + " contains: " + contains(mTmpPoint[0], mTmpPoint[1]));
}
- if (forceTransform || contains(tmp.getX(), tmp.getY())) {
- event.transform(outMatrix);
- tmp.recycle();
+ if (contains(mTmpPoint[0], mTmpPoint[1])) {
+ event.transform(mTmpMatrix);
return true;
}
return false;
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 9edc86e..07f838b 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -20,6 +20,7 @@
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
@@ -40,6 +41,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
+import java.util.function.Consumer;
/**
* Class to keep track of the current overview component based off user preferences and app updates
@@ -57,6 +59,9 @@
private final Intent mMyHomeIntent;
private final Intent mFallbackIntent;
private final SparseIntArray mConfigChangesMap = new SparseIntArray();
+
+ private Consumer<Boolean> mOverviewChangeListener = b -> { };
+
private String mUpdateRegisteredPackage;
private BaseActivityInterface mActivityInterface;
private Intent mOverviewIntent;
@@ -64,10 +69,10 @@
private boolean mIsDefaultHome;
private boolean mIsHomeDisabled;
+
public OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState) {
mContext = context;
mDeviceState = deviceState;
-
mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -95,6 +100,13 @@
updateOverviewTargets();
}
+ /**
+ * Sets a listener for changes in {@link #isHomeAndOverviewSame()}
+ */
+ public void setOverviewChangeListener(Consumer<Boolean> overviewChangeListener) {
+ mOverviewChangeListener = overviewChangeListener;
+ }
+
public void onSystemUiStateChanged() {
if (mDeviceState.isHomeDisabled() != mIsHomeDisabled) {
updateOverviewTargets();
@@ -127,9 +139,16 @@
mActivityInterface.onAssistantVisibilityChanged(0.f);
}
+ if (SEPARATE_RECENTS_ACTIVITY.get()) {
+ mIsDefaultHome = false;
+ if (defaultHome == null) {
+ defaultHome = mMyHomeIntent.getComponent();
+ }
+ }
+
if (!mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) {
// User default home is same as out home app. Use Overview integrated in Launcher.
- mActivityInterface = new LauncherActivityInterface();
+ mActivityInterface = LauncherActivityInterface.INSTANCE;
mIsHomeAndOverviewSame = true;
mOverviewIntent = mMyHomeIntent;
mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
@@ -139,7 +158,7 @@
} else {
// The default home app is a different launcher. Use the fallback Overview instead.
- mActivityInterface = new FallbackActivityInterface();
+ mActivityInterface = FallbackActivityInterface.INSTANCE;
mIsHomeAndOverviewSame = false;
mOverviewIntent = mFallbackIntent;
mCurrentHomeIntent.setComponent(defaultHome);
@@ -159,6 +178,7 @@
ACTION_PACKAGE_REMOVED));
}
}
+ mOverviewChangeListener.accept(mIsHomeAndOverviewSame);
}
/**
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 10f9feb..70b4f20 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -40,21 +40,20 @@
/**
* Manages the recent task list from the system, caching it as necessary.
*/
-@TargetApi(Build.VERSION_CODES.P)
+@TargetApi(Build.VERSION_CODES.R)
public class RecentTasksList extends TaskStackChangeListener {
+ private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
+
private final KeyguardManagerCompat mKeyguardManager;
private final LooperExecutor mMainThreadExecutor;
private final ActivityManagerWrapper mActivityManagerWrapper;
// The list change id, increments as the task list changes in the system
private int mChangeId;
- // The last change id when the list was last loaded completely, must be <= the list change id
- private int mLastLoadedId;
- // The last change id was loaded with keysOnly = true
- private boolean mLastLoadHadKeysOnly;
- ArrayList<Task> mTasks = new ArrayList<>();
+ private TaskLoadResult mResultsBg = INVALID_RESULT;
+ private TaskLoadResult mResultsUi = INVALID_RESULT;
public RecentTasksList(LooperExecutor mainThreadExecutor,
KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) {
@@ -71,7 +70,7 @@
public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
// Kick off task loading in the background
UI_HELPER_EXECUTOR.execute(() -> {
- ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */);
+ ArrayList<Task> tasks = loadTasksInBackground(numTasks, -1, true /* loadKeysOnly */);
mMainThreadExecutor.execute(() -> callback.accept(tasks));
});
}
@@ -85,26 +84,30 @@
*/
public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
final int requestLoadId = mChangeId;
- Runnable resultCallback = callback == null
- ? () -> { }
- : () -> callback.accept(copyOf(mTasks));
-
- if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
+ 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.
- mMainThreadExecutor.post(resultCallback);
+ if (callback != null) {
+ // Copy synchronously as the changeId might change by next frame
+ ArrayList<Task> result = copyOf(mResultsUi);
+ mMainThreadExecutor.post(() -> callback.accept(result));
+ }
+
return requestLoadId;
}
// Kick off task loading in the background
UI_HELPER_EXECUTOR.execute(() -> {
- ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly);
-
+ if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) {
+ mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly);
+ }
+ TaskLoadResult loadResult = mResultsBg;
mMainThreadExecutor.execute(() -> {
- mTasks = tasks;
- mLastLoadedId = requestLoadId;
- mLastLoadHadKeysOnly = loadKeysOnly;
- resultCallback.run();
+ mResultsUi = loadResult;
+ if (callback != null) {
+ ArrayList<Task> result = copyOf(mResultsUi);
+ callback.accept(result);
+ }
});
});
@@ -119,23 +122,40 @@
}
@Override
- public synchronized void onTaskStackChanged() {
- mChangeId++;
+ 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) {
- mTasks = loadTasksInBackground(Integer.MAX_VALUE, false);
+ invalidateLoadedTasks();
}
+
@Override
- public synchronized void onActivityPinned(String packageName, int userId, int taskId,
- int stackId) {
- mChangeId++;
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+ invalidateLoadedTasks();
}
@Override
public synchronized void onActivityUnpinned() {
+ invalidateLoadedTasks();
+ }
+
+ private synchronized void invalidateLoadedTasks() {
+ UI_HELPER_EXECUTOR.execute(() -> mResultsBg = INVALID_RESULT);
+ mResultsUi = INVALID_RESULT;
mChangeId++;
}
@@ -143,10 +163,8 @@
* Loads and creates a list of all the recent tasks.
*/
@VisibleForTesting
- ArrayList<Task> loadTasksInBackground(int numTasks,
- boolean loadKeysOnly) {
+ TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
int currentUserId = Process.myUserHandle().getIdentifier();
- ArrayList<Task> allTasks = new ArrayList<>();
List<ActivityManager.RecentTaskInfo> rawTasks =
mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId);
// The raw tasks are given in most-recent to least-recent order, we need to reverse it
@@ -163,9 +181,8 @@
}
};
- int taskCount = rawTasks.size();
- for (int i = 0; i < taskCount; i++) {
- ActivityManager.RecentTaskInfo rawTask = rawTasks.get(i);
+ TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
+ for (ActivityManager.RecentTaskInfo rawTask : rawTasks) {
Task.TaskKey taskKey = new Task.TaskKey(rawTask);
Task task;
if (!loadKeysOnly) {
@@ -189,4 +206,22 @@
}
return newTasks;
}
+
+ private static class TaskLoadResult extends ArrayList<Task> {
+
+ final int mId;
+
+ // If the result was loaded with keysOnly = true
+ final boolean mKeysOnly;
+
+ TaskLoadResult(int id, boolean keysOnly, int size) {
+ super(size);
+ mId = id;
+ mKeysOnly = keysOnly;
+ }
+
+ boolean isValidForRequest(int requestId, boolean loadKeysOnly) {
+ return mId == requestId && (!mKeysOnly || loadKeysOnly);
+ }
+ }
}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 566a701..a21c714 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -19,13 +19,11 @@
import android.graphics.Rect;
import android.util.ArraySet;
-import android.util.Log;
import androidx.annotation.BinderThread;
import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.Preconditions;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -41,15 +39,15 @@
com.android.systemui.shared.system.RecentsAnimationListener {
private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
- private final boolean mShouldMinimizeSplitScreen;
+ private final boolean mAllowMinimizeSplitScreen;
// TODO(141886704): Remove these references when they are no longer needed
private RecentsAnimationController mController;
private boolean mCancelled;
- public RecentsAnimationCallbacks(boolean shouldMinimizeSplitScreen) {
- mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
+ public RecentsAnimationCallbacks(boolean allowMinimizeSplitScreen) {
+ mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
}
@UiThread
@@ -91,13 +89,10 @@
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
Rect homeContentInsets, Rect minimizedHomeBounds) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_START_FROM_RECENTS, "onAnimationStart");
- }
RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
wallpaperTargets, homeContentInsets, minimizedHomeBounds);
mController = new RecentsAnimationController(animationController,
- mShouldMinimizeSplitScreen, this::onAnimationFinished);
+ mAllowMinimizeSplitScreen, this::onAnimationFinished);
if (mCancelled) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
@@ -164,6 +159,6 @@
/**
* Callback made when a task started from the recents is ready for an app transition.
*/
- default void onTaskAppeared(RemoteAnimationTargetCompat app) {}
+ default void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {}
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 268e564..4e9aa61 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -22,13 +22,13 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import android.annotation.NonNull;
import android.os.SystemClock;
import android.util.Log;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.util.Preconditions;
@@ -49,21 +49,22 @@
private final RecentsAnimationControllerCompat mController;
private final Consumer<RecentsAnimationController> mOnFinishedListener;
- private final boolean mShouldMinimizeSplitScreen;
+ private final boolean mAllowMinimizeSplitScreen;
private InputConsumerController mInputConsumerController;
private Supplier<InputConsumer> mInputProxySupplier;
private InputConsumer mInputConsumer;
- private boolean mWindowThresholdCrossed = false;
+ private boolean mUseLauncherSysBarFlags = false;
+ private boolean mSplitScreenMinimized = false;
private boolean mTouchInProgress;
- private boolean mFinishPending;
+ private boolean mDisableInputProxyPending;
public RecentsAnimationController(RecentsAnimationControllerCompat controller,
- boolean shouldMinimizeSplitScreen,
+ boolean allowMinimizeSplitScreen,
Consumer<RecentsAnimationController> onFinishedListener) {
mController = controller;
mOnFinishedListener = onFinishedListener;
- mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
+ mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
}
/**
@@ -76,16 +77,31 @@
/**
* Indicates that the gesture has crossed the window boundary threshold and system UI can be
- * update the represent the window behind
+ * update the system bar flags accordingly.
*/
- public void setWindowThresholdCrossed(boolean windowThresholdCrossed) {
- if (mWindowThresholdCrossed != windowThresholdCrossed) {
- mWindowThresholdCrossed = windowThresholdCrossed;
+ public void setUseLauncherSystemBarFlags(boolean useLauncherSysBarFlags) {
+ if (mUseLauncherSysBarFlags != useLauncherSysBarFlags) {
+ mUseLauncherSysBarFlags = useLauncherSysBarFlags;
UI_HELPER_EXECUTOR.execute(() -> {
- mController.setAnimationTargetsBehindSystemBars(!windowThresholdCrossed);
+ mController.setAnimationTargetsBehindSystemBars(!useLauncherSysBarFlags);
+ });
+ }
+ }
+
+ /**
+ * Indicates that the gesture has crossed the window boundary threshold and we should minimize
+ * if we are in splitscreen.
+ */
+ public void setSplitScreenMinimized(boolean splitScreenMinimized) {
+ if (!mAllowMinimizeSplitScreen) {
+ return;
+ }
+ if (mSplitScreenMinimized != splitScreenMinimized) {
+ mSplitScreenMinimized = splitScreenMinimized;
+ UI_HELPER_EXECUTOR.execute(() -> {
SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
- if (p != null && mShouldMinimizeSplitScreen) {
- p.setSplitScreenMinimized(windowThresholdCrossed);
+ if (p != null) {
+ p.setSplitScreenMinimized(splitScreenMinimized);
}
});
}
@@ -120,12 +136,12 @@
@UiThread
public void finishAnimationToHome() {
- finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
+ finishAndDisableInputProxy(true /* toRecents */, null, false /* sendUserLeaveHint */);
}
@UiThread
public void finishAnimationToApp() {
- finishAndClear(false /* toRecents */, null, false /* sendUserLeaveHint */);
+ finishAndDisableInputProxy(false /* toRecents */, null, false /* sendUserLeaveHint */);
}
/** See {@link #finish(boolean, Runnable, boolean)} */
@@ -144,22 +160,16 @@
@UiThread
public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
Preconditions.assertUIThread();
- if (!toRecents) {
- finishAndClear(false, onFinishComplete, sendUserLeaveHint);
+ if (toRecents && mTouchInProgress) {
+ // Finish the controller as requested, but don't disable input proxy yet.
+ mDisableInputProxyPending = true;
+ finishController(toRecents, onFinishComplete, sendUserLeaveHint);
} else {
- if (mTouchInProgress) {
- mFinishPending = true;
- // Execute the callback
- if (onFinishComplete != null) {
- onFinishComplete.run();
- }
- } else {
- finishAndClear(true, onFinishComplete, sendUserLeaveHint);
- }
+ finishAndDisableInputProxy(toRecents, onFinishComplete, sendUserLeaveHint);
}
}
- private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
+ private void finishAndDisableInputProxy(boolean toRecents, Runnable onFinishComplete,
boolean sendUserLeaveHint) {
disableInputProxy();
finishController(toRecents, onFinishComplete, sendUserLeaveHint);
@@ -247,9 +257,9 @@
} else if (action == ACTION_CANCEL || action == ACTION_UP) {
// Finish any pending actions
mTouchInProgress = false;
- if (mFinishPending) {
- mFinishPending = false;
- finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
+ if (mDisableInputProxyPending) {
+ mDisableInputProxyPending = false;
+ disableInputProxy();
}
}
if (mInputConsumer != null) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 491c611..6b0c395 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -16,16 +16,22 @@
package com.android.quickstep;
import static android.content.Intent.ACTION_USER_UNLOCKED;
+import static android.view.Surface.ROTATION_0;
+import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
+import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
+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.THREE_BUTTONS;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
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_BUBBLES_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
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;
@@ -34,27 +40,32 @@
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Region;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.UserManager;
+import android.provider.Settings;
import android.text.TextUtils;
-import android.util.Log;
+import android.util.DisplayMetrics;
import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.Surface;
import androidx.annotation.BinderThread;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.SecureSettingsObserver;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
import com.android.quickstep.util.NavBarPosition;
+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.QuickStepContract.SystemUiStateFlags;
@@ -71,7 +82,8 @@
*/
public class RecentsAnimationDeviceState implements
NavigationModeChangeListener,
- DefaultDisplay.DisplayInfoChangeListener {
+ DefaultDisplay.DisplayInfoChangeListener,
+ OneHandedModeChangeListener {
private final Context mContext;
private final SysUINavigationMode mSysUiNavMode;
@@ -88,6 +100,8 @@
private final Region mDeferredGestureRegion = new Region();
private boolean mAssistantAvailable;
private float mAssistantVisibility;
+ private boolean mIsOneHandedModeEnabled;
+ private boolean mIsSwipeToNotificationEnabled;
private boolean mIsUserUnlocked;
private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
@@ -104,23 +118,77 @@
private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
@Override
public void onRecentTaskListFrozenChanged(boolean frozen) {
- if (frozen) {
+ mTaskListFrozen = frozen;
+ if (frozen || mInOverview) {
return;
}
- mOrientationTouchTransformer.enableMultipleRegions(false, mDefaultDisplay.getInfo());
+ enableMultipleRegions(false);
+ }
+
+ @Override
+ public void onActivityRotation(int displayId) {
+ super.onActivityRotation(displayId);
+ // This always gets called before onDisplayInfoChanged() so we know how to process
+ // the rotation in that method. This is done to avoid having a race condition between
+ // the sensor readings and onDisplayInfoChanged() call
+ if (displayId != mDisplayId) {
+ return;
+ }
+
+ mPrioritizeDeviceRotation = true;
+ if (mInOverview) {
+ // reset, launcher must be rotating
+ mExitOverviewRunnable.run();
+ }
+ }
+ };
+
+ private Runnable mExitOverviewRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mInOverview = false;
+ enableMultipleRegions(false);
}
};
private OrientationTouchTransformer mOrientationTouchTransformer;
+ /**
+ * Used to listen for when the device rotates into the orientation of the current
+ * foreground app. For example, if a user quickswitches from a portrait to a fixed landscape
+ * app and then rotates rotates the device to match that orientation, this triggers calls to
+ * sysui to adjust the navbar.
+ */
+ private OrientationEventListener mOrientationListener;
+ private int mSensorRotation = ROTATION_0;
+ /**
+ * This is the configuration of the foreground app or the app that will be in the foreground
+ * once a quickstep gesture finishes.
+ */
+ private int mCurrentAppRotation = -1;
+ /**
+ * This flag is set to true when the device physically changes orientations. When true,
+ * we will always report the current rotation of the foreground app whenever the display
+ * changes, as it would indicate the user's intention to rotate the foreground app.
+ */
+ private boolean mPrioritizeDeviceRotation = false;
private Region mExclusionRegion;
private SystemGestureExclusionListenerCompat mExclusionListener;
private final List<ComponentName> mGestureBlockedActivities;
private Runnable mOnDestroyFrozenTaskRunnable;
+ /**
+ * Set to true when user swipes to recents. In recents, we ignore the state of the recents
+ * task list being frozen or not to allow the user to keep interacting with nav bar rotation
+ * they went into recents with as opposed to defaulting to the default display rotation.
+ * TODO: (b/156984037) For when user rotates after entering overview
+ */
+ private boolean mInOverview;
+ private boolean mTaskListFrozen;
+
+ private boolean mIsUserSetupComplete;
public RecentsAnimationDeviceState(Context context) {
- final ContentResolver resolver = context.getContentResolver();
mContext = context;
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
@@ -170,13 +238,56 @@
ComponentName.unflattenFromString(blockingActivity));
}
}
+
+ if (SystemProperties.getBoolean("ro.support_one_handed_mode", false)) {
+ SecureSettingsObserver oneHandedEnabledObserver =
+ SecureSettingsObserver.newOneHandedSettingsObserver(
+ mContext, enabled -> mIsOneHandedModeEnabled = enabled);
+ oneHandedEnabledObserver.register();
+ oneHandedEnabledObserver.dispatchOnChange();
+ runOnDestroy(oneHandedEnabledObserver::unregister);
+ }
+
+ SecureSettingsObserver swipeBottomEnabledObserver =
+ SecureSettingsObserver.newSwipeToNotificationSettingsObserver(
+ mContext, enabled -> mIsSwipeToNotificationEnabled = enabled);
+ swipeBottomEnabledObserver.register();
+ swipeBottomEnabledObserver.dispatchOnChange();
+ runOnDestroy(swipeBottomEnabledObserver::unregister);
+
+ SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
+ context.getContentResolver(),
+ e -> mIsUserSetupComplete = e,
+ Settings.Secure.USER_SETUP_COMPLETE,
+ 0);
+ mIsUserSetupComplete = userSetupObserver.getValue();
+ if (!mIsUserSetupComplete) {
+ userSetupObserver.register();
+ runOnDestroy(userSetupObserver::unregister);
+ }
+
+ mOrientationListener = new OrientationEventListener(context) {
+ @Override
+ public void onOrientationChanged(int degrees) {
+ int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
+ mSensorRotation);
+ if (newRotation == mSensorRotation) {
+ return;
+ }
+
+ mSensorRotation = newRotation;
+ mPrioritizeDeviceRotation = true;
+
+ if (newRotation == mCurrentAppRotation) {
+ // When user rotates device to the orientation of the foreground app after
+ // quickstepping
+ toggleSecondaryNavBarsForRotation();
+ }
+ }
+ };
}
private void setupOrientationSwipeHandler() {
- if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
- return;
- }
-
ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
.unregisterTaskStackListener(mFrozenTaskListener);
@@ -210,15 +321,20 @@
runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener));
}
+ /**
+ * Adds a listener for the one handed mode change,
+ * guaranteed to be called after the device state's mode has changed.
+ */
+ public void addOneHandedModeChangedCallback(OneHandedModeChangeListener listener) {
+ listener.onOneHandedModeChanged(mSysUiNavMode.addOneHandedOverlayChangeListener(listener));
+ runOnDestroy(() -> mSysUiNavMode.removeOneHandedOverlayChangeListener(listener));
+ }
+
@Override
public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode);
- }
mDefaultDisplay.removeChangeListener(this);
- if (newMode.hasGestures) {
- mDefaultDisplay.addChangeListener(this);
- }
+ mDefaultDisplay.addChangeListener(this);
+ onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
if (newMode == NO_BUTTON) {
mExclusionListener.register();
@@ -228,7 +344,8 @@
mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
- mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
+ mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo(),
+ mContext.getApplicationContext().getResources());
if (!mMode.hasGestures && newMode.hasGestures) {
setupOrientationSwipeHandler();
} else if (mMode.hasGestures && !newMode.hasGestures){
@@ -240,14 +357,39 @@
@Override
public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
- if (info.id != getDisplayId()) {
+ if (info.id != getDisplayId() || flags == CHANGE_FRAME_DELAY) {
+ // ignore displays that aren't running launcher and frame refresh rate changes
return;
}
mDisplayRotation = info.rotation;
+
+ if (!mMode.hasGestures) {
+ return;
+ }
mNavBarPosition = new NavBarPosition(mMode, info);
updateGestureTouchRegions();
mOrientationTouchTransformer.createOrAddTouchRegion(info);
+ mCurrentAppRotation = mDisplayRotation;
+
+ /* Update nav bars on the following:
+ * a) if this is coming from an activity rotation OR
+ * aa) we launch an app in the orientation that user is already in
+ * b) We're not in overview, since overview will always be portrait (w/o home rotation)
+ * c) We're actively in quickswitch mode
+ */
+ if ((mPrioritizeDeviceRotation
+ || mCurrentAppRotation == mSensorRotation) // switch to an app of orientation user is in
+ && !mInOverview
+ && mTaskListFrozen) {
+ toggleSecondaryNavBarsForRotation();
+ }
+ }
+
+ @Override
+ public void onOneHandedModeChanged(int newGesturalHeight) {
+ mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDefaultDisplay.getInfo(),
+ mContext.getApplicationContext().getResources());
}
/**
@@ -311,6 +453,13 @@
return mIsUserUnlocked;
}
+ /**
+ * @return whether the user has completed setup wizard
+ */
+ public boolean isUserSetupComplete() {
+ return mIsUserSetupComplete;
+ }
+
private void notifyUserUnlocked() {
for (Runnable action : mUserUnlockedActions) {
action.run();
@@ -354,10 +503,11 @@
* @return whether SystemUI is in a state where we can start a system gesture.
*/
public boolean canStartSystemGesture() {
- return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+ boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+ || mTaskListFrozen;
+ return canStartWithNavHidden
&& (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
- && (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) == 0
&& ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
|| (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
}
@@ -378,6 +528,20 @@
}
/**
+ * @return whether the bubble stack is expanded
+ */
+ public boolean isBubblesExpanded() {
+ return (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+ }
+
+ /**
+ * @return whether the global actions dialog is showing
+ */
+ public boolean isGlobalActionsShowing() {
+ return (mSystemUiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0;
+ }
+
+ /**
* @return whether lock-task mode is active
*/
public boolean isLockToAppActive() {
@@ -440,6 +604,13 @@
}
/**
+ * @return whether screen pinning is enabled and active
+ */
+ public boolean isOneHandedModeActive() {
+ return (mSystemUiStateFlags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
+ }
+
+ /**
* Sets the region in screen space where the gestures should be deferred (ie. due to specific
* nav bar ui).
*/
@@ -490,20 +661,48 @@
/**
* @param ev An ACTION_DOWN motion event
- * @return whether the given motion event can trigger the assistant.
+ * @param task Info for the currently running task
+ * @return whether the given motion event can trigger the assistant over the current task.
*/
- public boolean canTriggerAssistantAction(MotionEvent ev) {
+ public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) {
return mAssistantAvailable
&& !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
&& mOrientationTouchTransformer.touchInAssistantRegion(ev)
- && !isLockToAppActive();
+ && !isLockToAppActive()
+ && !isGestureBlockedActivity(task);
+ }
+
+ /**
+ * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+ *
+ * @param ev The touch screen motion event.
+ * @return whether the given motion event can trigger the one handed mode.
+ */
+ public boolean canTriggerOneHandedAction(MotionEvent ev) {
+ if (!mIsOneHandedModeEnabled && !mIsSwipeToNotificationEnabled) {
+ return false;
+ }
+
+ final DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo();
+ return (mOrientationTouchTransformer.touchInOneHandedModeRegion(ev)
+ && displayInfo.rotation != Surface.ROTATION_90
+ && displayInfo.rotation != Surface.ROTATION_270
+ && displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600);
+ }
+
+ public boolean isOneHandedModeEnabled() {
+ return mIsOneHandedModeEnabled;
+ }
+
+ public boolean isSwipeToNotificationEnabled() {
+ return mIsSwipeToNotificationEnabled;
}
/**
* *May* apply a transform on the motion event if it lies in the nav bar region for another
* orientation that is currently being tracked as a part of quickstep
*/
- public void setOrientationTransformIfNeeded(MotionEvent event) {
+ void setOrientationTransformIfNeeded(MotionEvent event) {
// negative coordinates bug b/143901881
if (event.getX() < 0 || event.getY() < 0) {
event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
@@ -511,11 +710,79 @@
mOrientationTouchTransformer.transform(event);
}
- public void enableMultipleRegions(boolean enable) {
+ private void enableMultipleRegions(boolean enable) {
mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
+ notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
+ if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
+ // Clear any previous state from sensor manager
+ mSensorRotation = mCurrentAppRotation;
+ mOrientationListener.enable();
+ } else {
+ mOrientationListener.disable();
+ }
+ }
+
+ public void onStartGesture() {
+ if (mTaskListFrozen) {
+ // Prioritize whatever nav bar user touches once in quickstep
+ // This case is specifically when user changes what nav bar they are using mid
+ // quickswitch session before tasks list is unfrozen
+ notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+ }
+ }
+
+ void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
+ BaseActivityInterface activityInterface) {
+ if (endTarget == GestureState.GestureEndTarget.RECENTS) {
+ mInOverview = true;
+ if (!mTaskListFrozen) {
+ // If we're in landscape w/o ever quickswitching, show the navbar in landscape
+ enableMultipleRegions(true);
+ }
+ activityInterface.onExitOverview(this, mExitOverviewRunnable);
+ } else if (endTarget == GestureState.GestureEndTarget.HOME) {
+ enableMultipleRegions(false);
+ } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
+ if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
+ // First gesture to start quickswitch
+ enableMultipleRegions(true);
+ } else {
+ notifySysuiOfCurrentRotation(
+ mOrientationTouchTransformer.getCurrentActiveRotation());
+ }
+
+ // A new gesture is starting, reset the current device rotation
+ // This is done under the assumption that the user won't rotate the phone and then
+ // quickswitch in the old orientation.
+ mPrioritizeDeviceRotation = false;
+ } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
+ if (!mTaskListFrozen) {
+ // touched nav bar but didn't go anywhere and not quickswitching, do nothing
+ return;
+ }
+ notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+ }
+ }
+
+ private void notifySysuiOfCurrentRotation(int rotation) {
+ UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
+ .onQuickSwitchToNewTask(rotation));
+ }
+
+ /**
+ * Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
+ * notifies system UI of the primary rotation the user is interacting with
+ */
+ private void toggleSecondaryNavBarsForRotation() {
+ mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
+ notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
}
public int getCurrentActiveRotation() {
+ if (!mMode.hasGestures) {
+ // touch rotation should always match that of display for 3 button
+ return mDisplayRotation;
+ }
return mOrientationTouchTransformer.getCurrentActiveRotation();
}
@@ -535,6 +802,8 @@
pw.println(" currentActiveRotation=" + getCurrentActiveRotation());
pw.println(" displayRotation=" + getDisplayRotation());
pw.println(" isUserUnlocked=" + mIsUserUnlocked);
+ pw.println(" isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
+ pw.println(" isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
mOrientationTouchTransformer.dump(pw);
}
}
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index 5fa6bc7..ab5e3ba 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -16,19 +16,16 @@
package com.android.quickstep;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Queue;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Holds a collection of RemoteAnimationTargets, filtered by different properties.
*/
public class RemoteAnimationTargets {
- private final Queue<SyncRtSurfaceTransactionApplierCompat> mDependentTransactionAppliers =
- new ArrayDeque<>(1);
+ private final CopyOnWriteArrayList<ReleaseCheck> mReleaseChecks = new CopyOnWriteArrayList<>();
public final RemoteAnimationTargetCompat[] unfilteredApps;
public final RemoteAnimationTargetCompat[] apps;
@@ -36,6 +33,8 @@
public final int targetMode;
public final boolean hasRecents;
+ private boolean mReleased = false;
+
public RemoteAnimationTargets(RemoteAnimationTargetCompat[] apps,
RemoteAnimationTargetCompat[] wallpapers, int targetMode) {
ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
@@ -68,7 +67,7 @@
}
public boolean isAnimatingHome() {
- for (RemoteAnimationTargetCompat target : apps) {
+ for (RemoteAnimationTargetCompat target : unfilteredApps) {
if (target.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
return true;
}
@@ -76,21 +75,65 @@
return false;
}
- public void addDependentTransactionApplier(SyncRtSurfaceTransactionApplierCompat delay) {
- mDependentTransactionAppliers.add(delay);
+ public void addReleaseCheck(ReleaseCheck check) {
+ mReleaseChecks.add(check);
}
public void release() {
- SyncRtSurfaceTransactionApplierCompat applier = mDependentTransactionAppliers.poll();
- if (applier == null) {
- for (RemoteAnimationTargetCompat target : unfilteredApps) {
- target.release();
+ if (mReleased) {
+ return;
+ }
+ for (ReleaseCheck check : mReleaseChecks) {
+ if (!check.mCanRelease) {
+ check.addOnSafeToReleaseCallback(this::release);
+ return;
}
- for (RemoteAnimationTargetCompat target : wallpapers) {
- target.release();
+ }
+ mReleaseChecks.clear();
+ mReleased = true;
+
+ for (RemoteAnimationTargetCompat target : unfilteredApps) {
+ target.release();
+ }
+ for (RemoteAnimationTargetCompat target : wallpapers) {
+ target.release();
+ }
+ }
+
+ /**
+ * Interface for intercepting surface release method
+ */
+ public static class ReleaseCheck {
+
+ boolean mCanRelease = false;
+ private Runnable mAfterApplyCallback;
+
+ protected void setCanRelease(boolean canRelease) {
+ mCanRelease = canRelease;
+ if (mCanRelease && mAfterApplyCallback != null) {
+ Runnable r = mAfterApplyCallback;
+ mAfterApplyCallback = null;
+ r.run();
}
- } else {
- applier.addAfterApplyCallback(this::release);
+ }
+
+ /**
+ * Adds a callback to notify when the surface can safely be released
+ */
+ void addOnSafeToReleaseCallback(Runnable callback) {
+ if (mCanRelease) {
+ callback.run();
+ } else {
+ if (mAfterApplyCallback == null) {
+ mAfterApplyCallback = callback;
+ } else {
+ final Runnable oldCallback = mAfterApplyCallback;
+ mAfterApplyCallback = () -> {
+ callback.run();
+ oldCallback.run();
+ };
+ }
+ }
}
}
}
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 375e589..6994d66 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -16,16 +16,19 @@
package com.android.quickstep;
+import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.util.Log;
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.MainThreadInitializedObject;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -56,15 +59,19 @@
new MainThreadInitializedObject<>(SysUINavigationMode::new);
private static final String TAG = "SysUINavigationMode";
-
private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
"config_navBarInteractionMode";
+ private static final String TARGET_OVERLAY_PACKAGE = "android";
private final Context mContext;
private Mode mMode;
+ private int mNavBarGesturalHeight;
+
private final List<NavigationModeChangeListener> mChangeListeners = new ArrayList<>();
+ private final List<OneHandedModeChangeListener> mOneHandedOverlayChangeListeners =
+ new ArrayList<>();
public SysUINavigationMode(Context context) {
mContext = context;
@@ -73,18 +80,50 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- Mode oldMode = mMode;
- initializeMode();
- if (mMode != oldMode) {
- dispatchModeChange();
- }
+ updateMode();
+ updateGesturalHeight();
}
- }, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
+ }, getPackageFilter(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED));
+ }
+
+ /** Updates navigation mode when needed. */
+ public void updateMode() {
+ Mode oldMode = mMode;
+ initializeMode();
+ if (mMode != oldMode) {
+ dispatchModeChange();
+ }
+ }
+
+ private void updateGesturalHeight() {
+ int newGesturalHeight = ResourceUtils.getDimenByName(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mContext.getResources(),
+ INVALID_RESOURCE_HANDLE);
+
+ if (newGesturalHeight == INVALID_RESOURCE_HANDLE) {
+ Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+ return;
+ }
+
+ if (mNavBarGesturalHeight != newGesturalHeight) {
+ mNavBarGesturalHeight = newGesturalHeight;
+ dispatchOneHandedOverlayChange();
+ }
}
private void initializeMode() {
- int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
- for(Mode m : Mode.values()) {
+ int modeInt = ResourceUtils.getIntegerByName(NAV_BAR_INTERACTION_MODE_RES_NAME,
+ mContext.getResources(), INVALID_RESOURCE_HANDLE);
+ mNavBarGesturalHeight = ResourceUtils.getDimenByName(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mContext.getResources(),
+ INVALID_RESOURCE_HANDLE);
+
+ if (modeInt == INVALID_RESOURCE_HANDLE) {
+ Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+ return;
+ }
+
+ for (Mode m : Mode.values()) {
if (m.resValue == modeInt) {
mMode = m;
}
@@ -97,6 +136,12 @@
}
}
+ private void dispatchOneHandedOverlayChange() {
+ for (OneHandedModeChangeListener listener : mOneHandedOverlayChangeListeners) {
+ listener.onOneHandedModeChanged(mNavBarGesturalHeight);
+ }
+ }
+
public Mode addModeChangeListener(NavigationModeChangeListener listener) {
mChangeListeners.add(listener);
return mMode;
@@ -106,20 +151,17 @@
mChangeListeners.remove(listener);
}
- public Mode getMode() {
- return mMode;
+ public int addOneHandedOverlayChangeListener(OneHandedModeChangeListener listener) {
+ mOneHandedOverlayChangeListeners.add(listener);
+ return mNavBarGesturalHeight;
}
- private static int getSystemIntegerRes(Context context, String resName) {
- Resources res = context.getResources();
- int resId = res.getIdentifier(resName, "integer", "android");
+ public void removeOneHandedOverlayChangeListener(OneHandedModeChangeListener listener) {
+ mOneHandedOverlayChangeListeners.remove(listener);
+ }
- if (resId != 0) {
- return res.getInteger(resId);
- } else {
- Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
- return -1;
- }
+ public Mode getMode() {
+ return mMode;
}
/** @return Whether we can remove the shelf from overview. */
@@ -128,8 +170,25 @@
return getMode(context) != Mode.TWO_BUTTONS;
}
+ public static boolean hideShelfInTwoButtonLandscape(Context context,
+ PagedOrientationHandler pagedOrientationHandler) {
+ return getMode(context) == Mode.TWO_BUTTONS &&
+ !pagedOrientationHandler.isLayoutNaturalToLauncher();
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("SysUINavigationMode:");
+ pw.println(" mode=" + mMode.name());
+ pw.println(" mNavBarGesturalHeight=:" + mNavBarGesturalHeight);
+ }
+
public interface NavigationModeChangeListener {
void onNavigationModeChanged(Mode newMode);
}
+
+ public interface OneHandedModeChangeListener {
+
+ void onOneHandedModeChanged(int newGesturalHeight);
+ }
}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index f5aaf1d..5b239a4 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -31,6 +31,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.recents.model.Task;
/**
* Holds the reference to SystemUI.
@@ -334,6 +335,7 @@
}
}
+ @Override
public void onQuickSwitchToNewTask(int rotation) {
if (mSystemUiProxy != null) {
try {
@@ -343,4 +345,50 @@
}
}
}
+
+ @Override
+ public void startOneHandedMode() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.startOneHandedMode();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call startOneHandedMode", e);
+ }
+ }
+ }
+
+ @Override
+ public void stopOneHandedMode() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.stopOneHandedMode();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call stopOneHandedMode", e);
+ }
+ }
+ }
+
+ @Override
+ public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
+ Insets visibleInsets, Task.TaskKey task) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.handleImageBundleAsScreenshot(screenImageBundle, locationInScreen,
+ visibleInsets, task);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
+ }
+ }
+ }
+
+ @Override
+ public void expandNotificationPanel() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.expandNotificationPanel();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call expandNotificationPanel", e);
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index b04a1ae..cad51f4 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -18,6 +18,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 android.content.Intent;
import android.util.Log;
@@ -26,9 +27,9 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.testing.TestProtocol;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
@@ -37,6 +38,7 @@
private RecentsAnimationTargets mTargets;
// Temporary until we can hook into gesture state events
private GestureState mLastGestureState;
+ private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
/**
* Preloads the recents animation.
@@ -53,9 +55,6 @@
@UiThread
public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState,
Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_START_FROM_RECENTS, "startRecentsAnimation");
- }
// Notify if recents animation is still running
if (mController != null) {
String msg = "New recents animation started before old animation completed";
@@ -70,13 +69,21 @@
final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
mLastGestureState = gestureState;
- mCallbacks = new RecentsAnimationCallbacks(activityInterface.shouldMinimizeSplitScreen());
+ mCallbacks = new RecentsAnimationCallbacks(activityInterface.allowMinimizeSplitScreen());
mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
+ if (mCallbacks == null) {
+ // It's possible for the recents animation to have finished and be cleaned up
+ // by the time we process the start callback, and in that case, just we can skip
+ // handling this call entirely
+ return;
+ }
mController = controller;
mTargets = targets;
+ mLastAppearedTaskTarget = mTargets.findTask(mLastGestureState.getRunningTaskId());
+ mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget);
}
@Override
@@ -94,6 +101,20 @@
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
cleanUpRecentsAnimation(null /* canceledThumbnail */);
}
+
+ @Override
+ public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ if (mController != null) {
+ if (mLastAppearedTaskTarget == null
+ || appearedTaskTarget.taskId != mLastAppearedTaskTarget.taskId) {
+ if (mLastAppearedTaskTarget != null) {
+ mController.removeTaskTarget(mLastAppearedTaskTarget);
+ }
+ mLastAppearedTaskTarget = appearedTaskTarget;
+ mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget);
+ }
+ }
+ }
});
mCallbacks.addListener(gestureState);
mCallbacks.addListener(listener);
@@ -110,6 +131,9 @@
mCallbacks.removeListener(mLastGestureState);
mLastGestureState = gestureState;
mCallbacks.addListener(gestureState);
+ gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED
+ | STATE_RECENTS_ANIMATION_STARTED);
+ gestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget);
return mCallbacks;
}
@@ -169,6 +193,7 @@
mCallbacks = null;
mTargets = null;
mLastGestureState = null;
+ mLastAppearedTaskTarget = null;
}
public void dump() {
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index ace6743..2b7a8ec 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -166,11 +166,13 @@
public void run() {
ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
key.id, lowResolution);
- if (isCanceled()) {
- // We don't call back to the provided callback in this case
- return;
- }
+
MAIN_EXECUTOR.execute(() -> {
+ if (isCanceled()) {
+ // We don't call back to the provided callback in this case
+ return;
+ }
+
mCache.put(key, thumbnail);
callback.accept(thumbnail);
onEnd();
diff --git a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
new file mode 100644
index 0000000..6862f07
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
@@ -0,0 +1,129 @@
+/*
+ * 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.interaction;
+
+import static com.android.quickstep.interaction.TutorialController.TutorialType.ASSISTANT_COMPLETE;
+
+import android.graphics.PointF;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
+import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
+
+/** A {@link TutorialController} for the Assistant tutorial. */
+final class AssistantGestureTutorialController extends TutorialController {
+
+ AssistantGestureTutorialController(AssistantGestureTutorialFragment fragment,
+ TutorialType tutorialType) {
+ super(fragment, tutorialType);
+ }
+
+ @Override
+ Integer getTitleStringId() {
+ switch (mTutorialType) {
+ case ASSISTANT:
+ return R.string.assistant_gesture_tutorial_playground_title;
+ case ASSISTANT_COMPLETE:
+ return R.string.gesture_tutorial_confirm_title;
+ }
+ return null;
+ }
+
+ @Override
+ Integer getSubtitleStringId() {
+ if (mTutorialType == TutorialType.ASSISTANT) {
+ return R.string.assistant_gesture_tutorial_playground_subtitle;
+ }
+ return null;
+ }
+
+ @Override
+ Integer getActionButtonStringId() {
+ if (mTutorialType == ASSISTANT_COMPLETE) {
+ return R.string.gesture_tutorial_action_button_label_done;
+ }
+ return null;
+ }
+
+ @Override
+ void onActionButtonClicked(View button) {
+ mTutorialFragment.closeTutorial();
+ }
+
+ @Override
+ public void onBackGestureAttempted(BackGestureResult result) {
+ switch (mTutorialType) {
+ case ASSISTANT:
+ switch (result) {
+ case BACK_COMPLETED_FROM_LEFT:
+ case BACK_COMPLETED_FROM_RIGHT:
+ case BACK_CANCELLED_FROM_LEFT:
+ case BACK_CANCELLED_FROM_RIGHT:
+ showFeedback(R.string.assistant_gesture_feedback_swipe_too_far_from_corner);
+ break;
+ }
+ break;
+ case ASSISTANT_COMPLETE:
+ if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
+ || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
+ mTutorialFragment.closeTutorial();
+ }
+ break;
+ }
+ }
+
+
+ @Override
+ public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
+ switch (mTutorialType) {
+ case ASSISTANT:
+ switch (result) {
+ case HOME_GESTURE_COMPLETED:
+ case OVERVIEW_GESTURE_COMPLETED:
+ case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
+ case HOME_OR_OVERVIEW_CANCELLED:
+ showFeedback(R.string.assistant_gesture_feedback_swipe_too_far_from_corner);
+ break;
+ case ASSISTANT_COMPLETED:
+ hideFeedback();
+ hideHandCoachingAnimation();
+ showRippleEffect(
+ () -> mTutorialFragment.changeController(ASSISTANT_COMPLETE));
+ break;
+ case ASSISTANT_NOT_STARTED_BAD_ANGLE:
+ showFeedback(R.string.assistant_gesture_feedback_swipe_not_diagonal);
+ break;
+ case ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT:
+ showFeedback(R.string.assistant_gesture_feedback_swipe_not_long_enough);
+ break;
+ }
+ break;
+ case ASSISTANT_COMPLETE:
+ if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
+ mTutorialFragment.closeTutorial();
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void setAssistantProgress(float progress) {
+ // TODO: Create an animation.
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
new file mode 100644
index 0000000..70181fb
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
@@ -0,0 +1,48 @@
+/*
+ * 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.interaction;
+
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.TutorialController.TutorialType;
+
+/** Shows the Home gesture interactive tutorial. */
+public class AssistantGestureTutorialFragment extends TutorialFragment {
+ @Override
+ int getHandAnimationResId() {
+ return R.drawable.assistant_gesture;
+ }
+
+ @Override
+ TutorialController createController(TutorialType type) {
+ return new AssistantGestureTutorialController(this, type);
+ }
+
+ @Override
+ Class<? extends TutorialController> getControllerClass() {
+ return AssistantGestureTutorialController.class;
+ }
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ if (motionEvent.getAction() == MotionEvent.ACTION_DOWN && mTutorialController != null) {
+ mTutorialController.setRippleHotspot(motionEvent.getX(), motionEvent.getY());
+ }
+ return super.onTouch(view, motionEvent);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java
deleted file mode 100644
index 486d676..0000000
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java
+++ /dev/null
@@ -1,64 +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.interaction;
-
-import android.view.View;
-
-import com.android.launcher3.R;
-import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
-
-import java.util.Optional;
-
-/**
- * An implementation of {@link BackGestureTutorialController} that defines the behavior of the
- * {@link TutorialStep#CONFIRM}.
- */
-final class BackGestureTutorialConfirmController extends BackGestureTutorialController {
-
- BackGestureTutorialConfirmController(BackGestureTutorialFragment fragment,
- BackGestureTutorialTypeInfo tutorialTypeInfo) {
- super(fragment, TutorialStep.CONFIRM, Optional.of(tutorialTypeInfo));
- }
-
- @Override
- Optional<Integer> getTitleStringId() {
- return Optional.of(mTutorialTypeInfo.get().getTutorialConfirmTitleId());
- }
-
- @Override
- Optional<Integer> getSubtitleStringId() {
- return Optional.of(mTutorialTypeInfo.get().getTutorialConfirmSubtitleId());
- }
-
- @Override
- Optional<Integer> getActionButtonStringId() {
- return Optional.of(R.string.back_gesture_tutorial_action_button_label);
- }
-
- @Override
- Optional<Integer> getActionTextButtonStringId() {
- return Optional.of(R.string.back_gesture_tutorial_action_text_button_label);
- }
-
- @Override
- void onActionButtonClicked(View button) {
- hideHandCoachingAnimation();
- if (button == mActionTextButton) {
- mFragment.startSystemNavigationSetting();
- }
- mFragment.closeTutorial();
- }
-}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 5c2e992..921e568 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -15,164 +15,143 @@
*/
package com.android.quickstep.interaction;
+import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION_COMPLETE;
+import static com.android.quickstep.interaction.TutorialController.TutorialType.LEFT_EDGE_BACK_NAVIGATION;
+
+import android.graphics.PointF;
import android.view.View;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
import com.android.launcher3.R;
-import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
-import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
+import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
-import java.util.Optional;
+/** A {@link TutorialController} for the Back tutorial. */
+final class BackGestureTutorialController extends TutorialController {
-/**
- * Defines the behavior of the particular {@link TutorialStep} and implements the transition to it.
- */
-abstract class BackGestureTutorialController {
-
- final BackGestureTutorialFragment mFragment;
- final TutorialStep mTutorialStep;
- final Optional<BackGestureTutorialTypeInfo> mTutorialTypeInfo;
- final Button mActionTextButton;
- final Button mActionButton;
- final TextView mSubtitleTextView;
- final ImageButton mCloseButton;
- final BackGestureTutorialHandAnimation mHandCoachingAnimation;
- final LinearLayout mTitlesContainer;
-
- private final TextView mTitleTextView;
- private final ImageView mHandCoachingView;
-
- BackGestureTutorialController(
- BackGestureTutorialFragment fragment,
- TutorialStep tutorialStep,
- Optional<BackGestureTutorialTypeInfo> tutorialTypeInfo) {
- mFragment = fragment;
- mTutorialStep = tutorialStep;
- mTutorialTypeInfo = tutorialTypeInfo;
-
- View rootView = fragment.getRootView();
- mActionTextButton = rootView.findViewById(
- R.id.back_gesture_tutorial_fragment_action_text_button);
- mActionButton = rootView.findViewById(R.id.back_gesture_tutorial_fragment_action_button);
- mSubtitleTextView = rootView.findViewById(
- R.id.back_gesture_tutorial_fragment_subtitle_view);
- mTitleTextView = rootView.findViewById(R.id.back_gesture_tutorial_fragment_title_view);
- mHandCoachingView = rootView.findViewById(
- R.id.back_gesture_tutorial_fragment_hand_coaching);
- mHandCoachingAnimation = mFragment.getHandAnimation();
- mHandCoachingView.bringToFront();
- mCloseButton = rootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button);
- mTitlesContainer = rootView.findViewById(
- R.id.back_gesture_tutorial_fragment_titles_container);
+ BackGestureTutorialController(BackGestureTutorialFragment fragment, TutorialType tutorialType) {
+ super(fragment, tutorialType);
}
- void transitToController() {
- updateTitles();
- updateActionButtons();
- }
-
- void hideHandCoachingAnimation() {
- mHandCoachingAnimation.stop();
- }
-
- void onGestureAttempted(BackGestureResult result) {
- if (mTutorialStep == TutorialStep.CONFIRM
- && (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
- || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT)) {
- mFragment.closeTutorial();
- return;
- }
-
- if (!mTutorialTypeInfo.isPresent()) {
- return;
- }
-
- switch (mTutorialTypeInfo.get().getTutorialType()) {
+ @Override
+ Integer getTitleStringId() {
+ switch (mTutorialType) {
case RIGHT_EDGE_BACK_NAVIGATION:
- if (result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
- hideHandCoachingAnimation();
- mFragment.changeController(
- TutorialStep.ENGAGED, TutorialType.LEFT_EDGE_BACK_NAVIGATION);
- }
+ return R.string.back_gesture_tutorial_playground_title_swipe_inward_right_edge;
+ case LEFT_EDGE_BACK_NAVIGATION:
+ return R.string.back_gesture_tutorial_playground_title_swipe_inward_left_edge;
+ case BACK_NAVIGATION_COMPLETE:
+ return R.string.gesture_tutorial_confirm_title;
+ }
+ return null;
+ }
+
+ @Override
+ Integer getSubtitleStringId() {
+ switch (mTutorialType) {
+ case RIGHT_EDGE_BACK_NAVIGATION:
+ return R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge;
+ case LEFT_EDGE_BACK_NAVIGATION:
+ return R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge;
+ case BACK_NAVIGATION_COMPLETE:
+ return R.string.back_gesture_tutorial_confirm_subtitle;
+ }
+ return null;
+ }
+
+ @Override
+ Integer getActionButtonStringId() {
+ if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
+ return R.string.gesture_tutorial_action_button_label_done;
+ }
+ return null;
+ }
+
+ @Override
+ Integer getActionTextButtonStringId() {
+ if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
+ return R.string.gesture_tutorial_action_button_label_settings;
+ }
+ return null;
+ }
+
+ @Override
+ void onActionButtonClicked(View button) {
+ mTutorialFragment.closeTutorial();
+ }
+
+ @Override
+ void onActionTextButtonClicked(View button) {
+ mTutorialFragment.startSystemNavigationSetting();
+ }
+
+ @Override
+ public void onBackGestureAttempted(BackGestureResult result) {
+ switch (mTutorialType) {
+ case RIGHT_EDGE_BACK_NAVIGATION:
+ handleAttemptFromRight(result);
break;
case LEFT_EDGE_BACK_NAVIGATION:
- if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT) {
- hideHandCoachingAnimation();
- mFragment.changeController(TutorialStep.CONFIRM);
+ handleAttemptFromLeft(result);
+ break;
+ case BACK_NAVIGATION_COMPLETE:
+ if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
+ || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
+ mTutorialFragment.closeTutorial();
}
break;
}
}
- abstract Optional<Integer> getTitleStringId();
-
- abstract Optional<Integer> getSubtitleStringId();
-
- abstract Optional<Integer> getActionButtonStringId();
-
- abstract Optional<Integer> getActionTextButtonStringId();
-
- abstract void onActionButtonClicked(View button);
-
- private void updateActionButtons() {
- updateButton(mActionButton, getActionButtonStringId(), this::onActionButtonClicked);
- updateButton(mActionTextButton, getActionTextButtonStringId(), this::onActionButtonClicked);
- }
-
- private static void updateButton(Button button, Optional<Integer> stringId,
- View.OnClickListener listener) {
- if (!stringId.isPresent()) {
- button.setVisibility(View.INVISIBLE);
- return;
+ private void handleAttemptFromRight(BackGestureResult result) {
+ switch (result) {
+ case BACK_COMPLETED_FROM_RIGHT:
+ hideFeedback();
+ hideHandCoachingAnimation();
+ showRippleEffect(
+ () -> mTutorialFragment.changeController(LEFT_EDGE_BACK_NAVIGATION));
+ break;
+ case BACK_CANCELLED_FROM_RIGHT:
+ showFeedback(R.string.back_gesture_feedback_cancelled_right_edge);
+ break;
+ case BACK_COMPLETED_FROM_LEFT:
+ case BACK_CANCELLED_FROM_LEFT:
+ case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ showFeedback(R.string.back_gesture_feedback_swipe_too_far_from_right_edge);
+ break;
+ case BACK_NOT_STARTED_IN_NAV_BAR_REGION:
+ showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
+ break;
}
-
- button.setVisibility(View.VISIBLE);
- button.setText(stringId.get());
- button.setOnClickListener(listener);
}
- private void updateTitles() {
- updateTitleView(mTitleTextView, getTitleStringId(),
- R.style.TextAppearance_BackGestureTutorial_Title);
- updateTitleView(mSubtitleTextView, getSubtitleStringId(),
- R.style.TextAppearance_BackGestureTutorial_Subtitle);
- }
-
- private static void updateTitleView(TextView textView, Optional<Integer> stringId,
- int styleId) {
- if (!stringId.isPresent()) {
- textView.setVisibility(View.GONE);
- return;
+ private void handleAttemptFromLeft(BackGestureResult result) {
+ switch (result) {
+ case BACK_COMPLETED_FROM_LEFT:
+ hideFeedback();
+ hideHandCoachingAnimation();
+ showRippleEffect(
+ () -> mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE));
+ break;
+ case BACK_CANCELLED_FROM_LEFT:
+ showFeedback(R.string.back_gesture_feedback_cancelled_left_edge);
+ break;
+ case BACK_COMPLETED_FROM_RIGHT:
+ case BACK_CANCELLED_FROM_RIGHT:
+ case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ showFeedback(R.string.back_gesture_feedback_swipe_too_far_from_left_edge);
+ break;
+ case BACK_NOT_STARTED_IN_NAV_BAR_REGION:
+ showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
+ break;
}
-
- textView.setVisibility(View.VISIBLE);
- textView.setText(stringId.get());
- textView.setTextAppearance(styleId);
}
- /**
- * Constructs {@link BackGestureTutorialController} for providing {@link TutorialType} and
- * {@link TutorialStep}.
- */
- static Optional<BackGestureTutorialController> getTutorialController(
- BackGestureTutorialFragment fragment, TutorialStep tutorialStep,
- TutorialType tutorialType) {
- BackGestureTutorialTypeInfo tutorialTypeInfo =
- BackGestureTutorialTypeInfoProvider.getTutorialTypeInfo(tutorialType);
- switch (tutorialStep) {
- case ENGAGED:
- return Optional.of(
- new BackGestureTutorialEngagedController(fragment, tutorialTypeInfo));
- case CONFIRM:
- return Optional.of(
- new BackGestureTutorialConfirmController(fragment, tutorialTypeInfo));
- default:
- throw new AssertionError("Unexpected tutorial step: " + tutorialStep);
+ @Override
+ public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
+ if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
+ if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
+ mTutorialFragment.closeTutorial();
+ }
}
}
}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java
deleted file mode 100644
index c9ee1e2..0000000
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java
+++ /dev/null
@@ -1,64 +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.interaction;
-
-import android.view.View;
-
-import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
-
-import java.util.Optional;
-
-/**
- * An implementation of {@link BackGestureTutorialController} that defines the behavior of the
- * {@link TutorialStep#ENGAGED}.
- */
-final class BackGestureTutorialEngagedController extends BackGestureTutorialController {
-
- BackGestureTutorialEngagedController(
- BackGestureTutorialFragment fragment, BackGestureTutorialTypeInfo tutorialTypeInfo) {
- super(fragment, TutorialStep.ENGAGED, Optional.of(tutorialTypeInfo));
- }
-
- @Override
- void transitToController() {
- super.transitToController();
- mHandCoachingAnimation.maybeStartLoopedAnimation(mTutorialTypeInfo.get().getTutorialType());
- }
-
- @Override
- Optional<Integer> getTitleStringId() {
- return Optional.of(mTutorialTypeInfo.get().getTutorialPlaygroundTitleId());
- }
-
- @Override
- Optional<Integer> getSubtitleStringId() {
- return Optional.of(mTutorialTypeInfo.get().getTutorialEngagedSubtitleId());
- }
-
- @Override
- Optional<Integer> getActionButtonStringId() {
- return Optional.empty();
- }
-
- @Override
- Optional<Integer> getActionTextButtonStringId() {
- return Optional.empty();
- }
-
- @Override
- void onActionButtonClicked(View button) {
- }
-}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index aeb718d..bef50ea 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -15,172 +15,34 @@
*/
package com.android.quickstep.interaction;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.graphics.Insets;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
import com.android.launcher3.R;
-import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
-import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
-
-import java.net.URISyntaxException;
-import java.util.Optional;
+import com.android.quickstep.interaction.TutorialController.TutorialType;
/** Shows the Back gesture interactive tutorial. */
-public class BackGestureTutorialFragment extends Fragment implements BackGestureAttemptCallback {
-
- private static final String LOG_TAG = "TutorialFragment";
- private static final String KEY_TUTORIAL_STEP = "tutorialStep";
- private static final String KEY_TUTORIAL_TYPE = "tutorialType";
- private static final String SYSTEM_NAVIGATION_SETTING_INTENT =
- "#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S"
- + ".:settings:fragment_args_key=gesture_system_navigation_input_summary;S"
- + ".:settings:show_fragment=com.android.settings.gestures"
- + ".SystemNavigationGestureSettings;end";
-
- private TutorialStep mTutorialStep;
- private TutorialType mTutorialType;
- private Optional<BackGestureTutorialController> mTutorialController = Optional.empty();
- private View mRootView;
- private BackGestureTutorialHandAnimation mHandCoachingAnimation;
- private EdgeBackGestureHandler mEdgeBackGestureHandler;
-
- public static BackGestureTutorialFragment newInstance(
- TutorialStep tutorialStep, TutorialType tutorialType) {
- BackGestureTutorialFragment fragment = new BackGestureTutorialFragment();
- Bundle args = new Bundle();
- args.putSerializable(KEY_TUTORIAL_STEP, tutorialStep);
- args.putSerializable(KEY_TUTORIAL_TYPE, tutorialType);
- fragment.setArguments(args);
- return fragment;
+public class BackGestureTutorialFragment extends TutorialFragment {
+ @Override
+ int getHandAnimationResId() {
+ return R.drawable.back_gesture;
}
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
- mTutorialStep = (TutorialStep) args.getSerializable(KEY_TUTORIAL_STEP);
- mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
- mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext());
- mEdgeBackGestureHandler.registerBackGestureAttemptCallback(this);
+ TutorialController createController(TutorialType type) {
+ return new BackGestureTutorialController(this, type);
}
@Override
- public View onCreateView(
- @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- super.onCreateView(inflater, container, savedInstanceState);
-
- mRootView = inflater.inflate(R.layout.back_gesture_tutorial_fragment,
- container, /* attachToRoot= */ false);
- mRootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button)
- .setOnClickListener(this::onCloseButtonClicked);
- mRootView.setOnApplyWindowInsetsListener((view, insets) -> {
- Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());
- mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right);
- return insets;
- });
- mRootView.setOnTouchListener(mEdgeBackGestureHandler);
- mHandCoachingAnimation = new BackGestureTutorialHandAnimation(getContext(), mRootView);
-
- return mRootView;
+ Class<? extends TutorialController> getControllerClass() {
+ return BackGestureTutorialController.class;
}
@Override
- public void onResume() {
- super.onResume();
- changeController(mTutorialStep, mTutorialType);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mHandCoachingAnimation.stop();
- }
-
- void onAttachedToWindow() {
- mEdgeBackGestureHandler.setViewGroupParent((ViewGroup) getRootView());
- }
-
- void onDetachedFromWindow() {
- mEdgeBackGestureHandler.setViewGroupParent(null);
- }
-
- @Override
- public void onSaveInstanceState(Bundle savedInstanceState) {
- savedInstanceState.putSerializable(KEY_TUTORIAL_STEP, mTutorialStep);
- savedInstanceState.putSerializable(KEY_TUTORIAL_TYPE, mTutorialType);
- super.onSaveInstanceState(savedInstanceState);
- }
-
- View getRootView() {
- return mRootView;
- }
-
- BackGestureTutorialHandAnimation getHandAnimation() {
- return mHandCoachingAnimation;
- }
-
- void changeController(TutorialStep tutorialStep) {
- changeController(tutorialStep, mTutorialType);
- }
-
- void changeController(TutorialStep tutorialStep, TutorialType tutorialType) {
- Optional<BackGestureTutorialController> tutorialController =
- BackGestureTutorialController.getTutorialController(/* fragment= */ this,
- tutorialStep, tutorialType);
- if (!tutorialController.isPresent()) {
- return;
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ if (motionEvent.getAction() == MotionEvent.ACTION_DOWN && mTutorialController != null) {
+ mTutorialController.setRippleHotspot(motionEvent.getX(), motionEvent.getY());
}
-
- mTutorialController = tutorialController;
- mTutorialController.get().transitToController();
- this.mTutorialStep = mTutorialController.get().mTutorialStep;
- this.mTutorialType = tutorialType;
- }
-
- @Override
- public void onBackGestureAttempted(BackGestureResult result) {
- mTutorialController.ifPresent(controller -> controller.onGestureAttempted(result));
- }
-
- void closeTutorial() {
- getActivity().finish();
- }
-
- void startSystemNavigationSetting() {
- try {
- startActivityForResult(
- Intent.parseUri(SYSTEM_NAVIGATION_SETTING_INTENT, /* flags= */ 0),
- /* requestCode= */ 0);
- } catch (URISyntaxException e) {
- Log.e(LOG_TAG, "The launch Intent Uri is wrong syntax: " + e);
- } catch (ActivityNotFoundException e) {
- Log.e(LOG_TAG, "The launch Activity not found: " + e);
- }
- }
-
- private void onCloseButtonClicked(View button) {
- closeTutorial();
- }
-
- /** Denotes the step of the tutorial. */
- enum TutorialStep {
- ENGAGED,
- CONFIRM,
- }
-
- /** Denotes the type of the tutorial. */
- enum TutorialType {
- RIGHT_EDGE_BACK_NAVIGATION,
- LEFT_EDGE_BACK_NAVIGATION,
+ return super.onTouch(view, motionEvent);
}
}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java
deleted file mode 100644
index ac8443d..0000000
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java
+++ /dev/null
@@ -1,109 +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.interaction;
-
-import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
-
-/** Defines the UI element identifiers for the particular {@link TutorialType}. */
-final class BackGestureTutorialTypeInfo {
-
- private final TutorialType mTutorialType;
- private final int mTutorialPlaygroundTitleId;
- private final int mTutorialEngagedSubtitleId;
- private final int mTutorialConfirmTitleId;
- private final int mTutorialConfirmSubtitleId;
-
- TutorialType getTutorialType() {
- return mTutorialType;
- }
-
- int getTutorialPlaygroundTitleId() {
- return mTutorialPlaygroundTitleId;
- }
-
- int getTutorialEngagedSubtitleId() {
- return mTutorialEngagedSubtitleId;
- }
-
- int getTutorialConfirmTitleId() {
- return mTutorialConfirmTitleId;
- }
-
- int getTutorialConfirmSubtitleId() {
- return mTutorialConfirmSubtitleId;
- }
-
- static Builder builder() {
- return new Builder();
- }
-
- private BackGestureTutorialTypeInfo(
- TutorialType tutorialType,
- int tutorialPlaygroundTitleId,
- int tutorialEngagedSubtitleId,
- int tutorialConfirmTitleId,
- int tutorialConfirmSubtitleId) {
- mTutorialType = tutorialType;
- mTutorialPlaygroundTitleId = tutorialPlaygroundTitleId;
- mTutorialEngagedSubtitleId = tutorialEngagedSubtitleId;
- mTutorialConfirmTitleId = tutorialConfirmTitleId;
- mTutorialConfirmSubtitleId = tutorialConfirmSubtitleId;
- }
-
- /** Builder for producing {@link BackGestureTutorialTypeInfo} objects. */
- static class Builder {
-
- private TutorialType mTutorialType;
- private Integer mTutorialPlaygroundTitleId;
- private Integer mTutorialEngagedSubtitleId;
- private Integer mTutorialConfirmTitleId;
- private Integer mTutorialConfirmSubtitleId;
-
- Builder setTutorialType(TutorialType tutorialType) {
- mTutorialType = tutorialType;
- return this;
- }
-
- Builder setTutorialPlaygroundTitleId(int stringId) {
- mTutorialPlaygroundTitleId = stringId;
- return this;
- }
-
- Builder setTutorialEngagedSubtitleId(int stringId) {
- mTutorialEngagedSubtitleId = stringId;
- return this;
- }
-
- Builder setTutorialConfirmTitleId(int stringId) {
- mTutorialConfirmTitleId = stringId;
- return this;
- }
-
- Builder setTutorialConfirmSubtitleId(int stringId) {
- mTutorialConfirmSubtitleId = stringId;
- return this;
- }
-
- BackGestureTutorialTypeInfo build() {
- return new BackGestureTutorialTypeInfo(
- mTutorialType,
- mTutorialPlaygroundTitleId,
- mTutorialEngagedSubtitleId,
- mTutorialConfirmTitleId,
- mTutorialConfirmSubtitleId);
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java
deleted file mode 100644
index 9575d83..0000000
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java
+++ /dev/null
@@ -1,59 +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.interaction;
-
-import com.android.launcher3.R;
-import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
-
-/** Provides instances of {@link BackGestureTutorialTypeInfo} for each {@link TutorialType}. */
-final class BackGestureTutorialTypeInfoProvider {
-
- private static final BackGestureTutorialTypeInfo RIGHT_EDGE_BACK_NAV_TUTORIAL_INFO =
- BackGestureTutorialTypeInfo.builder()
- .setTutorialType(TutorialType.RIGHT_EDGE_BACK_NAVIGATION)
- .setTutorialPlaygroundTitleId(
- R.string.back_gesture_tutorial_playground_title_swipe_inward_right_edge)
- .setTutorialEngagedSubtitleId(
- R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge)
- .setTutorialConfirmTitleId(R.string.back_gesture_tutorial_confirm_title)
- .setTutorialConfirmSubtitleId(R.string.back_gesture_tutorial_confirm_subtitle)
- .build();
-
- private static final BackGestureTutorialTypeInfo LEFT_EDGE_BACK_NAV_TUTORIAL_INFO =
- BackGestureTutorialTypeInfo.builder()
- .setTutorialType(TutorialType.LEFT_EDGE_BACK_NAVIGATION)
- .setTutorialPlaygroundTitleId(
- R.string.back_gesture_tutorial_playground_title_swipe_inward_left_edge)
- .setTutorialEngagedSubtitleId(
- R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge)
- .setTutorialConfirmTitleId(R.string.back_gesture_tutorial_confirm_title)
- .setTutorialConfirmSubtitleId(R.string.back_gesture_tutorial_confirm_subtitle)
- .build();
-
- static BackGestureTutorialTypeInfo getTutorialTypeInfo(TutorialType tutorialType) {
- switch (tutorialType) {
- case RIGHT_EDGE_BACK_NAVIGATION:
- return RIGHT_EDGE_BACK_NAV_TUTORIAL_INFO;
- case LEFT_EDGE_BACK_NAVIGATION:
- return LEFT_EDGE_BACK_NAV_TUTORIAL_INFO;
- default:
- throw new AssertionError("Unexpected tutorial type: " + tutorialType);
- }
- }
-
- private BackGestureTutorialTypeInfoProvider() {
- }
-}
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
index f34530e..e4b348e 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -20,7 +20,6 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.os.SystemProperties;
-import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
@@ -46,7 +45,6 @@
private final Context mContext;
private final Point mDisplaySize = new Point();
- private final int mDisplayId;
// The edge width where touch down is allowed
private int mEdgeWidth;
@@ -60,6 +58,7 @@
private final PointF mDownPoint = new PointF();
private boolean mThresholdCrossed = false;
private boolean mAllowGesture = false;
+ private BackGestureResult mDisallowedGestureReason;
private boolean mIsEnabled;
private int mLeftInset;
private int mRightInset;
@@ -91,8 +90,6 @@
EdgeBackGestureHandler(Context context) {
final Resources res = context.getResources();
mContext = context;
- mDisplayId = context.getDisplay() == null
- ? Display.DEFAULT_DISPLAY : context.getDisplay().getDisplayId();
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
@@ -126,6 +123,10 @@
mGestureCallback = callback;
}
+ void unregisterBackGestureAttemptCallback() {
+ mGestureCallback = null;
+ }
+
private LayoutParams createLayoutParams() {
Resources resources = mContext.getResources();
return new LayoutParams(
@@ -145,11 +146,13 @@
private boolean isWithinTouchRegion(int x, int y) {
// Disallow if too far from the edge
if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
+ mDisallowedGestureReason = BackGestureResult.BACK_NOT_STARTED_TOO_FAR_FROM_EDGE;
return false;
}
// Disallow if we are in the bottom gesture area
if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
+ mDisallowedGestureReason = BackGestureResult.BACK_NOT_STARTED_IN_NAV_BAR_REGION;
return false;
}
@@ -169,12 +172,12 @@
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
boolean isOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset;
+ mDisallowedGestureReason = BackGestureResult.UNKNOWN;
mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
+ mDownPoint.set(ev.getX(), ev.getY());
if (mAllowGesture) {
mEdgeBackPanel.setIsLeftPanel(isOnLeftEdge);
mEdgeBackPanel.onMotionEvent(ev);
-
- mDownPoint.set(ev.getX(), ev.getY());
mThresholdCrossed = false;
}
} else if (mAllowGesture) {
@@ -193,7 +196,6 @@
if (dy > dx && dy > mTouchSlop) {
cancelGesture(ev);
return;
-
} else if (dx > dy && dx > mTouchSlop) {
mThresholdCrossed = true;
}
@@ -206,8 +208,10 @@
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- if (!mAllowGesture && mGestureCallback != null) {
- mGestureCallback.onBackGestureAttempted(BackGestureResult.BACK_NOT_STARTED);
+ float dx = Math.abs(ev.getX() - mDownPoint.x);
+ float dy = Math.abs(ev.getY() - mDownPoint.y);
+ if (dx > dy && dx > mTouchSlop && !mAllowGesture && mGestureCallback != null) {
+ mGestureCallback.onBackGestureAttempted(mDisallowedGestureReason);
}
}
}
@@ -223,7 +227,8 @@
BACK_COMPLETED_FROM_RIGHT,
BACK_CANCELLED_FROM_LEFT,
BACK_CANCELLED_FROM_RIGHT,
- BACK_NOT_STARTED,
+ BACK_NOT_STARTED_TOO_FAR_FROM_EDGE,
+ BACK_NOT_STARTED_IN_NAV_BAR_REGION,
}
/** Callback to let the UI react to attempted back gestures. */
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index 5bf5026..0521db4 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -280,7 +280,11 @@
new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
- mPaint.setColor(context.getColor(R.color.back_arrow_color_dark));
+ int currentNightMode =
+ context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ mPaint.setColor(context.getColor(currentNightMode == Configuration.UI_MODE_NIGHT_YES
+ ? R.color.back_arrow_color_light
+ : R.color.back_arrow_color_dark));
loadDimens();
updateArrowDirection();
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 4815366..f8d9d8d 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.interaction;
+import static com.android.quickstep.interaction.TutorialFragment.KEY_TUTORIAL_TYPE;
+
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
@@ -26,26 +28,26 @@
import androidx.fragment.app.FragmentActivity;
import com.android.launcher3.R;
-import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
-import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+import com.android.quickstep.interaction.TutorialController.TutorialType;
import java.util.List;
/** Shows the gesture interactive sandbox in full screen mode. */
public class GestureSandboxActivity extends FragmentActivity {
- private BackGestureTutorialFragment mFragment;
+ private static final String LOG_TAG = "GestureSandboxActivity";
+
+ private TutorialFragment mFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.back_gesture_tutorial_activity);
+ setContentView(R.layout.gesture_tutorial_activity);
- mFragment = BackGestureTutorialFragment.newInstance(
- TutorialStep.ENGAGED, TutorialType.RIGHT_EDGE_BACK_NAVIGATION);
+ mFragment = TutorialFragment.newInstance(getTutorialType(getIntent().getExtras()));
getSupportFragmentManager().beginTransaction()
- .add(R.id.back_gesture_tutorial_fragment_container, mFragment)
+ .add(R.id.gesture_tutorial_fragment_container, mFragment)
.commit();
}
@@ -70,6 +72,19 @@
}
}
+ private TutorialType getTutorialType(Bundle extras) {
+ TutorialType defaultType = TutorialType.RIGHT_EDGE_BACK_NAVIGATION;
+
+ if (extras == null || !extras.containsKey(KEY_TUTORIAL_TYPE)) {
+ return defaultType;
+ }
+ try {
+ return TutorialType.valueOf(extras.getString(KEY_TUTORIAL_TYPE, ""));
+ } catch (IllegalArgumentException e) {
+ return defaultType;
+ }
+ }
+
private void hideSystemUI() {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
new file mode 100644
index 0000000..0edabd4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -0,0 +1,124 @@
+/*
+ * 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.interaction;
+
+import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
+
+import android.annotation.TargetApi;
+import android.graphics.PointF;
+import android.os.Build;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
+import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
+
+/** A {@link TutorialController} for the Home tutorial. */
+@TargetApi(Build.VERSION_CODES.R)
+final class HomeGestureTutorialController extends SwipeUpGestureTutorialController {
+
+ HomeGestureTutorialController(HomeGestureTutorialFragment fragment, TutorialType tutorialType) {
+ super(fragment, tutorialType);
+ }
+
+ @Override
+ Integer getTitleStringId() {
+ switch (mTutorialType) {
+ case HOME_NAVIGATION:
+ return R.string.home_gesture_tutorial_playground_title;
+ case HOME_NAVIGATION_COMPLETE:
+ return R.string.gesture_tutorial_confirm_title;
+ }
+ return null;
+ }
+
+ @Override
+ Integer getSubtitleStringId() {
+ if (mTutorialType == TutorialType.HOME_NAVIGATION) {
+ return R.string.home_gesture_tutorial_playground_subtitle;
+ }
+ return null;
+ }
+
+ @Override
+ Integer getActionButtonStringId() {
+ if (mTutorialType == HOME_NAVIGATION_COMPLETE) {
+ return R.string.gesture_tutorial_action_button_label_done;
+ }
+ return null;
+ }
+
+ @Override
+ void onActionButtonClicked(View button) {
+ mTutorialFragment.closeTutorial();
+ }
+
+ @Override
+ public void onBackGestureAttempted(BackGestureResult result) {
+ switch (mTutorialType) {
+ case HOME_NAVIGATION:
+ switch (result) {
+ case BACK_COMPLETED_FROM_LEFT:
+ case BACK_COMPLETED_FROM_RIGHT:
+ case BACK_CANCELLED_FROM_LEFT:
+ case BACK_CANCELLED_FROM_RIGHT:
+ showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
+ break;
+ }
+ break;
+ case HOME_NAVIGATION_COMPLETE:
+ if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
+ || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
+ mTutorialFragment.closeTutorial();
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
+ switch (mTutorialType) {
+ case HOME_NAVIGATION:
+ switch (result) {
+ case HOME_GESTURE_COMPLETED: {
+ animateFakeTaskViewHome(finalVelocity, () ->
+ mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE));
+ break;
+ }
+ case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
+ break;
+ case OVERVIEW_GESTURE_COMPLETED:
+ fadeOutFakeTaskView(true, () ->
+ showFeedback(R.string.home_gesture_feedback_overview_detected));
+ break;
+ case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
+ case HOME_OR_OVERVIEW_CANCELLED:
+ fadeOutFakeTaskView(false, null);
+ showFeedback(R.string.home_gesture_feedback_wrong_swipe_direction);
+ break;
+ }
+ break;
+ case HOME_NAVIGATION_COMPLETE:
+ if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
+ mTutorialFragment.closeTutorial();
+ }
+ break;
+ }
+ }
+
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
new file mode 100644
index 0000000..e2a9d12
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
@@ -0,0 +1,37 @@
+/*
+ * 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.interaction;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.TutorialController.TutorialType;
+
+/** Shows the Home gesture interactive tutorial. */
+public class HomeGestureTutorialFragment extends TutorialFragment {
+ @Override
+ int getHandAnimationResId() {
+ return R.drawable.home_gesture;
+ }
+
+ @Override
+ TutorialController createController(TutorialType type) {
+ return new HomeGestureTutorialController(this, type);
+ }
+
+ @Override
+ Class<? extends TutorialController> getControllerClass() {
+ return HomeGestureTutorialController.class;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
new file mode 100644
index 0000000..0e2312b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -0,0 +1,321 @@
+/*
+ * 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.interaction;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED;
+import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE;
+import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT;
+import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_GESTURE_COMPLETED;
+import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_NOT_STARTED_TOO_FAR_FROM_EDGE;
+import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_CANCELLED;
+import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION;
+import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED;
+import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.os.SystemClock;
+import android.view.Display;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.util.NavBarPosition;
+import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+import com.android.systemui.shared.system.QuickStepContract;
+
+/** Utility class to handle Home and Assistant gestures. */
+public class NavBarGestureHandler implements OnTouchListener,
+ TriggerSwipeUpTouchTracker.OnSwipeUpListener {
+
+ private static final String LOG_TAG = "NavBarGestureHandler";
+ private static final long RETRACT_GESTURE_ANIMATION_DURATION_MS = 300;
+
+ private final Context mContext;
+ private final Point mDisplaySize = new Point();
+ private final TriggerSwipeUpTouchTracker mSwipeUpTouchTracker;
+ private final int mBottomGestureHeight;
+ private final GestureDetector mAssistantGestureDetector;
+ private final int mAssistantAngleThreshold;
+ private final RectF mAssistantLeftRegion = new RectF();
+ private final RectF mAssistantRightRegion = new RectF();
+ private final float mAssistantDragDistThreshold;
+ private final float mAssistantFlingDistThreshold;
+ private final long mAssistantTimeThreshold;
+ private final float mAssistantSquaredSlop;
+ private final PointF mAssistantStartDragPos = new PointF();
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ private boolean mTouchCameFromAssistantCorner;
+ private boolean mTouchCameFromNavBar;
+ private boolean mPassedAssistantSlop;
+ private boolean mAssistantGestureActive;
+ private boolean mLaunchedAssistant;
+ private long mAssistantDragStartTime;
+ private float mAssistantDistance;
+ private float mAssistantTimeFraction;
+ private float mAssistantLastProgress;
+ @Nullable
+ private NavBarGestureAttemptCallback mGestureCallback;
+
+ NavBarGestureHandler(Context context) {
+ mContext = context;
+ final Display display = mContext.getDisplay();
+ final int displayRotation;
+ if (display == null) {
+ displayRotation = Surface.ROTATION_0;
+ } else {
+ displayRotation = display.getRotation();
+ display.getRealSize(mDisplaySize);
+ }
+ mSwipeUpTouchTracker =
+ new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
+ new NavBarPosition(Mode.NO_BUTTON, displayRotation),
+ null /*onInterceptTouch*/, this);
+
+ final Resources resources = context.getResources();
+ mBottomGestureHeight =
+ ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, resources);
+ mAssistantDragDistThreshold =
+ resources.getDimension(R.dimen.gestures_assistant_drag_threshold);
+ mAssistantFlingDistThreshold =
+ resources.getDimension(R.dimen.gestures_assistant_fling_threshold);
+ mAssistantTimeThreshold =
+ resources.getInteger(R.integer.assistant_gesture_min_time_threshold);
+ mAssistantAngleThreshold =
+ resources.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
+
+ mAssistantGestureDetector = new GestureDetector(context, new AssistantGestureListener());
+ int assistantWidth = resources.getDimensionPixelSize(R.dimen.gestures_assistant_width);
+ final float assistantHeight = Math.max(mBottomGestureHeight,
+ QuickStepContract.getWindowCornerRadius(resources));
+ mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mDisplaySize.y;
+ mAssistantLeftRegion.top = mAssistantRightRegion.top = mDisplaySize.y - assistantHeight;
+ mAssistantLeftRegion.left = 0;
+ mAssistantLeftRegion.right = assistantWidth;
+ mAssistantRightRegion.right = mDisplaySize.x;
+ mAssistantRightRegion.left = mDisplaySize.x - assistantWidth;
+ float slop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mAssistantSquaredSlop = slop * slop;
+ }
+
+ void registerNavBarGestureAttemptCallback(NavBarGestureAttemptCallback callback) {
+ mGestureCallback = callback;
+ }
+
+ void unregisterNavBarGestureAttemptCallback() {
+ mGestureCallback = null;
+ }
+
+ @Override
+ public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
+ if (mGestureCallback == null || mAssistantGestureActive) {
+ return;
+ }
+ finalVelocity.set(finalVelocity.x / 1000, finalVelocity.y / 1000);
+ if (mTouchCameFromNavBar) {
+ mGestureCallback.onNavBarGestureAttempted(wasFling
+ ? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED, finalVelocity);
+ } else {
+ mGestureCallback.onNavBarGestureAttempted(wasFling
+ ? HOME_NOT_STARTED_TOO_FAR_FROM_EDGE : OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE,
+ finalVelocity);
+ }
+ }
+
+ @Override
+ public void onSwipeUpCancelled() {
+ if (mGestureCallback != null && !mAssistantGestureActive) {
+ mGestureCallback.onNavBarGestureAttempted(HOME_OR_OVERVIEW_CANCELLED, new PointF());
+ }
+ }
+
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ int action = event.getAction();
+ boolean intercepted = mSwipeUpTouchTracker.interceptedTouch();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mDownPos.set(event.getX(), event.getY());
+ mLastPos.set(mDownPos);
+ mTouchCameFromAssistantCorner =
+ mAssistantLeftRegion.contains(event.getX(), event.getY())
+ || mAssistantRightRegion.contains(event.getX(), event.getY());
+ mAssistantGestureActive = mTouchCameFromAssistantCorner;
+ mTouchCameFromNavBar = !mTouchCameFromAssistantCorner
+ && mDownPos.y >= mDisplaySize.y - mBottomGestureHeight;
+ if (!mTouchCameFromNavBar && mGestureCallback != null) {
+ mGestureCallback.setNavBarGestureProgress(null);
+ }
+ mLaunchedAssistant = false;
+ mSwipeUpTouchTracker.init();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (!mAssistantGestureActive) {
+ break;
+ }
+ mLastPos.set(event.getX(), event.getY());
+
+ if (!mPassedAssistantSlop) {
+ // Normal gesture, ensure we pass the slop before we start tracking the gesture
+ if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
+ > mAssistantSquaredSlop) {
+
+ mPassedAssistantSlop = true;
+ mAssistantStartDragPos.set(mLastPos.x, mLastPos.y);
+ mAssistantDragStartTime = SystemClock.uptimeMillis();
+
+ mAssistantGestureActive = isValidAssistantGestureAngle(
+ mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y);
+ if (!mAssistantGestureActive && mGestureCallback != null) {
+ mGestureCallback.onNavBarGestureAttempted(
+ ASSISTANT_NOT_STARTED_BAD_ANGLE, new PointF());
+ }
+ }
+ } else {
+ // Movement
+ mAssistantDistance = (float) Math.hypot(mLastPos.x - mAssistantStartDragPos.x,
+ mLastPos.y - mAssistantStartDragPos.y);
+ if (mAssistantDistance >= 0) {
+ final long diff = SystemClock.uptimeMillis() - mAssistantDragStartTime;
+ mAssistantTimeFraction = Math.min(diff * 1f / mAssistantTimeThreshold, 1);
+ updateAssistantProgress();
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) {
+ mGestureCallback.onNavBarGestureAttempted(
+ HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, new PointF());
+ intercepted = true;
+ break;
+ }
+ if (mAssistantGestureActive && !mLaunchedAssistant && mGestureCallback != null) {
+ mGestureCallback.onNavBarGestureAttempted(
+ ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT, new PointF());
+ ValueAnimator animator = ValueAnimator.ofFloat(mAssistantLastProgress, 0)
+ .setDuration(RETRACT_GESTURE_ANIMATION_DURATION_MS);
+ animator.addUpdateListener(valueAnimator -> {
+ float progress = (float) valueAnimator.getAnimatedValue();
+ mGestureCallback.setAssistantProgress(progress);
+ });
+ animator.setInterpolator(Interpolators.DEACCEL_2);
+ animator.start();
+ }
+ mPassedAssistantSlop = false;
+ break;
+ }
+ if (mTouchCameFromNavBar && mGestureCallback != null) {
+ mGestureCallback.setNavBarGestureProgress(event.getY() - mDownPos.y);
+ }
+ mSwipeUpTouchTracker.onMotionEvent(event);
+ mAssistantGestureDetector.onTouchEvent(event);
+ return intercepted;
+ }
+
+ /**
+ * Determine if angle is larger than threshold for assistant detection
+ */
+ private boolean isValidAssistantGestureAngle(float deltaX, float deltaY) {
+ float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+
+ // normalize so that angle is measured clockwise from horizontal in the bottom right corner
+ // and counterclockwise from horizontal in the bottom left corner
+ angle = angle > 90 ? 180 - angle : angle;
+ return (angle > mAssistantAngleThreshold && angle < 90);
+ }
+
+ private void updateAssistantProgress() {
+ if (!mLaunchedAssistant) {
+ mAssistantLastProgress =
+ Math.min(mAssistantDistance * 1f / mAssistantDragDistThreshold, 1)
+ * mAssistantTimeFraction;
+ if (mAssistantDistance >= mAssistantDragDistThreshold && mAssistantTimeFraction >= 1) {
+ startAssistant(new PointF());
+ } else if (mGestureCallback != null) {
+ mGestureCallback.setAssistantProgress(mAssistantLastProgress);
+ }
+ }
+ }
+
+ private void startAssistant(PointF velocity) {
+ if (mGestureCallback != null) {
+ mGestureCallback.onNavBarGestureAttempted(ASSISTANT_COMPLETED, velocity);
+ }
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(VibratorWrapper.EFFECT_CLICK);
+ mLaunchedAssistant = true;
+ }
+
+ enum NavBarGestureResult {
+ UNKNOWN,
+ HOME_GESTURE_COMPLETED,
+ OVERVIEW_GESTURE_COMPLETED,
+ HOME_NOT_STARTED_TOO_FAR_FROM_EDGE,
+ OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE,
+ HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, // Side swipe on nav bar.
+ HOME_OR_OVERVIEW_CANCELLED,
+ ASSISTANT_COMPLETED,
+ ASSISTANT_NOT_STARTED_BAD_ANGLE,
+ ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT,
+ }
+
+ /** Callback to let the UI react to attempted nav bar gestures. */
+ interface NavBarGestureAttemptCallback {
+ /** Called whenever any touch is completed. */
+ void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity);
+
+ /** Indicates how far a touch originating in the nav bar has moved from the nav bar. */
+ default void setNavBarGestureProgress(@Nullable Float displacement) {}
+
+ /** Indicates the progress of an Assistant gesture. */
+ default void setAssistantProgress(float progress) {}
+ }
+
+ private class AssistantGestureListener extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ if (!mLaunchedAssistant && mTouchCameFromAssistantCorner) {
+ PointF velocity = new PointF(velocityX, velocityY);
+ if (!isValidAssistantGestureAngle(velocityX, -velocityY)) {
+ if (mGestureCallback != null) {
+ mGestureCallback.onNavBarGestureAttempted(ASSISTANT_NOT_STARTED_BAD_ANGLE,
+ velocity);
+ }
+ } else if (mAssistantDistance >= mAssistantFlingDistThreshold) {
+ mAssistantLastProgress = 1;
+ startAssistant(velocity);
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
new file mode 100644
index 0000000..c636eba
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -0,0 +1,124 @@
+/*
+ * 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.interaction;
+
+import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
+
+import android.annotation.TargetApi;
+import android.graphics.PointF;
+import android.os.Build;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
+import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
+
+/** A {@link TutorialController} for the Overview tutorial. */
+@TargetApi(Build.VERSION_CODES.R)
+final class OverviewGestureTutorialController extends SwipeUpGestureTutorialController {
+
+ OverviewGestureTutorialController(OverviewGestureTutorialFragment fragment,
+ TutorialType tutorialType) {
+ super(fragment, tutorialType);
+ }
+
+ @Override
+ Integer getTitleStringId() {
+ switch (mTutorialType) {
+ case OVERVIEW_NAVIGATION:
+ return R.string.overview_gesture_tutorial_playground_title;
+ case OVERVIEW_NAVIGATION_COMPLETE:
+ return R.string.gesture_tutorial_confirm_title;
+ }
+ return null;
+ }
+
+ @Override
+ Integer getSubtitleStringId() {
+ if (mTutorialType == TutorialType.OVERVIEW_NAVIGATION) {
+ return R.string.overview_gesture_tutorial_playground_subtitle;
+ }
+ return null;
+ }
+
+ @Override
+ Integer getActionButtonStringId() {
+ if (mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
+ return R.string.gesture_tutorial_action_button_label_done;
+ }
+ return null;
+ }
+
+ @Override
+ void onActionButtonClicked(View button) {
+ mTutorialFragment.closeTutorial();
+ }
+
+ @Override
+ public void onBackGestureAttempted(BackGestureResult result) {
+ switch (mTutorialType) {
+ case OVERVIEW_NAVIGATION:
+ switch (result) {
+ case BACK_COMPLETED_FROM_LEFT:
+ case BACK_COMPLETED_FROM_RIGHT:
+ case BACK_CANCELLED_FROM_LEFT:
+ case BACK_CANCELLED_FROM_RIGHT:
+ showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
+ break;
+ }
+ break;
+ case OVERVIEW_NAVIGATION_COMPLETE:
+ if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
+ || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
+ mTutorialFragment.closeTutorial();
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
+ switch (mTutorialType) {
+ case OVERVIEW_NAVIGATION:
+ switch (result) {
+ case HOME_GESTURE_COMPLETED: {
+ animateFakeTaskViewHome(finalVelocity, () ->
+ showFeedback(R.string.overview_gesture_feedback_home_detected));
+ break;
+ }
+ case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
+ break;
+ case OVERVIEW_GESTURE_COMPLETED:
+ fadeOutFakeTaskView(true, () ->
+ mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE));
+ break;
+ case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
+ case HOME_OR_OVERVIEW_CANCELLED:
+ fadeOutFakeTaskView(false, null);
+ showFeedback(R.string.overview_gesture_feedback_wrong_swipe_direction);
+ break;
+ }
+ break;
+ case OVERVIEW_NAVIGATION_COMPLETE:
+ if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
+ mTutorialFragment.closeTutorial();
+ }
+ break;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
new file mode 100644
index 0000000..3357b70
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
@@ -0,0 +1,37 @@
+/*
+ * 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.interaction;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.TutorialController.TutorialType;
+
+/** Shows the Overview gesture interactive tutorial. */
+public class OverviewGestureTutorialFragment extends TutorialFragment {
+ @Override
+ int getHandAnimationResId() {
+ return R.drawable.overview_gesture;
+ }
+
+ @Override
+ TutorialController createController(TutorialType type) {
+ return new OverviewGestureTutorialController(this, type);
+ }
+
+ @Override
+ Class<? extends TutorialController> getControllerClass() {
+ return OverviewGestureTutorialController.class;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
new file mode 100644
index 0000000..14e00dc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -0,0 +1,246 @@
+/*
+ * 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.interaction;
+
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION;
+import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
+import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Outline;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SwipeUpAnimationLogic;
+import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim;
+import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.TransformParams;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+@TargetApi(Build.VERSION_CODES.R)
+abstract class SwipeUpGestureTutorialController extends TutorialController {
+ private final ViewSwipeUpAnimation mViewSwipeUpAnimation;
+ private float mFakeTaskViewRadius;
+ private Rect mFakeTaskViewRect = new Rect();
+ private RunningWindowAnim mRunningWindowAnim;
+
+ SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
+ super(tutorialFragment, tutorialType);
+ RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext);
+ OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState);
+ mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
+ new GestureState(observer, -1));
+ observer.onDestroy();
+ deviceState.destroy();
+
+ DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext)
+ .getDeviceProfile(mContext)
+ .copy(mContext);
+ Insets insets = mContext.getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics()
+ .getWindowInsets()
+ .getInsets(WindowInsets.Type.systemBars());
+ dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom));
+ mViewSwipeUpAnimation.initDp(dp);
+
+ mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources());
+ mFakeTaskView.setClipToOutline(true);
+ mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius);
+ }
+ });
+ }
+
+ private void cancelRunningAnimation() {
+ if (mRunningWindowAnim != null) {
+ mRunningWindowAnim.cancel();
+ }
+ mRunningWindowAnim = null;
+ }
+
+ /** Fades the task view, optionally after animating to a fake Overview. */
+ void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) {
+ hideFeedback();
+ hideHandCoachingAnimation();
+ cancelRunningAnimation();
+ PendingAnimation anim = new PendingAnimation(300);
+ AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation, boolean isReverse) {
+ mFakeTaskView.setVisibility(View.INVISIBLE);
+ mFakeTaskView.setAlpha(1);
+ mRunningWindowAnim = null;
+ }
+ };
+ if (toOverviewFirst) {
+ anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation, boolean isReverse) {
+ PendingAnimation fadeAnim = new PendingAnimation(300);
+ fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL);
+ fadeAnim.addListener(resetTaskView);
+ AnimatorSet animset = fadeAnim.buildAnim();
+ animset.setStartDelay(100);
+ animset.start();
+ mRunningWindowAnim = RunningWindowAnim.wrap(animset);
+ }
+ });
+ } else {
+ anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
+ anim.addListener(resetTaskView);
+ }
+ if (onEndRunnable != null) {
+ anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
+ }
+ AnimatorSet animset = anim.buildAnim();
+ animset.start();
+ mRunningWindowAnim = RunningWindowAnim.wrap(animset);
+ }
+
+ void animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable) {
+ hideFeedback();
+ hideHandCoachingAnimation();
+ cancelRunningAnimation();
+ RectFSpringAnim rectAnim =
+ mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
+ // After home animation finishes, fade out and run onEndRunnable.
+ rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable(
+ () -> fadeOutFakeTaskView(false, onEndRunnable)));
+ mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim);
+ }
+
+ @Override
+ public void setNavBarGestureProgress(@Nullable Float displacement) {
+ if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE
+ || mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
+ mFakeTaskView.setVisibility(View.INVISIBLE);
+ } else {
+ mFakeTaskView.setVisibility(View.VISIBLE);
+ if (mRunningWindowAnim == null) {
+ mViewSwipeUpAnimation.updateDisplacement(displacement);
+ }
+ }
+ }
+
+ class ViewSwipeUpAnimation extends SwipeUpAnimationLogic {
+
+ ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState,
+ GestureState gestureState) {
+ super(context, deviceState, gestureState, new FakeTransformParams());
+ }
+
+ void initDp(DeviceProfile dp) {
+ initTransitionEndpoints(dp);
+ mTaskViewSimulator.setPreviewBounds(
+ new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets());
+ }
+
+ @Override
+ public void updateFinalShift() {
+ float progress = mCurrentShift.value / mDragLengthFactor;
+ mWindowTransitionController.setPlayFraction(progress);
+ mTaskViewSimulator.apply(mTransformParams);
+ }
+
+ AnimatedFloat getCurrentShift() {
+ return mCurrentShift;
+ }
+
+ RectFSpringAnim handleSwipeUpToHome(PointF velocity) {
+ PointF velocityPxPerMs = new PointF(velocity.x, velocity.y);
+ float currentShift = mCurrentShift.value;
+ final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
+ * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
+ float distanceToTravel = (1 - currentShift) * mTransitionDragLength;
+
+ // we want the page's snap velocity to approximately match the velocity at
+ // which the user flings, so we scale the duration by a value near to the
+ // derivative of the scroll interpolator at zero, ie. 2.
+ long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
+ long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
+ HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) {
+ @Override
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
+ }
+
+ @NonNull
+ @Override
+ public RectF getWindowTargetRect() {
+ int fakeHomeIconSizePx = mDp.allAppsIconSizePx;
+ int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2;
+ int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3);
+ return new RectF(fakeHomeIconLeft, fakeHomeIconTop,
+ fakeHomeIconLeft + fakeHomeIconSizePx,
+ fakeHomeIconTop + fakeHomeIconSizePx);
+ }
+ };
+ RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory);
+ windowAnim.start(mContext, velocityPxPerMs);
+ return windowAnim;
+ }
+ }
+
+ private class FakeTransformParams extends TransformParams {
+
+ @Override
+ public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
+ SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
+ proxy.onBuildTargetParams(builder, null, this);
+ return new SurfaceParams[] {builder.build()};
+ }
+
+ @Override
+ public void applySurfaceParams(SurfaceParams[] params) {
+ SurfaceParams p = params[0];
+ mFakeTaskView.setAnimationMatrix(p.matrix);
+ mFakeTaskViewRect.set(p.windowCrop);
+ mFakeTaskViewRadius = p.cornerRadius;
+ mFakeTaskView.invalidateOutline();
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
new file mode 100644
index 0000000..c1918c2
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -0,0 +1,221 @@
+/*
+ * 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.interaction;
+
+import android.content.Context;
+import android.graphics.drawable.RippleDrawable;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
+import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
+
+abstract class TutorialController implements BackGestureAttemptCallback,
+ NavBarGestureAttemptCallback {
+
+ private static final int FEEDBACK_VISIBLE_MS = 3000;
+ private static final int FEEDBACK_ANIMATION_MS = 500;
+ private static final int RIPPLE_VISIBLE_MS = 300;
+
+ final TutorialFragment mTutorialFragment;
+ TutorialType mTutorialType;
+ final Context mContext;
+
+ final ImageButton mCloseButton;
+ final TextView mTitleTextView;
+ final TextView mSubtitleTextView;
+ final TextView mFeedbackView;
+ final View mFakeTaskView;
+ final View mRippleView;
+ final RippleDrawable mRippleDrawable;
+ final TutorialHandAnimation mHandCoachingAnimation;
+ final ImageView mHandCoachingView;
+ final Button mActionTextButton;
+ final Button mActionButton;
+ private final Runnable mHideFeedbackRunnable;
+
+ TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
+ mTutorialFragment = tutorialFragment;
+ mTutorialType = tutorialType;
+ mContext = mTutorialFragment.getContext();
+
+ View rootView = tutorialFragment.getRootView();
+ mCloseButton = rootView.findViewById(R.id.gesture_tutorial_fragment_close_button);
+ mCloseButton.setOnClickListener(button -> mTutorialFragment.closeTutorial());
+ mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view);
+ mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view);
+ mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
+ mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
+ mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
+ mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
+ mHandCoachingAnimation = tutorialFragment.getHandAnimation();
+ mHandCoachingView = rootView.findViewById(R.id.gesture_tutorial_fragment_hand_coaching);
+ mHandCoachingView.bringToFront();
+ mActionTextButton =
+ rootView.findViewById(R.id.gesture_tutorial_fragment_action_text_button);
+ mActionButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button);
+
+ mHideFeedbackRunnable =
+ () -> mFeedbackView.animate().alpha(0).setDuration(FEEDBACK_ANIMATION_MS)
+ .withEndAction(this::showHandCoachingAnimation).start();
+ }
+
+ void setTutorialType(TutorialType tutorialType) {
+ mTutorialType = tutorialType;
+ }
+
+ @Nullable
+ Integer getTitleStringId() {
+ return null;
+ }
+
+ @Nullable
+ Integer getSubtitleStringId() {
+ return null;
+ }
+
+ @Nullable
+ Integer getActionButtonStringId() {
+ return null;
+ }
+
+ @Nullable
+ Integer getActionTextButtonStringId() {
+ return null;
+ }
+
+ void showFeedback(int resId) {
+ hideHandCoachingAnimation();
+ mFeedbackView.setText(resId);
+ mFeedbackView.animate().alpha(1).setDuration(FEEDBACK_ANIMATION_MS).start();
+ mFeedbackView.removeCallbacks(mHideFeedbackRunnable);
+ mFeedbackView.postDelayed(mHideFeedbackRunnable, FEEDBACK_VISIBLE_MS);
+ }
+
+ void hideFeedback() {
+ mFeedbackView.setText(null);
+ mFeedbackView.removeCallbacks(mHideFeedbackRunnable);
+ mFeedbackView.clearAnimation();
+ mFeedbackView.setAlpha(0);
+ }
+
+ void setRippleHotspot(float x, float y) {
+ mRippleDrawable.setHotspot(x, y);
+ }
+
+ void showRippleEffect(@Nullable Runnable onCompleteRunnable) {
+ mRippleDrawable.setState(
+ new int[] {android.R.attr.state_pressed, android.R.attr.state_enabled});
+ mRippleView.postDelayed(() -> {
+ mRippleDrawable.setState(new int[] {});
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ }, RIPPLE_VISIBLE_MS);
+ }
+
+ void onActionButtonClicked(View button) {}
+
+ void onActionTextButtonClicked(View button) {}
+
+ void showHandCoachingAnimation() {
+ if (isComplete()) {
+ return;
+ }
+ mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
+ }
+
+ void hideHandCoachingAnimation() {
+ mHandCoachingAnimation.stop();
+ mHandCoachingView.setVisibility(View.INVISIBLE);
+ }
+
+ @CallSuper
+ void transitToController() {
+ hideFeedback();
+ updateTitles();
+ updateActionButtons();
+
+ if (isComplete()) {
+ hideHandCoachingAnimation();
+ } else {
+ showHandCoachingAnimation();
+ }
+ }
+
+ private void updateTitles() {
+ updateTitleView(mTitleTextView, getTitleStringId(),
+ R.style.TextAppearance_GestureTutorial_Title);
+ updateTitleView(mSubtitleTextView, getSubtitleStringId(),
+ R.style.TextAppearance_GestureTutorial_Subtitle);
+ }
+
+ private void updateTitleView(TextView textView, @Nullable Integer stringId, int styleId) {
+ if (stringId == null) {
+ textView.setVisibility(View.GONE);
+ return;
+ }
+
+ textView.setVisibility(View.VISIBLE);
+ textView.setText(stringId);
+ textView.setTextAppearance(styleId);
+ }
+
+ private void updateActionButtons() {
+ updateButton(mActionButton, getActionButtonStringId(), this::onActionButtonClicked);
+ updateButton(
+ mActionTextButton, getActionTextButtonStringId(), this::onActionTextButtonClicked);
+ }
+
+ private void updateButton(Button button, @Nullable Integer stringId, OnClickListener listener) {
+ if (stringId == null) {
+ button.setVisibility(View.INVISIBLE);
+ return;
+ }
+
+ button.setVisibility(View.VISIBLE);
+ button.setText(stringId);
+ button.setOnClickListener(listener);
+ }
+
+ private boolean isComplete() {
+ return mTutorialType == TutorialType.BACK_NAVIGATION_COMPLETE
+ || mTutorialType == TutorialType.HOME_NAVIGATION_COMPLETE
+ || mTutorialType == TutorialType.OVERVIEW_NAVIGATION_COMPLETE
+ || mTutorialType == TutorialType.ASSISTANT_COMPLETE;
+ }
+
+ /** Denotes the type of the tutorial. */
+ enum TutorialType {
+ RIGHT_EDGE_BACK_NAVIGATION,
+ LEFT_EDGE_BACK_NAVIGATION,
+ BACK_NAVIGATION_COMPLETE,
+ HOME_NAVIGATION,
+ HOME_NAVIGATION_COMPLETE,
+ OVERVIEW_NAVIGATION,
+ OVERVIEW_NAVIGATION_COMPLETE,
+ ASSISTANT,
+ ASSISTANT_COMPLETE
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
new file mode 100644
index 0000000..9a8264d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -0,0 +1,185 @@
+/*
+ * 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.interaction;
+
+import android.content.Intent;
+import android.graphics.Insets;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.TutorialController.TutorialType;
+
+abstract class TutorialFragment extends Fragment implements OnTouchListener {
+
+ private static final String LOG_TAG = "TutorialFragment";
+ static final String KEY_TUTORIAL_TYPE = "tutorial_type";
+
+ TutorialType mTutorialType;
+ @Nullable TutorialController mTutorialController = null;
+ View mRootView;
+ TutorialHandAnimation mHandCoachingAnimation;
+ EdgeBackGestureHandler mEdgeBackGestureHandler;
+ NavBarGestureHandler mNavBarGestureHandler;
+
+ public static TutorialFragment newInstance(TutorialType tutorialType) {
+ TutorialFragment fragment = getFragmentForTutorialType(tutorialType);
+ if (fragment == null) {
+ fragment = new BackGestureTutorialFragment();
+ tutorialType = TutorialType.RIGHT_EDGE_BACK_NAVIGATION;
+ }
+ Bundle args = new Bundle();
+ args.putSerializable(KEY_TUTORIAL_TYPE, tutorialType);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Nullable
+ private static TutorialFragment getFragmentForTutorialType(TutorialType tutorialType) {
+ switch (tutorialType) {
+ case RIGHT_EDGE_BACK_NAVIGATION:
+ case LEFT_EDGE_BACK_NAVIGATION:
+ case BACK_NAVIGATION_COMPLETE:
+ return new BackGestureTutorialFragment();
+ case HOME_NAVIGATION:
+ case HOME_NAVIGATION_COMPLETE:
+ return new HomeGestureTutorialFragment();
+ case OVERVIEW_NAVIGATION:
+ case OVERVIEW_NAVIGATION_COMPLETE:
+ return new OverviewGestureTutorialFragment();
+ case ASSISTANT:
+ case ASSISTANT_COMPLETE:
+ return new AssistantGestureTutorialFragment();
+ default:
+ Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name());
+ }
+ return null;
+ }
+
+ abstract int getHandAnimationResId();
+
+ abstract TutorialController createController(TutorialType type);
+
+ abstract Class<? extends TutorialController> getControllerClass();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
+ mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
+ mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext());
+ mNavBarGestureHandler = new NavBarGestureHandler(getContext());
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mEdgeBackGestureHandler.unregisterBackGestureAttemptCallback();
+ mNavBarGestureHandler.unregisterNavBarGestureAttemptCallback();
+ }
+
+ @Override
+ public View onCreateView(
+ @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+
+ mRootView = inflater.inflate(R.layout.gesture_tutorial_fragment, container, false);
+ mRootView.setOnApplyWindowInsetsListener((view, insets) -> {
+ Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());
+ mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right);
+ return insets;
+ });
+ mRootView.setOnTouchListener(this);
+ mHandCoachingAnimation = new TutorialHandAnimation(getContext(), mRootView,
+ getHandAnimationResId());
+ return mRootView;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ changeController(mTutorialType);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mHandCoachingAnimation.stop();
+ }
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ // Note: Using logical or to ensure both functions get called.
+ return mEdgeBackGestureHandler.onTouch(view, motionEvent)
+ | mNavBarGestureHandler.onTouch(view, motionEvent);
+ }
+
+ void onAttachedToWindow() {
+ mEdgeBackGestureHandler.setViewGroupParent((ViewGroup) getRootView());
+ }
+
+ void onDetachedFromWindow() {
+ mEdgeBackGestureHandler.setViewGroupParent(null);
+ }
+
+ void changeController(TutorialType tutorialType) {
+ if (getControllerClass().isInstance(mTutorialController)) {
+ mTutorialController.setTutorialType(tutorialType);
+ } else {
+ mTutorialController = createController(tutorialType);
+ }
+ mTutorialController.transitToController();
+ mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController);
+ mNavBarGestureHandler.registerNavBarGestureAttemptCallback(mTutorialController);
+ mTutorialType = tutorialType;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ savedInstanceState.putSerializable(KEY_TUTORIAL_TYPE, mTutorialType);
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ View getRootView() {
+ return mRootView;
+ }
+
+ TutorialHandAnimation getHandAnimation() {
+ return mHandCoachingAnimation;
+ }
+
+ void closeTutorial() {
+ FragmentActivity activity = getActivity();
+ if (activity != null) {
+ activity.finish();
+ }
+ }
+
+ void startSystemNavigationSetting() {
+ startActivity(new Intent("com.android.settings.GESTURE_NAVIGATION_SETTINGS"));
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java b/quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java
similarity index 68%
rename from quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java
rename to quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java
index d03811d..c810e43 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialHandAnimation.java
@@ -25,12 +25,12 @@
import androidx.core.content.ContextCompat;
import com.android.launcher3.R;
-import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+import com.android.quickstep.interaction.TutorialController.TutorialType;
import java.time.Duration;
/** Hand coaching animation. */
-final class BackGestureTutorialHandAnimation {
+final class TutorialHandAnimation {
// A delay for waiting the Activity fully launches.
private static final Duration ANIMATION_START_DELAY = Duration.ofMillis(300L);
@@ -38,29 +38,19 @@
private final ImageView mHandCoachingView;
private final AnimatedVectorDrawable mGestureAnimation;
- private boolean mIsAnimationPlayed = false;
-
- BackGestureTutorialHandAnimation(Context context, View rootView) {
- mHandCoachingView = rootView.findViewById(
- R.id.back_gesture_tutorial_fragment_hand_coaching);
- mGestureAnimation = (AnimatedVectorDrawable) ContextCompat.getDrawable(context,
- R.drawable.back_gesture);
+ TutorialHandAnimation(Context context, View rootView, int resId) {
+ mHandCoachingView = rootView.findViewById(R.id.gesture_tutorial_fragment_hand_coaching);
+ mGestureAnimation = (AnimatedVectorDrawable) ContextCompat.getDrawable(context, resId);
}
- boolean isRunning() {
- return mGestureAnimation.isRunning();
- }
-
- /**
- * Starts animation if the playground is launched for the first time.
- */
- void maybeStartLoopedAnimation(TutorialType tutorialType) {
- if (isRunning() || mIsAnimationPlayed) {
- return;
+ /** [Re]starts animation for the given tutorial. */
+ void startLoopedAnimation(TutorialType tutorialType) {
+ mHandCoachingView.setVisibility(View.VISIBLE);
+ if (mGestureAnimation.isRunning()) {
+ stop();
}
- mIsAnimationPlayed = true;
- clearAnimationCallbacks();
+ mGestureAnimation.clearAnimationCallbacks();
mGestureAnimation.registerAnimationCallback(
new Animatable2.AnimationCallback() {
@Override
@@ -78,17 +68,11 @@
float rotationY = tutorialType == TutorialType.LEFT_EDGE_BACK_NAVIGATION ? 180f : 0f;
mHandCoachingView.setRotationY(rotationY);
mHandCoachingView.setImageDrawable(mGestureAnimation);
- mHandCoachingView.postDelayed(() -> mGestureAnimation.start(),
- ANIMATION_START_DELAY.toMillis());
- }
-
- private void clearAnimationCallbacks() {
- mGestureAnimation.clearAnimationCallbacks();
+ mHandCoachingView.postDelayed(mGestureAnimation::start, ANIMATION_START_DELAY.toMillis());
}
void stop() {
- mIsAnimationPlayed = false;
- clearAnimationCallbacks();
+ mGestureAnimation.clearAnimationCallbacks();
mGestureAnimation.stop();
}
}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 58bb980..eac45e9 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,91 +16,437 @@
package com.android.quickstep.logging;
-import static android.stats.launcher.nano.Launcher.ALLAPPS;
-import static android.stats.launcher.nano.Launcher.BACKGROUND;
-import static android.stats.launcher.nano.Launcher.HOME;
-import static android.stats.launcher.nano.Launcher.OVERVIEW;
+import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
+import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
import android.content.Context;
+import android.util.Log;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.ItemInfo;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.Utilities;
import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FolderIcon;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.logging.StatsLogUtils;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.LogConfig;
+import com.android.systemui.shared.system.SysUiStatsLog;
import java.util.ArrayList;
+import java.util.Optional;
+import java.util.OptionalInt;
/**
- * This method calls the StatsLog hidden method until they are made available public.
+ * This class calls StatsLog compile time generated methods.
*
* To see if the logs are properly sent to statsd, execute following command.
- * $ adb root && adb shell statsd
- * $ adb shell cmd stats print-logs
- * $ adb logcat | grep statsd OR $ adb logcat -b stats
+ * $ wwdebug (to turn on the logcat printout)
+ * $ wwlogcat (see logcat with grep filter on)
+ * $ statsd_testdrive (see how ww is writing the proto to statsd buffer)
*/
public class StatsLogCompatManager extends StatsLogManager {
- private static final int SUPPORTED_TARGET_DEPTH = 2;
private static final String TAG = "StatsLog";
- private static final boolean DEBUG = false;
+ private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
+
private static Context sContext;
+ private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
+ // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
+ // from nano to lite, bake constant to prevent robo test failure.
+ private static final int DEFAULT_PAGE_INDEX = -2;
+ private static final int FOLDER_HIERARCHY_OFFSET = 100;
+ private static final int SEARCH_RESULT_HIERARCHY_OFFSET = 200;
+
public StatsLogCompatManager(Context context) {
sContext = context;
}
@Override
- public void log(LauncherEvent eventId, LauncherAtom.ItemInfo item) {
- // Call StatsLog method
+ public StatsLogger logger() {
+ return new StatsCompatLogger();
}
+ /**
+ * Logs a ranking event and accompanying {@link InstanceId} and package name.
+ */
@Override
- public void verify() {
- if (!(StatsLogUtils.LAUNCHER_STATE_ALLAPPS == ALLAPPS
- && StatsLogUtils.LAUNCHER_STATE_BACKGROUND == BACKGROUND
- && StatsLogUtils.LAUNCHER_STATE_OVERVIEW == OVERVIEW
- && StatsLogUtils.LAUNCHER_STATE_HOME == HOME)) {
- throw new IllegalStateException(
- "StatsLogUtil constants doesn't match enums in launcher.proto");
- }
+ public void log(EventEnum rankingEvent, InstanceId instanceId, @Nullable String packageName,
+ int position) {
+ SysUiStatsLog.write(SysUiStatsLog.RANKING_SELECTED,
+ rankingEvent.getId() /* event_id = 1; */,
+ packageName /* package_name = 2; */,
+ instanceId.getId() /* instance_id = 3; */,
+ position /* position_picked = 4; */);
}
/**
* Logs the workspace layout information on the model thread.
*/
+ @Override
public void logSnapshot() {
LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
new SnapshotWorker());
}
private class SnapshotWorker extends BaseModelUpdateTask {
+ private final InstanceId mInstanceId;
+ SnapshotWorker() {
+ mInstanceId = new InstanceIdSequence(
+ 1 << 20 /*InstanceId.INSTANCE_ID_MAX*/).newInstanceId();
+ }
+
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone();
ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone();
ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone();
-
for (ItemInfo info : workspaceItems) {
- LauncherAtom.ItemInfo atomInfo = info.buildProto(null, null);
- // call StatsLog method
+ LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
+ writeSnapshot(atomInfo, mInstanceId);
}
for (FolderInfo fInfo : folders) {
- for (ItemInfo info : fInfo.contents) {
- LauncherAtom.ItemInfo atomInfo = info.buildProto(null, fInfo);
- // call StatsLog method
- }
+ try {
+ ArrayList<WorkspaceItemInfo> folderContents =
+ (ArrayList) Executors.MAIN_EXECUTOR.submit(fInfo.contents::clone).get();
+ for (ItemInfo info : folderContents) {
+ LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
+ writeSnapshot(atomInfo, mInstanceId);
+ }
+ } catch (Exception e) { }
}
for (ItemInfo info : appWidgets) {
- LauncherAtom.ItemInfo atomInfo = info.buildProto(null, null);
- // call StatsLog method
+ LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
+ writeSnapshot(atomInfo, mInstanceId);
}
}
}
+
+ private static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
+ if (IS_VERBOSE) {
+ Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
+ }
+ if (!Utilities.ATLEAST_R) {
+ return;
+ }
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT,
+ LAUNCHER_WORKSPACE_SNAPSHOT.getId() /* event_id */,
+ info.getItemCase().getNumber() /* target_id */,
+ instanceId.getId() /* instance_id */,
+ 0 /* uid */,
+ getPackageName(info) /* package_name */,
+ getComponentName(info) /* component_name */,
+ getGridX(info, false) /* grid_x */,
+ getGridY(info, false) /* grid_y */,
+ getPageId(info) /* page_id */,
+ getGridX(info, true) /* grid_x_parent */,
+ getGridY(info, true) /* grid_y_parent */,
+ getParentPageId(info) /* page_id_parent */,
+ getHierarchy(info) /* hierarchy */,
+ info.getIsWork() /* is_work_profile */,
+ info.getAttribute().getNumber() /* origin */,
+ getCardinality(info) /* cardinality */,
+ info.getWidget().getSpanX(),
+ info.getWidget().getSpanY());
+ }
+
+ /**
+ * Helps to construct and write statsd compatible log message.
+ */
+ private static class StatsCompatLogger implements StatsLogger {
+
+ private static final ItemInfo DEFAULT_ITEM_INFO = new ItemInfo();
+ private ItemInfo mItemInfo = DEFAULT_ITEM_INFO;
+ private InstanceId mInstanceId = DEFAULT_INSTANCE_ID;
+ private OptionalInt mRank = OptionalInt.empty();
+ private Optional<ContainerInfo> mContainerInfo = Optional.empty();
+ private int mSrcState = LAUNCHER_STATE_UNSPECIFIED;
+ private int mDstState = LAUNCHER_STATE_UNSPECIFIED;
+ private Optional<FromState> mFromState = Optional.empty();
+ private Optional<ToState> mToState = Optional.empty();
+ private Optional<String> mEditText = Optional.empty();
+
+ @Override
+ public StatsLogger withItemInfo(ItemInfo itemInfo) {
+ if (mContainerInfo.isPresent()) {
+ throw new IllegalArgumentException(
+ "ItemInfo and ContainerInfo are mutual exclusive; cannot log both.");
+ }
+ this.mItemInfo = itemInfo;
+ return this;
+ }
+
+ @Override
+ public StatsLogger withInstanceId(InstanceId instanceId) {
+ this.mInstanceId = instanceId;
+ return this;
+ }
+
+ @Override
+ public StatsLogger withRank(int rank) {
+ this.mRank = OptionalInt.of(rank);
+ return this;
+ }
+
+ @Override
+ public StatsLogger withSrcState(int srcState) {
+ this.mSrcState = srcState;
+ return this;
+ }
+
+ @Override
+ public StatsLogger withDstState(int dstState) {
+ this.mDstState = dstState;
+ return this;
+ }
+
+ @Override
+ public StatsLogger withContainerInfo(ContainerInfo containerInfo) {
+ if (mItemInfo != DEFAULT_ITEM_INFO) {
+ throw new IllegalArgumentException(
+ "ItemInfo and ContainerInfo are mutual exclusive; cannot log both.");
+ }
+ this.mContainerInfo = Optional.of(containerInfo);
+ return this;
+ }
+
+ @Override
+ public StatsLogger withFromState(FromState fromState) {
+ this.mFromState = Optional.of(fromState);
+ return this;
+ }
+
+ @Override
+ public StatsLogger withToState(ToState toState) {
+ this.mToState = Optional.of(toState);
+ return this;
+ }
+
+ @Override
+ public StatsLogger withEditText(String editText) {
+ this.mEditText = Optional.of(editText);
+ return this;
+ }
+
+ @Override
+ public void log(EventEnum event) {
+ if (!Utilities.ATLEAST_R) {
+ return;
+ }
+
+ if (mItemInfo.container < 0) {
+ // Item is not within a folder. Write to StatsLog in same thread.
+ write(event, mInstanceId, applyOverwrites(mItemInfo.buildProto()), mSrcState,
+ mDstState);
+ } else {
+ // Item is inside the folder, fetch folder info in a BG thread
+ // and then write to StatsLog.
+ LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
+ new BaseModelUpdateTask() {
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel,
+ AllAppsList apps) {
+ FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
+ write(event, mInstanceId,
+ applyOverwrites(mItemInfo.buildProto(folderInfo)),
+ mSrcState, mDstState);
+ }
+ });
+ }
+ }
+
+ private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) {
+ LauncherAtom.ItemInfo.Builder itemInfoBuilder =
+ (LauncherAtom.ItemInfo.Builder) atomInfo.toBuilder();
+
+ mRank.ifPresent(itemInfoBuilder::setRank);
+ mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
+
+ if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) {
+ FolderIcon.Builder folderIconBuilder = (FolderIcon.Builder) itemInfoBuilder
+ .getFolderIcon()
+ .toBuilder();
+ mFromState.ifPresent(folderIconBuilder::setFromLabelState);
+ mToState.ifPresent(folderIconBuilder::setToLabelState);
+ mEditText.ifPresent(folderIconBuilder::setLabelInfo);
+ itemInfoBuilder.setFolderIcon(folderIconBuilder);
+ }
+ return itemInfoBuilder.build();
+ }
+
+ private void write(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo,
+ int srcState, int dstState) {
+ if (IS_VERBOSE) {
+ String name = (event instanceof Enum) ? ((Enum) event).name() :
+ event.getId() + "";
+
+ Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID
+ ? String.format("\n%s (State:%s->%s)\n%s", name, getStateString(srcState),
+ getStateString(dstState), atomInfo)
+ : String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s", name,
+ getStateString(srcState), getStateString(dstState), instanceId,
+ atomInfo));
+ }
+
+ SysUiStatsLog.write(
+ SysUiStatsLog.LAUNCHER_EVENT,
+ SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */,
+ srcState,
+ dstState,
+ null /* launcher extensions, deprecated */,
+ false /* quickstep_enabled, deprecated */,
+ event.getId() /* event_id */,
+ atomInfo.getItemCase().getNumber() /* target_id */,
+ instanceId.getId() /* instance_id TODO */,
+ 0 /* uid TODO */,
+ getPackageName(atomInfo) /* package_name */,
+ getComponentName(atomInfo) /* component_name */,
+ getGridX(atomInfo, false) /* grid_x */,
+ getGridY(atomInfo, false) /* grid_y */,
+ getPageId(atomInfo) /* page_id */,
+ getGridX(atomInfo, true) /* grid_x_parent */,
+ getGridY(atomInfo, true) /* grid_y_parent */,
+ getParentPageId(atomInfo) /* page_id_parent */,
+ getHierarchy(atomInfo) /* hierarchy */,
+ atomInfo.getIsWork() /* is_work_profile */,
+ atomInfo.getRank() /* rank */,
+ atomInfo.getFolderIcon().getFromLabelState().getNumber() /* fromState */,
+ atomInfo.getFolderIcon().getToLabelState().getNumber() /* toState */,
+ atomInfo.getFolderIcon().getLabelInfo() /* edittext */,
+ getCardinality(atomInfo) /* cardinality */);
+ }
+ }
+
+ private static int getCardinality(LauncherAtom.ItemInfo info) {
+ switch (info.getContainerInfo().getContainerCase()){
+ case PREDICTED_HOTSEAT_CONTAINER:
+ return info.getContainerInfo().getPredictedHotseatContainer().getCardinality();
+ case SEARCH_RESULT_CONTAINER:
+ return info.getContainerInfo().getSearchResultContainer().getQueryLength();
+ default:
+ return info.getFolderIcon().getCardinality();
+ }
+ }
+
+ private static String getPackageName(LauncherAtom.ItemInfo info) {
+ switch (info.getItemCase()) {
+ case APPLICATION:
+ return info.getApplication().getPackageName();
+ case SHORTCUT:
+ return info.getShortcut().getShortcutName();
+ case WIDGET:
+ return info.getWidget().getPackageName();
+ case TASK:
+ return info.getTask().getPackageName();
+ default:
+ return null;
+ }
+ }
+
+ private static String getComponentName(LauncherAtom.ItemInfo info) {
+ switch (info.getItemCase()) {
+ case APPLICATION:
+ return info.getApplication().getComponentName();
+ case SHORTCUT:
+ return info.getShortcut().getShortcutName();
+ case WIDGET:
+ return info.getWidget().getComponentName();
+ case TASK:
+ return info.getTask().getComponentName();
+ default:
+ return null;
+ }
+ }
+
+ private static int getGridX(LauncherAtom.ItemInfo info, boolean parent) {
+ if (info.getContainerInfo().getContainerCase() == FOLDER) {
+ if (parent) {
+ return info.getContainerInfo().getFolder().getWorkspace().getGridX();
+ } else {
+ return info.getContainerInfo().getFolder().getGridX();
+ }
+ } else {
+ return info.getContainerInfo().getWorkspace().getGridX();
+ }
+ }
+
+ private static int getGridY(LauncherAtom.ItemInfo info, boolean parent) {
+ if (info.getContainerInfo().getContainerCase() == FOLDER) {
+ if (parent) {
+ return info.getContainerInfo().getFolder().getWorkspace().getGridY();
+ } else {
+ return info.getContainerInfo().getFolder().getGridY();
+ }
+ } else {
+ return info.getContainerInfo().getWorkspace().getGridY();
+ }
+ }
+
+ private static int getPageId(LauncherAtom.ItemInfo info) {
+ switch (info.getContainerInfo().getContainerCase()) {
+ case FOLDER:
+ return info.getContainerInfo().getFolder().getPageIndex();
+ default:
+ return info.getContainerInfo().getWorkspace().getPageIndex();
+ }
+ }
+
+ private static int getParentPageId(LauncherAtom.ItemInfo info) {
+ switch (info.getContainerInfo().getContainerCase()) {
+ case FOLDER:
+ return info.getContainerInfo().getFolder().getWorkspace().getPageIndex();
+ case SEARCH_RESULT_CONTAINER:
+ return info.getContainerInfo().getSearchResultContainer().getWorkspace()
+ .getPageIndex();
+ default:
+ return info.getContainerInfo().getWorkspace().getPageIndex();
+ }
+ }
+
+ private static int getHierarchy(LauncherAtom.ItemInfo info) {
+ if (info.getContainerInfo().getContainerCase() == FOLDER) {
+ return info.getContainerInfo().getFolder().getParentContainerCase().getNumber()
+ + FOLDER_HIERARCHY_OFFSET;
+ } else if (info.getContainerInfo().getContainerCase() == SEARCH_RESULT_CONTAINER) {
+ return info.getContainerInfo().getSearchResultContainer().getParentContainerCase()
+ .getNumber() + SEARCH_RESULT_HIERARCHY_OFFSET;
+ } else {
+ return info.getContainerInfo().getContainerCase().getNumber();
+ }
+ }
+
+ private static String getStateString(int state) {
+ switch (state) {
+ case LAUNCHER_UICHANGED__DST_STATE__BACKGROUND:
+ return "BACKGROUND";
+ case LAUNCHER_UICHANGED__DST_STATE__HOME:
+ return "HOME";
+ case LAUNCHER_UICHANGED__DST_STATE__OVERVIEW:
+ return "OVERVIEW";
+ case LAUNCHER_UICHANGED__DST_STATE__ALLAPPS:
+ return "ALLAPPS";
+ default:
+ return "INVALID";
+
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
index b1c72ce..dfb8c1d 100644
--- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
+++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
@@ -28,9 +28,11 @@
public class ActivityInitListener<T extends BaseActivity> implements SchedulerCallback<T> {
- private final BiPredicate<T, Boolean> mOnInitListener;
+ private BiPredicate<T, Boolean> mOnInitListener;
private final ActivityTracker<T> mActivityTracker;
+ private boolean mIsRegistered = false;
+
/**
* @param onInitListener a callback made when the activity is initialized. The callback should
* return true to continue receiving callbacks (ie. for if the activity is
@@ -43,27 +45,46 @@
}
@Override
- public boolean init(T activity, boolean alreadyOnHome) {
+ public final boolean init(T activity, boolean alreadyOnHome) {
+ if (!mIsRegistered) {
+ return false;
+ }
+ return handleInit(activity, alreadyOnHome);
+ }
+
+ protected boolean handleInit(T activity, boolean alreadyOnHome) {
return mOnInitListener.test(activity, alreadyOnHome);
}
/**
* Registers the activity-created listener. If the activity is already created, then the
* callback provided in the constructor will be called synchronously.
+ * @param intent The intent that will be used to initialize the activity, if the activity
+ * doesn't already exist. We add the callback as an extra on this intent.
*/
- public void register() {
- mActivityTracker.schedule(this);
+ public void register(Intent intent) {
+ mIsRegistered = true;
+ mActivityTracker.runCallbackWhenActivityExists(this, intent);
}
+ /**
+ * After calling this, we won't {@link #init} even when the activity is ready.
+ */
public void unregister() {
- mActivityTracker.clearReference(this);
+ mIsRegistered = false;
+ mOnInitListener = null;
}
+ /**
+ * Starts the given intent with the provided animation. Unlike {@link #register(Intent)}, this
+ * method will not call {@link #init} if the activity already exists, it will only call it when
+ * we get handleIntent() for the provided intent that we're starting.
+ */
public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
Context context, Handler handler, long duration) {
- register();
+ mIsRegistered = true;
Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
- context.startActivity(addToIntent(new Intent((intent))), options);
+ context.startActivity(addToIntent(new Intent(intent)), options);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index f5fbf28..e998e9a 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -19,6 +19,7 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.ClipData;
@@ -39,6 +40,8 @@
import com.android.launcher3.BuildConfig;
import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.BitmapUtil;
import java.io.File;
import java.io.FileOutputStream;
@@ -53,14 +56,18 @@
public class ImageActionUtils {
private static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".overview.fileprovider";
+ private static final long FILE_LIFE = 1000L /*ms*/ * 60L /*s*/ * 60L /*m*/ * 24L /*h*/;
+ private static final String SUB_FOLDER = "Overview";
+ private static final String BASE_NAME = "overview_image_";
/**
* Saves screenshot to location determine by SystemUiProxy
*/
public static void saveScreenshot(SystemUiProxy systemUiProxy, Bitmap screenshot,
Rect screenshotBounds,
- Insets visibleInsets, int taskId) {
- systemUiProxy.handleImageAsScreenshot(screenshot, screenshotBounds, visibleInsets, taskId);
+ Insets visibleInsets, Task.TaskKey task) {
+ systemUiProxy.handleImageBundleAsScreenshot(BitmapUtil.hardwareBitmapToBundle(screenshot),
+ screenshotBounds, visibleInsets, task);
}
/**
@@ -85,8 +92,14 @@
@WorkerThread
public static void persistBitmapAndStartActivity(Context context, Bitmap bitmap, Rect crop,
Intent intent, BiFunction<Uri, Intent, Intent[]> uriToIntentMap, String tag) {
- context.startActivities(
- uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent));
+ Intent[] intents = uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent);
+
+ // Work around b/159412574
+ if (intents.length == 1) {
+ context.startActivity(intents[0]);
+ } else {
+ context.startActivities(intents);
+ }
}
/**
@@ -101,10 +114,13 @@
*/
@WorkerThread
public static Uri getImageUri(Bitmap bitmap, Rect crop, Context context, String tag) {
+ clearOldCacheFiles(context);
Bitmap croppedBitmap = cropBitmap(bitmap, crop);
int cropHash = crop == null ? 0 : crop.hashCode();
- String baseName = "image_" + bitmap.hashCode() + "_" + cropHash + ".png";
- File file = new File(context.getCacheDir(), baseName);
+ String baseName = BASE_NAME + bitmap.hashCode() + "_" + cropHash + ".png";
+ File parent = new File(context.getCacheDir(), SUB_FOLDER);
+ parent.mkdir();
+ File file = new File(parent, baseName);
try (FileOutputStream fos = new FileOutputStream(file)) {
croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
@@ -155,15 +171,30 @@
intent = new Intent();
}
ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
+ new String[]{"image/png"}),
new ClipData.Item(uri));
intent.setAction(Intent.ACTION_SEND)
.setComponent(null)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
.setType("image/png")
- .setFlags(FLAG_GRANT_READ_URI_PERMISSION)
.putExtra(Intent.EXTRA_STREAM, uri)
.setClipData(clipdata);
return new Intent[]{Intent.createChooser(intent, null).addFlags(FLAG_ACTIVITY_NEW_TASK)};
}
+
+ private static void clearOldCacheFiles(Context context) {
+ THREAD_POOL_EXECUTOR.execute(() -> {
+ File parent = new File(context.getCacheDir(), SUB_FOLDER);
+ File[] files = parent.listFiles((File f, String s) -> s.startsWith(BASE_NAME));
+ if (files != null) {
+ for (File file: files) {
+ if (file.lastModified() + FILE_LIFE < System.currentTimeMillis()) {
+ file.delete();
+ }
+ }
+ }
+ });
+
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 1f1a999..ae19d73 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -18,30 +18,19 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Rect;
-
-import androidx.annotation.AnyThread;
-import androidx.annotation.IntDef;
+import android.view.View;
+import android.view.ViewGroup;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.SysUINavigationMode;
-import java.lang.annotation.Retention;
-
public class LayoutUtils {
- private static final int MULTI_WINDOW_STRATEGY_HALF_SCREEN = 1;
- private static final int MULTI_WINDOW_STRATEGY_DEVICE_PROFILE = 2;
-
- @Retention(SOURCE)
- @IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE})
- private @interface MultiWindowStrategy {}
-
/**
* The height for the swipe up motion
*/
@@ -53,107 +42,13 @@
return swipeHeight;
}
- public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) {
- float extraSpace;
- if (dp.isVerticalBarLayout()) {
- extraSpace = 0;
- } else {
- Resources res = context.getResources();
-
- if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
- //TODO: this needs to account for the swipe gesture height and accessibility
- // UI when shown.
- extraSpace = 0;
- } else {
- extraSpace = getDefaultSwipeHeight(context, dp) + dp.workspacePageIndicatorHeight
- + res.getDimensionPixelSize(
- R.dimen.dynamic_grid_hotseat_extra_vertical_size)
- + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
- }
- }
- calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
- }
-
- public static void calculateFallbackTaskSize(Context context, DeviceProfile dp, Rect outRect) {
- calculateTaskSize(context, dp, 0, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE, outRect);
- }
-
- @AnyThread
- public static void calculateTaskSize(Context context, DeviceProfile dp,
- float extraVerticalSpace, @MultiWindowStrategy int multiWindowStrategy, Rect outRect) {
- float taskWidth, taskHeight, paddingHorz;
- Resources res = context.getResources();
- Rect insets = dp.getInsets();
- final boolean overviewActionsEnabled = ENABLE_OVERVIEW_ACTIONS.get();
-
- if (dp.isMultiWindowMode) {
- if (multiWindowStrategy == MULTI_WINDOW_STRATEGY_HALF_SCREEN) {
- DeviceProfile fullDp = dp.getFullScreenProfile();
- // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
- // account for system insets
- taskWidth = fullDp.availableWidthPx;
- taskHeight = fullDp.availableHeightPx;
- float halfDividerSize = res.getDimension(R.dimen.multi_window_task_divider_size)
- / 2;
-
- if (fullDp.isLandscape) {
- taskWidth = taskWidth / 2 - halfDividerSize;
- } else {
- taskHeight = taskHeight / 2 - halfDividerSize;
- }
- } else {
- // multiWindowStrategy == MULTI_WINDOW_STRATEGY_DEVICE_PROFILE
- taskWidth = dp.widthPx;
- taskHeight = dp.heightPx;
- }
- paddingHorz = res.getDimension(R.dimen.multi_window_task_card_horz_space);
- } else {
- taskWidth = dp.availableWidthPx;
- taskHeight = dp.availableHeightPx;
-
- final int paddingResId;
- if (dp.isVerticalBarLayout()) {
- paddingResId = R.dimen.landscape_task_card_horz_space;
- } else if (overviewActionsEnabled && removeShelfFromOverview(context)) {
- paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
- } else {
- paddingResId = R.dimen.portrait_task_card_horz_space;
- }
- paddingHorz = res.getDimension(paddingResId);
- }
-
- float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
- float bottomMargin = thumbnailBottomMargin(context);
-
- float paddingVert = overviewActionsEnabled && removeShelfFromOverview(context)
- ? 0 : res.getDimension(R.dimen.task_card_vert_space);
-
- // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
- // we override the insets ourselves.
- int launcherVisibleWidth = dp.widthPx - insets.left - insets.right;
- int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
-
- float availableHeight = launcherVisibleHeight
- - topIconMargin - extraVerticalSpace - paddingVert - bottomMargin;
- float availableWidth = launcherVisibleWidth - paddingHorz;
-
- float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
- float outWidth = scale * taskWidth;
- float outHeight = scale * taskHeight;
-
- // Center in the visible space
- float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
- float y = insets.top + Math.max(topIconMargin,
- (launcherVisibleHeight - extraVerticalSpace - outHeight - bottomMargin) / 2);
- outRect.set(Math.round(x), Math.round(y),
- Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
- }
-
- public static int getShelfTrackingDistance(Context context, DeviceProfile dp) {
+ public static int getShelfTrackingDistance(Context context, DeviceProfile dp,
+ PagedOrientationHandler orientationHandler) {
// Track the bottom of the window.
if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
Rect taskSize = new Rect();
- calculateLauncherTaskSize(context, dp, taskSize);
+ LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize,
+ orientationHandler);
return (dp.heightPx - taskSize.height()) / 2;
}
int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
@@ -163,14 +58,16 @@
}
/**
- * Get the margin that the task thumbnail view should use.
- * @return the margin in pixels.
+ * Recursively sets view and all children enabled/disabled.
+ * @param view Top most parent view to change.
+ * @param enabled True = enable, False = disable.
*/
- public static int thumbnailBottomMargin(Context context) {
- if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
- return context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height);
- } else {
- return 0;
+ public static void setViewEnabled(View view, boolean enabled) {
+ view.setEnabled(enabled);
+ if (view instanceof ViewGroup) {
+ for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
+ setViewEnabled(((ViewGroup) view).getChildAt(i), enabled);
+ }
}
}
}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 7d52571..a5d4568 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -19,11 +19,13 @@
import android.content.Context;
import android.content.res.Resources;
+import android.util.Log;
import android.view.MotionEvent;
import com.android.launcher3.Alarm;
import com.android.launcher3.R;
import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.testing.TestProtocol;
/**
* Given positions along x- or y-axis, tracks velocity and acceleration and determines when there is
@@ -84,6 +86,9 @@
mSpeedSlow = res.getDimension(R.dimen.motion_pause_detector_speed_slow);
mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "creating alarm");
+ }
mForcePauseTimeout = new Alarm();
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
mMakePauseHarderToTrigger = makePauseHarderToTrigger;
@@ -120,6 +125,9 @@
* @param pointerIndex Index for the pointer being tracked in the motion event
*/
public void addPosition(MotionEvent ev, int pointerIndex) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "setting alarm");
+ }
mForcePauseTimeout.setAlarm(mMakePauseHarderToTrigger
? HARDER_TRIGGER_TIMEOUT
: FORCE_PAUSE_TIMEOUT);
@@ -167,6 +175,9 @@
}
private void updatePaused(boolean isPaused) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "updatePaused: " + isPaused);
+ }
if (mDisallowPause) {
isPaused = false;
}
@@ -188,6 +199,9 @@
setOnMotionPauseListener(null);
mIsPaused = mHasEverBeenPaused = false;
mSlowStartTime = 0;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "canceling alarm");
+ }
mForcePauseTimeout.cancelAlarm();
}
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
index 74e6b29..0a98e1b 100644
--- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
@@ -35,6 +35,11 @@
mDisplayRotation = info.rotation;
}
+ public NavBarPosition(SysUINavigationMode.Mode mode, int displayRotation) {
+ mMode = mode;
+ mDisplayRotation = displayRotation;
+ }
+
public boolean isRightEdge() {
return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90;
}
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
new file mode 100644
index 0000000..9ed2bbe
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -0,0 +1,173 @@
+/*
+ * 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.util;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
+import static com.android.launcher3.AbstractFloatingView.getOpenView;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.HINT_STATE;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+
+import android.content.SharedPreferences;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.util.OnboardingPrefs;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.views.AllAppsEduView;
+
+/**
+ * Extends {@link OnboardingPrefs} for quickstep-specific onboarding data.
+ */
+public class QuickstepOnboardingPrefs extends OnboardingPrefs<BaseQuickstepLauncher> {
+
+ public QuickstepOnboardingPrefs(BaseQuickstepLauncher launcher, SharedPreferences sharedPrefs) {
+ super(launcher, sharedPrefs);
+
+ StateManager<LauncherState> stateManager = launcher.getStateManager();
+ if (!getBoolean(HOME_BOUNCE_SEEN)) {
+ stateManager.addStateListener(new StateListener<LauncherState>() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ boolean swipeUpEnabled = SysUINavigationMode.INSTANCE
+ .get(mLauncher).getMode().hasGestures;
+ LauncherState prevState = stateManager.getLastState();
+
+ if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
+ && finalState == ALL_APPS && prevState == NORMAL) ||
+ hasReachedMaxCount(HOME_BOUNCE_COUNT))) {
+ mSharedPrefs.edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
+ stateManager.removeStateListener(this);
+ }
+ }
+ });
+ }
+
+ boolean shelfBounceSeen = getBoolean(SHELF_BOUNCE_SEEN);
+ if (!shelfBounceSeen && ENABLE_OVERVIEW_ACTIONS.get()
+ && removeShelfFromOverview(launcher)) {
+ // There's no shelf in overview, so don't bounce it (can't get to all apps anyway).
+ shelfBounceSeen = true;
+ mSharedPrefs.edit().putBoolean(SHELF_BOUNCE_SEEN, shelfBounceSeen).apply();
+ }
+ if (!shelfBounceSeen) {
+ stateManager.addStateListener(new StateListener<LauncherState>() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ LauncherState prevState = stateManager.getLastState();
+
+ if ((finalState == ALL_APPS && prevState == OVERVIEW) ||
+ hasReachedMaxCount(SHELF_BOUNCE_COUNT)) {
+ mSharedPrefs.edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply();
+ stateManager.removeStateListener(this);
+ }
+ }
+ });
+ }
+
+ if (!hasReachedMaxCount(ALL_APPS_COUNT)) {
+ stateManager.addStateListener(new StateListener<LauncherState>() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState == ALL_APPS) {
+ if (incrementEventCount(ALL_APPS_COUNT)) {
+ stateManager.removeStateListener(this);
+ mLauncher.getScrimView().updateDragHandleVisibility();
+ }
+ }
+ }
+ });
+ }
+
+ if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() && !hasReachedMaxCount(
+ HOTSEAT_DISCOVERY_TIP_COUNT)) {
+ stateManager.addStateListener(new StateListener<LauncherState>() {
+ boolean mFromAllApps = false;
+
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {
+ mFromAllApps = mLauncher.getStateManager().getCurrentStableState() == ALL_APPS;
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ HotseatPredictionController client = mLauncher.getHotseatPredictionController();
+ if (mFromAllApps && finalState == NORMAL && client.hasPredictions()) {
+ if (incrementEventCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
+ client.showEdu();
+ stateManager.removeStateListener(this);
+ }
+ }
+ }
+ });
+ }
+
+ if (SysUINavigationMode.getMode(launcher) == NO_BUTTON
+ && FeatureFlags.ENABLE_ALL_APPS_EDU.get()) {
+ stateManager.addStateListener(new StateListener<LauncherState>() {
+ private static final int MAX_NUM_SWIPES_TO_TRIGGER_EDU = 3;
+
+ // Counts the number of consecutive swipes on nav bar without moving screens.
+ private int mCount = 0;
+ private boolean mShouldIncreaseCount;
+
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {
+ if (toState == NORMAL) {
+ return;
+ }
+ mShouldIncreaseCount = toState == HINT_STATE
+ && launcher.getWorkspace().getNextPage() == Workspace.DEFAULT_PAGE;
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState == NORMAL) {
+ if (mCount >= MAX_NUM_SWIPES_TO_TRIGGER_EDU) {
+ if (getOpenView(mLauncher, TYPE_ALL_APPS_EDU) == null) {
+ AllAppsEduView.show(launcher);
+ }
+ mCount = 0;
+ }
+ return;
+ }
+
+ if (mShouldIncreaseCount && finalState == HINT_STATE) {
+ mCount++;
+ } else {
+ mCount = 0;
+ }
+
+ if (finalState == ALL_APPS) {
+ AllAppsEduView view = getOpenView(mLauncher, TYPE_ALL_APPS_EDU);
+ if (view != null) {
+ view.close(false);
+ }
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
new file mode 100644
index 0000000..d822b6c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -0,0 +1,543 @@
+/*
+ * 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.util;
+
+import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
+import static android.view.OrientationEventListener.ORIENTATION_UNKNOWN;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static com.android.launcher3.logging.LoggerUtils.extractObjectNameAndAddress;
+import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.WindowBounds;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.systemui.shared.system.ConfigurationCompat;
+
+import java.lang.annotation.Retention;
+import java.util.function.IntConsumer;
+
+/**
+ * Container to hold orientation/rotation related information for Launcher.
+ * This is not meant to be an abstraction layer for applying different functionality between
+ * the different orientation/rotations. For that see {@link PagedOrientationHandler}
+ *
+ * This class has initial default state assuming the device and foreground app have
+ * no ({@link Surface#ROTATION_0} rotation.
+ */
+public final class RecentsOrientedState implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private static final String TAG = "RecentsOrientedState";
+ private static final boolean DEBUG = true;
+
+ private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAutoRotateSetting();
+ }
+ };
+ @Retention(SOURCE)
+ @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
+ public @interface SurfaceRotation {}
+
+ private PagedOrientationHandler mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+
+ private @SurfaceRotation int mTouchRotation = ROTATION_0;
+ private @SurfaceRotation int mDisplayRotation = ROTATION_0;
+ private @SurfaceRotation int mRecentsActivityRotation = ROTATION_0;
+
+ // Launcher activity supports multiple orientation, but fallback activity does not
+ private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY = 1 << 0;
+ // Multiple orientation is only supported if density is < 600
+ private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY = 1 << 1;
+ // Shared prefs for rotation, only if activity supports it
+ private static final int FLAG_HOME_ROTATION_ALLOWED_IN_PREFS = 1 << 2;
+ // If the user has enabled system rotation
+ private static final int FLAG_SYSTEM_ROTATION_ALLOWED = 1 << 3;
+ // Multiple orientation is not supported in multiwindow mode
+ private static final int FLAG_MULTIWINDOW_ROTATION_ALLOWED = 1 << 4;
+ // Whether to rotation sensor is supported on the device
+ private static final int FLAG_ROTATION_WATCHER_SUPPORTED = 1 << 5;
+ // Whether to enable rotation watcher when multi-rotation is supported
+ private static final int FLAG_ROTATION_WATCHER_ENABLED = 1 << 6;
+ // Enable home rotation for UI tests, ignoring home rotation value from prefs
+ private static final int FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING = 1 << 7;
+ // Whether the swipe gesture is running, so the recents would stay locked in the
+ // current orientation
+ private static final int FLAG_SWIPE_UP_NOT_RUNNING = 1 << 8;
+
+ private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE =
+ FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY
+ | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
+
+ // State for which rotation watcher will be enabled. We skip it when home rotation or
+ // multi-window is enabled as in that case, activity itself rotates.
+ private static final int VALUE_ROTATION_WATCHER_ENABLED =
+ MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE | FLAG_SYSTEM_ROTATION_ALLOWED
+ | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED
+ | FLAG_SWIPE_UP_NOT_RUNNING;
+
+ private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private final SharedPreferences mSharedPrefs;
+ private final OrientationEventListener mOrientationListener;
+
+ private final Matrix mTmpMatrix = new Matrix();
+
+ private int mFlags;
+ private int mPreviousRotation = ROTATION_0;
+
+ @Nullable private Configuration mActivityConfiguration;
+
+ /**
+ * @param rotationChangeListener Callback for receiving rotation events when rotation watcher
+ * is enabled
+ * @see #setRotationWatcherEnabled(boolean)
+ */
+ public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy,
+ IntConsumer rotationChangeListener) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mSharedPrefs = Utilities.getPrefs(context);
+ mOrientationListener = new OrientationEventListener(context) {
+ @Override
+ public void onOrientationChanged(int degrees) {
+ int newRotation = getRotationForUserDegreesRotated(degrees, mPreviousRotation);
+ if (newRotation != mPreviousRotation) {
+ mPreviousRotation = newRotation;
+ rotationChangeListener.accept(newRotation);
+ }
+ }
+ };
+
+ mFlags = sizeStrategy.rotationSupportedByActivity
+ ? FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY : 0;
+
+ Resources res = context.getResources();
+ int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
+ * res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE;
+ if (originalSmallestWidth < 600) {
+ mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
+ }
+ mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
+ initFlags();
+ }
+
+ /**
+ * Sets the configuration for the recents activity, which could affect the activity's rotation
+ * @see #update(int, int)
+ */
+ public boolean setActivityConfiguration(Configuration activityConfiguration) {
+ mActivityConfiguration = activityConfiguration;
+ return update(mTouchRotation, mDisplayRotation);
+ }
+
+ /**
+ * Sets if the host is in multi-window mode
+ */
+ public void setMultiWindowMode(boolean isMultiWindow) {
+ setFlag(FLAG_MULTIWINDOW_ROTATION_ALLOWED, isMultiWindow);
+ }
+
+ /**
+ * Sets if the swipe up gesture is currently running or not
+ */
+ public boolean setGestureActive(boolean isGestureActive) {
+ setFlag(FLAG_SWIPE_UP_NOT_RUNNING, !isGestureActive);
+ return update(mTouchRotation, mDisplayRotation);
+ }
+
+ /**
+ * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
+ * @param touchRotation The rotation the nav bar region that is touched is in
+ * @param displayRotation Rotation of the display/device
+ *
+ * @return true if there was any change in the internal state as a result of this call,
+ * false otherwise
+ */
+ public boolean update(
+ @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation) {
+ mRecentsActivityRotation = inferRecentsActivityRotation(displayRotation);
+ mDisplayRotation = displayRotation;
+ mTouchRotation = touchRotation;
+ mPreviousRotation = touchRotation;
+
+ PagedOrientationHandler oldHandler = mOrientationHandler;
+ if (mRecentsActivityRotation == mTouchRotation
+ || (canRecentsActivityRotate() && (mFlags & FLAG_SWIPE_UP_NOT_RUNNING) != 0)) {
+ mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+ if (DEBUG) {
+ Log.d(TAG, "current RecentsOrientedState: " + this);
+ }
+ } else if (mTouchRotation == ROTATION_90) {
+ mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
+ } else if (mTouchRotation == ROTATION_270) {
+ mOrientationHandler = PagedOrientationHandler.SEASCAPE;
+ } else {
+ mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "current RecentsOrientedState: " + this);
+ }
+ return oldHandler != mOrientationHandler;
+ }
+
+ @SurfaceRotation
+ private int inferRecentsActivityRotation(@SurfaceRotation int displayRotation) {
+ if (isRecentsActivityRotationAllowed()) {
+ return mActivityConfiguration == null
+ ? displayRotation
+ : ConfigurationCompat.getWindowConfigurationRotation(mActivityConfiguration);
+ } else {
+ return ROTATION_0;
+ }
+ }
+
+ private void setFlag(int mask, boolean enabled) {
+ boolean wasRotationEnabled = !TestProtocol.sDisableSensorRotation
+ && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED
+ && !canRecentsActivityRotate();
+ if (enabled) {
+ mFlags |= mask;
+ } else {
+ mFlags &= ~mask;
+ }
+
+ boolean isRotationEnabled = !TestProtocol.sDisableSensorRotation
+ && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED
+ && !canRecentsActivityRotate();
+ if (wasRotationEnabled != isRotationEnabled) {
+ UI_HELPER_EXECUTOR.execute(() -> {
+ if (isRotationEnabled) {
+ mOrientationListener.enable();
+ } else {
+ mOrientationListener.disable();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+ if (ALLOW_ROTATION_PREFERENCE_KEY.equals(s)) {
+ updateHomeRotationSetting();
+ }
+ }
+
+ private void updateAutoRotateSetting() {
+ setFlag(FLAG_SYSTEM_ROTATION_ALLOWED, Settings.System.getInt(mContentResolver,
+ Settings.System.ACCELEROMETER_ROTATION, 1) == 1);
+ }
+
+ private void updateHomeRotationSetting() {
+ setFlag(FLAG_HOME_ROTATION_ALLOWED_IN_PREFS,
+ mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false));
+ }
+
+ private void initFlags() {
+ SysUINavigationMode.Mode currentMode = SysUINavigationMode.getMode(mContext);
+ boolean rotationWatcherSupported = mOrientationListener.canDetectOrientation() &&
+ currentMode != TWO_BUTTONS;
+ setFlag(FLAG_ROTATION_WATCHER_SUPPORTED, rotationWatcherSupported);
+
+ // initialize external flags
+ updateAutoRotateSetting();
+ updateHomeRotationSetting();
+ }
+
+ /**
+ * Initializes any system values and registers corresponding change listeners. It must be
+ * paired with {@link #destroyListeners()} call
+ */
+ public void initListeners() {
+ if (isMultipleOrientationSupportedByDevice()) {
+ mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
+ mContentResolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
+ false, mSystemAutoRotateObserver);
+ }
+ initFlags();
+ }
+
+ /**
+ * Unregisters any previously registered listeners.
+ */
+ public void destroyListeners() {
+ if (isMultipleOrientationSupportedByDevice()) {
+ mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
+ }
+ setRotationWatcherEnabled(false);
+ }
+
+ public void forceAllowRotationForTesting(boolean forceAllow) {
+ setFlag(FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING, forceAllow);
+ }
+
+ @SurfaceRotation
+ public int getDisplayRotation() {
+ return mDisplayRotation;
+ }
+
+ @SurfaceRotation
+ public int getTouchRotation() {
+ return mTouchRotation;
+ }
+
+ @SurfaceRotation
+ public int getRecentsActivityRotation() {
+ return mRecentsActivityRotation;
+ }
+
+ public boolean isMultipleOrientationSupportedByDevice() {
+ return (mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
+ == MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE;
+ }
+
+ public boolean isRecentsActivityRotationAllowed() {
+ // Activity rotation is allowed if the multi-simulated-rotation is not supported
+ // (fallback recents or tablets) or activity rotation is enabled by various settings.
+ return ((mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
+ != MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
+ || (mFlags & (FLAG_HOME_ROTATION_ALLOWED_IN_PREFS
+ | FLAG_MULTIWINDOW_ROTATION_ALLOWED
+ | FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING)) != 0;
+ }
+
+ /**
+ * Returns true if the activity can rotate, if allowed by system rotation settings
+ */
+ public boolean canRecentsActivityRotate() {
+ return (mFlags & FLAG_SYSTEM_ROTATION_ALLOWED) != 0 && isRecentsActivityRotationAllowed();
+ }
+
+ /**
+ * Enables or disables the rotation watcher for listening to rotation callbacks
+ */
+ public void setRotationWatcherEnabled(boolean isEnabled) {
+ setFlag(FLAG_ROTATION_WATCHER_ENABLED, isEnabled);
+ }
+
+ /**
+ * Returns the scale and pivot so that the provided taskRect can fit the provided full size
+ */
+ public float getFullScreenScaleAndPivot(Rect taskView, DeviceProfile dp, PointF outPivot) {
+ Rect insets = dp.getInsets();
+ float fullWidth = dp.widthPx - insets.left - insets.right;
+ float fullHeight = dp.heightPx - insets.top - insets.bottom;
+
+ if (dp.isMultiWindowMode) {
+ WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(mContext);
+ outPivot.set(bounds.availableSize.x, bounds.availableSize.y);
+ } else {
+ outPivot.set(fullWidth, fullHeight);
+ }
+ float scale = Math.min(outPivot.x / taskView.width(), outPivot.y / taskView.height());
+ // We also scale the preview as part of fullScreenParams, so account for that as well.
+ if (fullWidth > 0) {
+ scale = scale * dp.widthPx / fullWidth;
+ }
+
+ if (scale == 1) {
+ outPivot.set(fullWidth / 2, fullHeight / 2);
+ } else if (dp.isMultiWindowMode) {
+ float denominator = 1 / (scale - 1);
+ // Ensure that the task aligns to right bottom for the root view
+ float y = (scale * taskView.bottom - fullHeight) * denominator;
+ float x = (scale * taskView.right - fullWidth) * denominator;
+ outPivot.set(x, y);
+ } else {
+ float factor = scale / (scale - 1);
+ outPivot.set(taskView.left * factor, taskView.top * factor);
+ }
+ return scale;
+ }
+
+ public PagedOrientationHandler getOrientationHandler() {
+ return mOrientationHandler;
+ }
+
+ /**
+ * For landscape, since the navbar is already in a vertical position, we don't have to do any
+ * rotations as the change in Y coordinate is what is read. We only flip the sign of the
+ * y coordinate to make it match existing behavior of swipe to the top to go previous
+ */
+ public void flipVertical(MotionEvent ev) {
+ mTmpMatrix.setScale(1, -1);
+ ev.transform(mTmpMatrix);
+ }
+
+ /**
+ * Creates a matrix to transform the given motion event specified by degrees.
+ * If inverse is {@code true}, the inverse of that matrix will be applied
+ */
+ public void transformEvent(float degrees, MotionEvent ev, boolean inverse) {
+ mTmpMatrix.setRotate(inverse ? -degrees : degrees);
+ ev.transform(mTmpMatrix);
+
+ // TODO: Add scaling back in based on degrees
+ /*
+ if (getWidth() > 0 && getHeight() > 0) {
+ float scale = ((float) getWidth()) / getHeight();
+ transform.postScale(scale, 1 / scale);
+ }
+ */
+ }
+
+ @SurfaceRotation
+ public static int getRotationForUserDegreesRotated(float degrees, int currentRotation) {
+ if (degrees == ORIENTATION_UNKNOWN) {
+ return currentRotation;
+ }
+
+ int threshold = 70;
+ switch (currentRotation) {
+ case ROTATION_0:
+ if (degrees > 180 && degrees < (360 - threshold)) {
+ return ROTATION_90;
+ }
+ if (degrees < 180 && degrees > threshold) {
+ return ROTATION_270;
+ }
+ break;
+ case ROTATION_270:
+ if (degrees < (90 - threshold) ||
+ (degrees > (270 + threshold) && degrees < 360)) {
+ return ROTATION_0;
+ }
+ if (degrees > (90 + threshold) && degrees < 180) {
+ return ROTATION_180;
+ }
+ // flip from seascape to landscape
+ if (degrees > (180 + threshold) && degrees < 360) {
+ return ROTATION_90;
+ }
+ break;
+ case ROTATION_180:
+ if (degrees < (180 - threshold)) {
+ return ROTATION_270;
+ }
+ if (degrees > (180 + threshold)) {
+ return ROTATION_90;
+ }
+ break;
+ case ROTATION_90:
+ if (degrees < (270 - threshold) && degrees > 90) {
+ return ROTATION_180;
+ }
+ if (degrees > (270 + threshold) && degrees < 360
+ || (degrees >= 0 && degrees < threshold)) {
+ return ROTATION_0;
+ }
+ // flip from landscape to seascape
+ if (degrees > threshold && degrees < 180) {
+ return ROTATION_270;
+ }
+ break;
+ }
+
+ return currentRotation;
+ }
+
+ public boolean isDisplayPhoneNatural() {
+ return mDisplayRotation == Surface.ROTATION_0 || mDisplayRotation == Surface.ROTATION_180;
+ }
+
+ /**
+ * Posts the transformation on the matrix representing the provided display rotation
+ */
+ public static void postDisplayRotation(@SurfaceRotation int displayRotation,
+ float screenWidth, float screenHeight, Matrix out) {
+ switch (displayRotation) {
+ case ROTATION_0:
+ return;
+ case ROTATION_90:
+ out.postRotate(270);
+ out.postTranslate(0, screenWidth);
+ break;
+ case ROTATION_180:
+ out.postRotate(180);
+ out.postTranslate(screenHeight, screenWidth);
+ break;
+ case ROTATION_270:
+ out.postRotate(90);
+ out.postTranslate(screenHeight, 0);
+ break;
+ }
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ boolean systemRotationOn = (mFlags & FLAG_SYSTEM_ROTATION_ALLOWED) != 0;
+ return "["
+ + "this=" + extractObjectNameAndAddress(super.toString())
+ + " mOrientationHandler=" +
+ extractObjectNameAndAddress(mOrientationHandler.toString())
+ + " mDisplayRotation=" + mDisplayRotation
+ + " mTouchRotation=" + mTouchRotation
+ + " mRecentsActivityRotation=" + mRecentsActivityRotation
+ + " isRecentsActivityRotationAllowed=" + isRecentsActivityRotationAllowed()
+ + " mSystemRotation=" + systemRotationOn
+ + " mFlags=" + mFlags
+ + "]";
+ }
+
+ /**
+ * Returns the device profile based on expected launcher rotation
+ */
+ public DeviceProfile getLauncherDeviceProfile() {
+ InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
+ // TODO also check the natural orientation is landscape or portrait
+ return (mRecentsActivityRotation == ROTATION_90
+ || mRecentsActivityRotation == ROTATION_270)
+ ? idp.landscapeProfile
+ : idp.portraitProfile;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 21b97ec..04308c8 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -25,12 +25,10 @@
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.TransactionCompat;
public abstract class RemoteAnimationProvider {
LauncherAnimationRunner mAnimationRunner;
- static final int Z_BOOST_BASE = 800570000;
public abstract AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets);
@@ -53,27 +51,6 @@
}
/**
- * Prepares the given {@param targets} for a remote animation, and should be called with the
- * transaction from the first frame of animation.
- *
- * @param boostModeTargets The mode indicating which targets to boost in z-order above other
- * targets.
- */
- static void prepareTargetsForFirstFrame(RemoteAnimationTargetCompat[] targets,
- TransactionCompat t, int boostModeTargets) {
- for (RemoteAnimationTargetCompat target : targets) {
- t.setLayer(target.leash, getLayer(target, boostModeTargets));
- t.show(target.leash);
- }
- }
-
- public static int getLayer(RemoteAnimationTargetCompat target, int boostModeTarget) {
- return target.mode == boostModeTarget
- ? Z_BOOST_BASE + target.prefixOrderIndex
- : target.prefixOrderIndex;
- }
-
- /**
* @return the target with the lowest opaque layer for a certain app animation, or null.
*/
public static RemoteAnimationTargetCompat findLowestOpaqueLayerTarget(
@@ -83,7 +60,7 @@
for (int i = appTargets.length - 1; i >= 0; i--) {
RemoteAnimationTargetCompat target = appTargets[i];
if (target.mode == mode && !target.isTranslucent) {
- int layer = getLayer(target, mode);
+ int layer = target.prefixOrderIndex;
if (layer < lowestLayer) {
lowestLayer = layer;
lowestLayerIndex = i;
diff --git a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
index fa2d338..958ee24 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep.util;
-import static com.android.quickstep.util.RemoteAnimationProvider.prepareTargetsForFirstFrame;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.ValueAnimator;
@@ -42,7 +41,9 @@
public void onAnimationUpdate(ValueAnimator valueAnimator) {
TransactionCompat t = new TransactionCompat();
if (mFirstFrame) {
- prepareTargetsForFirstFrame(mTarget.unfilteredApps, t, MODE_CLOSING);
+ for (RemoteAnimationTargetCompat target : mTarget.unfilteredApps) {
+ t.show(target.leash);
+ }
mFirstFrame = false;
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
new file mode 100644
index 0000000..a770e8e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
@@ -0,0 +1,112 @@
+/*
+ * 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.util;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Build;
+import android.view.WindowInsets.Type;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.WindowBounds;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to hold the information abound a window bounds for split screen
+ */
+@TargetApi(Build.VERSION_CODES.R)
+public class SplitScreenBounds {
+
+ public static final SplitScreenBounds INSTANCE = new SplitScreenBounds();
+ private final ArrayList<OnChangeListener> mListeners = new ArrayList<>();
+
+ @Nullable
+ private WindowBounds mBounds;
+
+ private SplitScreenBounds() { }
+
+ @UiThread
+ public void setSecondaryWindowBounds(@NonNull WindowBounds bounds) {
+ if (!bounds.equals(mBounds)) {
+ mBounds = bounds;
+ for (OnChangeListener listener : mListeners) {
+ listener.onSecondaryWindowBoundsChanged();
+ }
+ }
+ }
+
+ public @NonNull WindowBounds getSecondaryWindowBounds(Context context) {
+ if (mBounds == null) {
+ mBounds = createDefaultWindowBounds(context);
+ }
+ return mBounds;
+ }
+
+ /**
+ * Creates window bounds as 50% of device size
+ */
+ private static WindowBounds createDefaultWindowBounds(Context context) {
+ WindowMetrics wm = context.getSystemService(WindowManager.class).getMaximumWindowMetrics();
+ Insets insets = wm.getWindowInsets().getInsets(Type.systemBars());
+
+ WindowBounds bounds = new WindowBounds(wm.getBounds(),
+ new Rect(insets.left, insets.top, insets.right, insets.bottom));
+ int rotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
+ int halfDividerSize = context.getResources()
+ .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
+
+ if (rotation == ROTATION_0 || rotation == ROTATION_180) {
+ bounds.bounds.top = bounds.insets.top + bounds.availableSize.y / 2 + halfDividerSize;
+ bounds.insets.top = 0;
+ } else {
+ bounds.bounds.left = bounds.insets.left + bounds.availableSize.x / 2 + halfDividerSize;
+ bounds.insets.left = 0;
+ }
+ return new WindowBounds(bounds.bounds, bounds.insets);
+ }
+
+ public void addOnChangeListener(OnChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void removeOnChangeListener(OnChangeListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Interface to receive window bounds changes
+ */
+ public interface OnChangeListener {
+
+ /**
+ * Called when window bounds for secondary window changes
+ */
+ void onSecondaryWindowBoundsChanged();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index c2ccd90..19e278b 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
import android.content.Context;
import android.graphics.Canvas;
@@ -43,6 +44,7 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.states.OverviewState;
+import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.SysUINavigationMode;
@@ -75,6 +77,7 @@
private final float mRadius;
private final int mMaxScrimAlpha;
private final Paint mPaint;
+ private final OnboardingPrefs mOnboardingPrefs;
// Mid point where the alpha changes
private int mMidAlpha;
@@ -100,6 +103,7 @@
private boolean mRemainingScreenPathValid = false;
private Mode mSysUINavigationMode;
+ private boolean mIsTwoZoneSwipeModel;
public ShelfScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -108,6 +112,7 @@
mEndAlpha = Color.alpha(mEndScrim);
mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mOnboardingPrefs = mLauncher.getOnboardingPrefs();
// Just assume the easiest UI for now, until we have the proper layout information.
mDrawingFlatColor = true;
@@ -140,9 +145,11 @@
// Show the shelf more quickly before reaching overview progress.
mBeforeMidProgressColorInterpolator = ACCEL_2;
mAfterMidProgressColorInterpolator = ACCEL;
+ mIsTwoZoneSwipeModel = FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get();
} else {
mBeforeMidProgressColorInterpolator = ACCEL;
mAfterMidProgressColorInterpolator = Interpolators.clampToProgress(ACCEL, 0.5f, 1f);
+ mIsTwoZoneSwipeModel = false;
}
}
@@ -181,6 +188,7 @@
mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
}
updateColors();
+ updateSysUiColors();
updateDragHandleAlpha();
invalidate();
}
@@ -235,10 +243,26 @@
}
@Override
+ protected void updateSysUiColors() {
+ if (mDrawingFlatColor) {
+ super.updateSysUiColors();
+ } else {
+ // Use a light system UI (dark icons) if all apps is behind at least half of the
+ // status bar.
+ boolean forceChange = mShelfTop <= mLauncher.getDeviceProfile().getInsets().top / 2f;
+ if (forceChange) {
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark);
+ } else {
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
+ }
+ }
+ }
+
+ @Override
protected boolean shouldDragHandleBeVisible() {
- boolean twoZoneSwipeModel = FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()
- && SysUINavigationMode.removeShelfFromOverview(mLauncher);
- return twoZoneSwipeModel || super.shouldDragHandleBeVisible();
+ boolean needsAllAppsEdu = mIsTwoZoneSwipeModel
+ && !mOnboardingPrefs.hasReachedMaxCount(OnboardingPrefs.ALL_APPS_COUNT);
+ return needsAllAppsEdu || super.shouldDragHandleBeVisible();
}
@Override
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index a726052..b9e0f62 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -37,7 +37,6 @@
import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -58,10 +57,8 @@
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.FailureRewriterRule;
import com.android.launcher3.util.rule.FailureWatcher;
-import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.views.RecentsView;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
@@ -95,7 +92,8 @@
mDevice = UiDevice.getInstance(instrumentation);
mDevice.setOrientationNatural();
mLauncher = new LauncherInstrumentation();
- mLauncher.enableCheckEventsForSuccessfulGestures();
+ // b/143488140
+ //mLauncher.enableCheckEventsForSuccessfulGestures();
if (TestHelpers.isInLauncherProcess()) {
Utilities.enableRunningInTestHarnessForTests();
@@ -122,6 +120,9 @@
TestCommandReceiver.callCommand(TestCommandReceiver.DISABLE_TEST_LAUNCHER);
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
getLauncherCommand(getLauncherInMyProcess()));
+ // b/143488140
+ mDevice.pressHome();
+ mDevice.waitForIdle();
}
}
};
@@ -130,11 +131,15 @@
TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
getString("result"));
}
+ // b/143488140
+ mDevice.pressHome();
+ mDevice.waitForIdle();
+ startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
}
- @NavigationModeSwitch
+ // b/143488140
+ //@NavigationModeSwitch
@Test
- @Ignore // b/143488140
public void goToOverviewFromHome() {
mDevice.pressHome();
assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
@@ -143,9 +148,9 @@
mLauncher.getBackground().switchToOverview();
}
- @NavigationModeSwitch
+ // b/143488140
+ //@NavigationModeSwitch
@Test
- @Ignore // b/143488140
public void goToOverviewFromApp() {
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
@@ -178,9 +183,9 @@
return mLauncher.getBackground().switchToOverview();
}
- @NavigationModeSwitch
+ // b/143488140
+ //@NavigationModeSwitch
@Test
- @Ignore // b/143488140
public void testOverview() {
startAppFast(getAppPackageName());
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 0afe4a8..40265c4 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -203,7 +203,7 @@
+ launcher.getNavigationModeMismatchError(),
() -> launcher.getNavigationModeMismatchError() == null,
60000 /* b/148422894 */, launcher);
- AbstractLauncherUiTest.checkDetectedLeaks();
+ AbstractLauncherUiTest.checkDetectedLeaks(launcher);
return true;
}
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index 34eb7f8..79ddf7a 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -58,9 +58,9 @@
}
@Test
- public void onTaskRemoved_reloadsAllTasks() {
+ public void onTaskRemoved_doesNotFetchTasks() {
mRecentTasksList.onTaskRemoved(0);
- verify(mockActivityManagerWrapper, times(1))
+ verify(mockActivityManagerWrapper, times(0))
.getRecentTasks(anyInt(), anyInt());
}
@@ -77,7 +77,7 @@
when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt()))
.thenReturn(Collections.singletonList(recentTaskInfo));
- List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, true);
+ List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, true);
assertEquals(1, taskList.size());
assertNull(taskList.get(0).taskDescription.getLabel());
@@ -91,7 +91,7 @@
when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt()))
.thenReturn(Collections.singletonList(recentTaskInfo));
- List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, false);
+ List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, false);
assertEquals(1, taskList.size());
assertEquals(taskDescription, taskList.get(0).taskDescription.getLabel());
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 731a220..9732cdc 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -25,7 +25,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.Launcher;
-import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.util.RaceConditionReproducer;
import com.android.quickstep.NavigationModeSwitchRule.Mode;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -46,6 +45,8 @@
@Before
public void setUp() throws Exception {
super.setUp();
+ // b/143488140
+ mLauncher.pressHome();
// Start an activity where the gestures start.
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
}
@@ -99,5 +100,6 @@
// The test action.
mLauncher.getBackground().switchToOverview();
}
+ mLauncher.pressHome();
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 75fcfe2..bf093fd 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -42,6 +42,7 @@
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.views.RecentsView;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,6 +54,18 @@
public void setUp() throws Exception {
super.setUp();
TaplTestsLauncher3.initialize(this);
+ executeOnLauncher(launcher -> {
+ RecentsView recentsView = launcher.getOverviewPanel();
+ recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true);
+ });
+ }
+
+ @After
+ public void tearDown() {
+ executeOnLauncher(launcher -> {
+ RecentsView recentsView = launcher.getOverviewPanel();
+ recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
+ });
}
private void startTestApps() throws Exception {
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index f8f22a1..115294a 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -17,10 +17,11 @@
import static androidx.test.InstrumentationRegistry.getContext;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static com.android.launcher3.common.WidgetUtils.createWidgetInfo;
import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
-import static com.android.launcher3.ui.widget.BindWidgetTest.createWidgetInfo;
import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
import static org.junit.Assert.assertEquals;
@@ -48,9 +49,9 @@
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.tapl.Background;
import com.android.launcher3.testcomponent.ListViewService;
import com.android.launcher3.testcomponent.ListViewService.SimpleViewsFactory;
@@ -187,7 +188,7 @@
LauncherSettings.Settings.call(mResolver,
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
- LauncherAppWidgetInfo item = createWidgetInfo(info, true);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
addItemToScreen(item);
assertTrue("Widget is not present",
diff --git a/res/color/overview_button.xml b/res/color/overview_button.xml
new file mode 100644
index 0000000..6ac36bf
--- /dev/null
+++ b/res/color/overview_button.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:alpha="1"
+ android:color="?attr/workspaceTextColor"
+ android:state_enabled="true" />
+ <item
+ android:alpha="?android:disabledAlpha"
+ android:color="?attr/workspaceTextColor"
+ android:state_enabled="false" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable/gesture_tutorial_ripple.xml b/res/drawable/gesture_tutorial_ripple.xml
new file mode 100644
index 0000000..782af33
--- /dev/null
+++ b/res/drawable/gesture_tutorial_ripple.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ripple android:color="@color/gesture_tutorial_ripple_color"
+ xmlns:android="http://schemas.android.com/apk/res/android"/>
\ No newline at end of file
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index a41fb9a..67ec664 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -19,6 +19,7 @@
<com.android.launcher3.allapps.LauncherAllAppsContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apps_view"
+ android:theme="?attr/allAppsTheme"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="true"
diff --git a/res/layout/all_apps_content_layout.xml b/res/layout/all_apps_content_layout.xml
new file mode 100644
index 0000000..5698977
--- /dev/null
+++ b/res/layout/all_apps_content_layout.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/apps_list_view_override"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_below="@id/search_container_all_apps"
+ android:clipToPadding="false"
+ android:descendantFocusability="afterDescendants"
+ android:focusable="true"
+ android:layout_marginTop="@dimen/all_apps_header_top_padding"
+ android:orientation="vertical">
+</LinearLayout>
diff --git a/res/layout/arrow_toast.xml b/res/layout/arrow_toast.xml
index 087e45a..0ec9981 100644
--- a/res/layout/arrow_toast.xml
+++ b/res/layout/arrow_toast.xml
@@ -34,6 +34,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
+ android:paddingTop="5dp"
+ android:paddingBottom="5dp"
android:gravity="center"
android:layout_gravity="center_vertical"
android:textColor="@android:color/white"
@@ -58,6 +60,5 @@
android:elevation="2dp"
android:layout_width="10dp"
android:layout_height="8dp"
- android:layout_marginTop="-2dp"
- android:layout_gravity="center_horizontal"/>
+ android:layout_marginTop="-2dp"/>
</merge>
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index de13277..a137908 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -45,12 +45,8 @@
<include
android:id="@+id/overview_panel"
- layout="@layout/overview_panel"
- android:visibility="gone" />
+ layout="@layout/overview_panel" />
- <include
- android:id="@+id/overview_actions_view"
- layout="@layout/overview_actions_container" />
<!-- Keep these behind the workspace so that they are not visible when
we go into AllApps -->
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index 2637f03..f513688 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -17,4 +17,5 @@
<Space
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
- android:layout_height="0dp" />
\ No newline at end of file
+ android:layout_height="0dp"
+ android:visibility="gone" />
\ No newline at end of file
diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml
index 98cfc34..fdf4446 100644
--- a/res/layout/secondary_launcher.xml
+++ b/res/layout/secondary_launcher.xml
@@ -17,7 +17,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:id="@+id/drag_layer" >
+ android:id="@+id/drag_layer"
+ android:padding="@dimen/dynamic_grid_edge_margin">
<GridView
android:layout_width="match_parent"
@@ -119,8 +120,7 @@
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textColorHint="@drawable/all_apps_search_hint"
- android:textSize="16sp"
- android:translationY="24dp" />
+ android:textSize="16sp" />
<include layout="@layout/all_apps_fast_scroller" />
</com.android.launcher3.allapps.AllAppsContainerView>
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index 4b7097a..881df1b 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -22,7 +22,7 @@
android:theme="@style/PopupItem" >
<com.android.launcher3.BubbleTextView
- style="@style/BaseIcon"
+ style="@style/BaseIconUnBounded"
android:id="@+id/bubble_text"
android:background="?android:attr/selectableItemBackground"
android:gravity="start|center_vertical"
@@ -30,6 +30,7 @@
android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
android:paddingEnd="@dimen/popup_padding_end"
android:textSize="14sp"
+ android:maxLines="2"
android:textColor="?android:attr/textColorPrimary"
launcher:iconDisplay="shortcut_popup"
launcher:layoutHorizontal="true"
diff --git a/res/layout/system_shortcut_icon_only.xml b/res/layout/system_shortcut_icon_only.xml
index b8b5b8c..5a81f70 100644
--- a/res/layout/system_shortcut_icon_only.xml
+++ b/res/layout/system_shortcut_icon_only.xml
@@ -19,7 +19,7 @@
android:layout_width="@dimen/system_shortcut_header_icon_touch_size"
android:layout_height="@dimen/system_shortcut_header_icon_touch_size"
android:background="?android:attr/selectableItemBackgroundBorderless"
- android:tint="?android:attr/textColorHint"
+ android:tint="?attr/iconOnlyShortcutColor"
android:tintMode="src_in"
android:padding="@dimen/system_shortcut_header_icon_padding"
android:theme="@style/PopupItem" />
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index f64b2d9..7f1107f 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -15,12 +15,13 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:background="?attr/allAppsScrimColor"
android:padding="48dp"
android:orientation="vertical"
android:gravity="center">
<TextView
- style="@style/PrimaryMediumText"
+ style="@style/PrimaryHeadline"
android:textColor="?attr/workProfileOverlayTextColor"
android:id="@+id/work_apps_paused_title"
android:layout_width="wrap_content"
@@ -34,6 +35,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:id="@+id/work_apps_paused_content"
android:textColor="?attr/workProfileOverlayTextColor"
android:text="@string/work_apps_paused_body"
android:textAlignment="center"
diff --git a/res/layout/work_mode_switch.xml b/res/layout/work_mode_switch.xml
index 689dbe7..31953c7 100644
--- a/res/layout/work_mode_switch.xml
+++ b/res/layout/work_mode_switch.xml
@@ -17,7 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/PrimaryMediumText"
+ style="@style/PrimaryHeadline"
android:id="@+id/work_mode_toggle"
android:drawableStart="@drawable/ic_corp"
android:drawablePadding="16dp"
@@ -25,14 +25,15 @@
android:textColor="?attr/workProfileOverlayTextColor"
android:layout_alignParentBottom="true"
android:ellipsize="end"
+ android:elevation="10dp"
android:gravity="start"
android:lines="1"
android:showText="false"
- android:textSize="16sp"
+ android:textSize="@dimen/work_profile_footer_text_size"
android:background="?attr/allAppsScrimColor"
android:text="@string/work_profile_toggle_label"
- android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding"
- android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding"
- android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding"
- android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding"
-/>
\ No newline at end of file
+ android:paddingBottom="@dimen/work_profile_footer_padding"
+ android:paddingLeft="@dimen/work_profile_footer_padding"
+ android:paddingRight="@dimen/work_profile_footer_padding"
+ android:paddingTop="@dimen/work_profile_footer_padding"
+/>
diff --git a/res/layout/work_profile_edu.xml b/res/layout/work_profile_edu.xml
index 5506b94..c3c7010 100644
--- a/res/layout/work_profile_edu.xml
+++ b/res/layout/work_profile_edu.xml
@@ -23,13 +23,13 @@
android:layout_width="match_parent"
android:layout_height="32dp"
android:background="@drawable/bottom_sheet_top_border"
- android:backgroundTint="?android:attr/colorAccent" />
+ android:backgroundTint="?attr/eduHalfSheetBGColor" />
<LinearLayout
android:id="@+id/view_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?android:attr/colorAccent"
+ android:background="?attr/eduHalfSheetBGColor"
android:orientation="vertical"
android:paddingLeft="@dimen/bottom_sheet_edu_padding"
android:paddingRight="@dimen/bottom_sheet_edu_padding">
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index eb9af97..09bdaaf 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -1,4 +1,3 @@
<resources>
- <bool name="is_tablet">true</bool>
<bool name="allow_rotation">true</bool>
</resources>
diff --git a/res/values-sw720dp/config.xml b/res/values-sw720dp/config.xml
index 94cffcb..1f401c4 100644
--- a/res/values-sw720dp/config.xml
+++ b/res/values-sw720dp/config.xml
@@ -1,6 +1,5 @@
<resources>
<bool name="config_largeHeap">true</bool>
- <bool name="is_large_tablet">true</bool>
<!-- All Apps & Widgets -->
<!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
diff --git a/res/values-v26/styles.xml b/res/values-v26/styles.xml
index 8fb408b..d2f0802 100644
--- a/res/values-v26/styles.xml
+++ b/res/values-v26/styles.xml
@@ -20,6 +20,7 @@
<!-- Theme for the widget container. -->
<style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:colorPrimaryDark">#E8EAED</item>
+ <item name="android:textColorSecondary">?android:attr/textColorPrimary</item>
<item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
</style>
<style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
diff --git a/res/values-v30/styles.xml b/res/values-v30/styles.xml
new file mode 100644
index 0000000..71740a9
--- /dev/null
+++ b/res/values-v30/styles.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* 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.
+*/
+-->
+
+<resources>
+ <!-- Launcher theme -->
+ <style name="BaseLauncherTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:colorEdgeEffect">#FF757575</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowShowWallpaper">true</item>
+ <item name="folderTextColor">?attr/workspaceTextColor</item>
+ <item name="android:windowLayoutInDisplayCutoutMode">always</item>
+ <item name="android:enforceStatusBarContrast">false</item>
+ <item name="android:enforceNavigationBarContrast">false</item>
+ </style>
+</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 5a15ec6..26e6cba 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -22,6 +22,7 @@
<attr name="allAppsScrimColor" format="color" />
<attr name="allAppsInterimScrimAlpha" format="integer" />
<attr name="allAppsNavBarScrimColor" format="color" />
+ <attr name="allAppsTheme" format="reference" />
<attr name="popupColorPrimary" format="color" />
<attr name="popupColorSecondary" format="color" />
<attr name="popupColorTertiary" format="color" />
@@ -34,6 +35,8 @@
<attr name="workspaceStatusBarScrim" format="reference" />
<attr name="widgetsTheme" format="reference" />
<attr name="loadingIconColor" format="color" />
+ <attr name="iconOnlyShortcutColor" format="color"/>
+ <attr name="eduHalfSheetBGColor" format="color"/>
<attr name="folderDotColor" format="color" />
<attr name="folderFillColor" format="color" />
@@ -42,6 +45,7 @@
<attr name="folderTextColor" format="color" />
<attr name="folderHintColor" format="color" />
<attr name="workProfileOverlayTextColor" format="color" />
+ <attr name="disabledIconAlpha" format="float" />
<!-- BubbleTextView specific attributes. -->
<declare-styleable name="BubbleTextView">
@@ -118,6 +122,8 @@
<!-- numHotseatIcons defaults to numColumns, if not specified -->
<attr name="numHotseatIcons" format="integer" />
<attr name="dbFile" format="string" />
+ <!-- numAllAppsColumns defaults to numColumns, if not specified -->
+ <attr name="numAllAppsColumns" format="integer" />
<attr name="defaultLayoutId" format="reference" />
<attr name="demoModeLayoutId" format="reference" />
</declare-styleable>
@@ -133,6 +139,12 @@
<attr name="iconTextSize" format="float" />
<!-- If true, this display option is used to determine the default grid -->
<attr name="canBeDefault" format="boolean" />
+
+ <!-- The following values are only enabled if grid is supported. -->
+ <!-- allAppsIconSize defaults to iconSize, if not specified -->
+ <attr name="allAppsIconSize" format="float" />
+ <!-- allAppsIconTextSize defaults to iconTextSize, if not specified -->
+ <attr name="allAppsIconTextSize" format="float" />
</declare-styleable>
<declare-styleable name="CellLayout">
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 194ef2c..043ad9a 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -38,11 +38,8 @@
<color name="all_apps_bg_hand_fill">#E5E5E5</color>
<color name="all_apps_bg_hand_fill_dark">#9AA0A6</color>
- <color name="back_gesture_tutorial_background_color">#FFFFFFFF</color>
- <color name="back_gesture_tutorial_subtitle_color">#99000000</color> <!-- 60% black -->
- <color name="back_gesture_tutorial_title_color">#FF000000</color>
- <color name="back_gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
- <color name="back_gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
-
-
+ <color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
+ <color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
+ <color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
+ <color name="gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index ef67613..ca25325 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -1,8 +1,6 @@
<resources>
<!-- Miscellaneous -->
<bool name="config_largeHeap">false</bool>
- <bool name="is_tablet">false</bool>
- <bool name="is_large_tablet">false</bool>
<bool name="allow_rotation">false</bool>
<integer name="extracted_color_gradient_alpha">153</integer>
@@ -71,6 +69,7 @@
<string name="app_launch_tracker_class" translatable="false"></string>
<string name="test_information_handler_class" translatable="false"></string>
<string name="launcher_activity_logic_class" translatable="false"></string>
+ <string name="prediction_model_class" translatable="false"></string>
<!-- View ID to use for QSB widget -->
<item type="id" name="qsb_widget" />
@@ -125,14 +124,14 @@
<item name="all_apps_spring_damping_ratio" type="dimen" format="float">0.75</item>
<item name="all_apps_spring_stiffness" type="dimen" format="float">600</item>
- <item name="dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.5</item>
- <item name="dismiss_task_trans_y_stiffness" type="dimen" format="float">1500</item>
+ <item name="dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.73</item>
+ <item name="dismiss_task_trans_y_stiffness" type="dimen" format="float">800</item>
- <item name="dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.5</item>
- <item name="dismiss_task_trans_x_stiffness" type="dimen" format="float">1500</item>
+ <item name="dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.73</item>
+ <item name="dismiss_task_trans_x_stiffness" type="dimen" format="float">800</item>
- <item name="horizontal_spring_damping_ratio" type="dimen" format="float">0.75</item>
- <item name="horizontal_spring_stiffness" type="dimen" format="float">200</item>
+ <item name="horizontal_spring_damping_ratio" type="dimen" format="float">0.8</item>
+ <item name="horizontal_spring_stiffness" type="dimen" format="float">250</item>
<item name="swipe_up_rect_scale_damping_ratio" type="dimen" format="float">0.75</item>
<item name="swipe_up_rect_scale_stiffness" type="dimen" format="float">200</item>
@@ -145,6 +144,10 @@
<item name="staggered_stiffness" type="dimen" format="float">150</item>
<dimen name="unlock_staggered_velocity_dp_per_s">3dp</dimen>
+ <item name="hint_scale_damping_ratio" type="dimen" format="float">0.7</item>
+ <item name="hint_scale_stiffness" type="dimen" format="float">200</item>
+ <dimen name="hint_scale_velocity_dp_per_s">0.3dp</dimen>
+
<!-- Swipe up to home related -->
<dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
<dimen name="swipe_up_y_overshoot">10dp</dimen>
@@ -176,6 +179,10 @@
<item>@dimen/swipe_up_fling_min_visible_change</item>
<item>@dimen/swipe_up_y_overshoot</item>
+
+ <item>@dimen/hint_scale_damping_ratio</item>
+ <item>@dimen/hint_scale_stiffness</item>
+ <item>@dimen/hint_scale_velocity_dp_per_s</item>
</array>
<string-array name="live_wallpapers_remove_sysui_scrims">
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 0b589a2..947e635 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -96,7 +96,6 @@
<!-- The size of corner radius of the arrow in the arrow toast. -->
<dimen name="arrow_toast_corner_radius">2dp</dimen>
- <dimen name="all_apps_work_profile_tab_footer_padding">20dp</dimen>
<!-- Search bar in All Apps -->
<dimen name="all_apps_header_max_elevation">3dp</dimen>
@@ -105,6 +104,9 @@
<dimen name="all_apps_divider_margin_vertical">8dp</dimen>
+ <dimen name="work_profile_footer_padding">20dp</dimen>
+ <dimen name="work_profile_footer_text_size">16sp</dimen>
+
<!-- Widget tray -->
<dimen name="widget_preview_label_vertical_padding">8dp</dimen>
<dimen name="widget_preview_label_horizontal_padding">16dp</dimen>
@@ -200,7 +202,7 @@
<dimen name="system_shortcut_icon_size">24dp</dimen>
<!-- popup_arrow_center_start - system_shortcut_icon_size / 2 -->
<dimen name="system_shortcut_margin_start">16dp</dimen>
- <dimen name="system_shortcut_header_height">40dp</dimen>
+ <dimen name="system_shortcut_header_height">48dp</dimen>
<dimen name="system_shortcut_header_icon_touch_size">48dp</dimen>
<!-- (touch_size - icon_size) / 2 -->
<dimen name="system_shortcut_header_icon_padding">12dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ac04262..935bb40 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -103,9 +103,9 @@
<!-- Label for install drop target. [CHAR_LIMIT=20] -->
<string name="install_drop_target_label">Install</string>
<!-- Label for install dismiss prediction. -->
- <string translatable="false" name="dismiss_prediction_label">Don\'t suggest app</string>
+ <string name="dismiss_prediction_label">Don\'t suggest app</string>
<!-- Label for pinning predicted app. -->
- <string name="pin_prediction" translatable="false">Pin Prediction</string>
+ <string name="pin_prediction">Pin Prediction</string>
<!-- Permissions: -->
@@ -172,8 +172,10 @@
<string name="folder_closed">Folder closed</string>
<!-- Folder renamed format -->
<string name="folder_renamed">Folder renamed to <xliff:g id="name" example="Games">%1$s</xliff:g></string>
- <!-- Folder name format -->
- <string name="folder_name_format">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string>
+ <!-- Folder name format when folder has less than 4 items -->
+ <string name="folder_name_format_exact">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> items</string>
+ <!-- Folder name format when folder has 4 or more items shown in preview-->
+ <string name="folder_name_format_overflow">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> or more items</string>
<!-- Strings for the customization mode -->
<!-- Text for widget add button -->
@@ -182,7 +184,7 @@
<string name="wallpaper_button_text">Wallpapers</string>
<!-- Text for wallpaper change button -->
<string name="styles_wallpaper_button_text">Styles & wallpapers</string>
- <!-- Text for settings button -->
+ <!-- Text for settings button [CHAR LIMIT=20]-->
<string name="settings_button_text">Home settings</string>
<!-- Message shown when a feature is disabled by the administrator -->
<string name="msg_disabled_by_admin">Disabled by your admin</string>
@@ -207,7 +209,7 @@
<!-- Summary for Notification dots setting. Tapping this will link enable/disable notification dots feature on the home screen. [CHAR LIMIT=50] -->
<string name="notification_dots_service_title">Show notification dots</string>
- <!-- Label for the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=40] -->
+ <!-- Label for the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=60] -->
<string name="auto_add_shortcuts_label">Add app icons to Home screen</string>
<!-- Text description of the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=NONE] -->
<string name="auto_add_shortcuts_description">For new apps</string>
@@ -342,7 +344,11 @@
<!--- heading shown when user opens work apps tab while work apps are paused -->
<string name="work_apps_paused_title">Work profile is paused</string>
<!--- body shown when user opens work apps tab while work apps are paused -->
- <string name="work_apps_paused_body">Work apps can\’t send you notifications, use your battery, or access your location</string>
+ <string name="work_apps_paused_body">Work apps can\'t send you notifications, use your battery, or access your location</string>
+ <!-- content description for paused work apps list -->
+ <string name="work_apps_paused_content_description">Work profile is paused. Work apps can\’t send you notifications, use your battery, or access your location</string>
+
+
<!-- A tip shown pointing at work toggle -->
<string name="work_switch_tip">Pause work apps and notifications</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index bc6ab45..25f21f3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -30,11 +30,13 @@
</style>
<style name="LauncherTheme" parent="@style/BaseLauncherTheme">
- <item name="allAppsScrimColor">#EAFFFFFF</item>
+ <item name="android:textColorSecondary">#DE000000</item>
+ <item name="allAppsScrimColor">#FFFFFFFF</item>
<item name="allAppsInterimScrimAlpha">46</item>
<item name="allAppsNavBarScrimColor">#66FFFFFF</item>
+ <item name="allAppsTheme">@style/AllAppsTheme</item>
<item name="popupColorPrimary">#FFF</item>
- <item name="popupColorSecondary">#F5F5F5</item> <!-- Gray 100 -->
+ <item name="popupColorSecondary">#F1F3F4</item>
<item name="popupColorTertiary">#E0E0E0</item> <!-- Gray 300 -->
<item name="isMainColorDark">false</item>
<item name="isWorkspaceDarkText">false</item>
@@ -48,9 +50,12 @@
<item name="folderFillColor">#CDFFFFFF</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
<item name="folderTextColor">#FF212121</item>
- <item name="folderHintColor">#FF616161</item>
+ <item name="folderHintColor">#89616161</item>
<item name="loadingIconColor">#CCFFFFFF</item>
+ <item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
<item name="workProfileOverlayTextColor">#FF212121</item>
+ <item name="eduHalfSheetBGColor">?android:attr/colorAccent</item>
+ <item name="disabledIconAlpha">.54</item>
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowTranslucentNavigation">false</item>
@@ -64,6 +69,8 @@
<style name="LauncherTheme.DarkMainColor" parent="@style/LauncherTheme">
<item name="folderFillColor">#FF3C4043</item> <!-- 100% GM2 800 -->
<item name="folderTextColor">?attr/workspaceTextColor</item>
+ <item name="disabledIconAlpha">.254</item>
+
</style>
<style name="LauncherTheme.DarkText" parent="@style/LauncherTheme">
@@ -78,7 +85,6 @@
<item name="folderFillColor">#CDFFFFFF</item>
<item name="folderIconBorderColor">#FF80868B</item>
<item name="folderTextColor">?attr/workspaceTextColor</item>
-
</style>
<style name="LauncherTheme.Dark" parent="@style/LauncherTheme">
@@ -88,26 +94,30 @@
<item name="android:textColorHint">#A0FFFFFF</item>
<item name="android:colorControlHighlight">#A0FFFFFF</item>
<item name="android:colorPrimary">#FF212121</item>
- <item name="allAppsScrimColor">#EA212121</item>
+ <item name="allAppsScrimColor">#FF000000</item>
<item name="allAppsInterimScrimAlpha">102</item>
<item name="allAppsNavBarScrimColor">#80000000</item>
+ <item name="allAppsTheme">@style/AllAppsTheme.Dark</item>
<item name="popupColorPrimary">#3C4043</item> <!-- Gray 800 -->
- <item name="popupColorSecondary">#5F6368</item> <!-- Gray 700 -->
+ <item name="popupColorSecondary">#202124</item>
<item name="popupColorTertiary">#757575</item> <!-- Gray 600 -->
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
<item name="folderDotColor">#FF464646</item>
<item name="folderFillColor">#DD3C4043</item> <!-- 87% GM2 800 -->
<item name="folderIconBorderColor">#FF80868B</item>
<item name="folderTextColor">@android:color/white</item>
- <item name="folderHintColor">#FFCCCCCC</item>
+ <item name="folderHintColor">#89CCCCCC</item>
<item name="isMainColorDark">true</item>
<item name="loadingIconColor">#99FFFFFF</item>
+ <item name="iconOnlyShortcutColor">#B3FFFFFF</item>
<item name="workProfileOverlayTextColor">@android:color/white</item>
+ <item name="eduHalfSheetBGColor">#DD000000</item>
</style>
<style name="LauncherTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark">
<item name="folderFillColor">#FF3C4043</item> <!-- 100% GM2 800 -->
<item name="folderTextColor">@android:color/white</item>
+ <item name="disabledIconAlpha">.54</item>
</style>
<style name="LauncherTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark">
@@ -176,21 +186,32 @@
<item name="android:importantForAccessibility">no</item>
</style>
- <!-- Base theme for BubbleTextView and sub classes -->
- <style name="BaseIcon" parent="@android:style/TextAppearance.DeviceDefault">
+ <style name="AllAppsTheme">
+ <item name="disabledIconAlpha">.54</item>
+ </style>
+
+ <style name="AllAppsTheme.Dark">
+ <item name="disabledIconAlpha">.54</item>
+ </style>
+
+
+ <style name="BaseIconUnBounded" parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_gravity">center</item>
<item name="android:focusable">true</item>
<item name="android:gravity">center_horizontal</item>
- <item name="android:lines">1</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:defaultFocusHighlightEnabled">false</item>
-
<!-- No shadows in the base theme -->
<item name="android:shadowRadius">0</item>
</style>
+ <!-- Base theme for BubbleTextView and sub classes -->
+ <style name="BaseIcon" parent="BaseIconUnBounded">
+ <item name="android:lines">1</item>
+ </style>
+
<!-- Icon displayed on the workspace -->
<style name="BaseIcon.Workspace" >
<item name="android:shadowRadius">2.0</item>
@@ -225,7 +246,7 @@
<style name="DropTargetButton" parent="DropTargetButtonBase" />
<style name="TextHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
- <style name="PrimaryMediumText" parent="@android:style/TextAppearance.DeviceDefault.Medium"/>
+ <style name="PrimaryHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/>
<style name="TextTitle" parent="@android:style/TextAppearance.DeviceDefault" />
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index 7c7e73c..3fa9b0a 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -21,8 +21,11 @@
LOCAL_MODULE := LauncherRoboTests
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SDK_VERSION := system_current
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../tests/src_common)
+
LOCAL_STATIC_JAVA_LIBRARIES := \
androidx.test.runner \
androidx.test.rules \
@@ -47,7 +50,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE := RunLauncherRoboTests
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := system_current
LOCAL_JAVA_LIBRARIES := LauncherRoboTests
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
index 3d78689..a8e0cb3 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/config/robolectric.properties
@@ -1 +1,19 @@
sdk=29
+shadows= \
+ com.android.launcher3.shadows.LShadowApplicationPackageManager \
+ com.android.launcher3.shadows.LShadowAppPredictionManager \
+ com.android.launcher3.shadows.LShadowAppWidgetManager \
+ com.android.launcher3.shadows.LShadowBackupManager \
+ com.android.launcher3.shadows.LShadowBitmap \
+ com.android.launcher3.shadows.LShadowDisplay \
+ com.android.launcher3.shadows.LShadowLauncherApps \
+ com.android.launcher3.shadows.LShadowTypeface \
+ com.android.launcher3.shadows.LShadowUserManager \
+ com.android.launcher3.shadows.LShadowWallpaperManager \
+ com.android.launcher3.shadows.ShadowDeviceFlag \
+ com.android.launcher3.shadows.ShadowLooperExecutor \
+ com.android.launcher3.shadows.ShadowMainThreadInitializedObject \
+ com.android.launcher3.shadows.ShadowOverrides \
+ com.android.launcher3.shadows.ShadowSurfaceTransactionApplier \
+
+application=com.android.launcher3.util.LauncherTestApplication
\ No newline at end of file
diff --git a/robolectric_tests/resources/cache_data_updated_task_data.txt b/robolectric_tests/resources/cache_data_updated_task_data.txt
index 302d58f..603dbe3 100644
--- a/robolectric_tests/resources/cache_data_updated_task_data.txt
+++ b/robolectric_tests/resources/cache_data_updated_task_data.txt
@@ -1,6 +1,6 @@
# Model data used by CacheDataUpdatedTaskTest
-classMap s com.android.launcher3.WorkspaceItemInfo
+classMap s com.android.launcher3.model.data.WorkspaceItemInfo
# Items for the BgDataModel
diff --git a/robolectric_tests/resources/package_install_state_change_task_data.txt b/robolectric_tests/resources/package_install_state_change_task_data.txt
index 4d63664..e82ea9d 100644
--- a/robolectric_tests/resources/package_install_state_change_task_data.txt
+++ b/robolectric_tests/resources/package_install_state_change_task_data.txt
@@ -1,7 +1,7 @@
# Model data used by PackageInstallStateChangeTaskTest
-classMap s com.android.launcher3.WorkspaceItemInfo
-classMap w com.android.launcher3.LauncherAppWidgetInfo
+classMap s com.android.launcher3.model.data.WorkspaceItemInfo
+classMap w com.android.launcher3.model.data.LauncherAppWidgetInfo
# Items for the BgDataModel
diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
index 74ef55e..b7ba106 100644
--- a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -16,24 +16,25 @@
package com.android.launcher3.folder;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.os.UserHandle;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.shadows.LShadowUserManager;
-import com.android.launcher3.util.LauncherRoboTestRunner;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public final class FolderNameProviderTest {
private Context mContext;
private WorkspaceItemInfo mItem1;
@@ -45,13 +46,13 @@
mItem1 = new WorkspaceItemInfo(new AppInfo(
new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
"title1",
- LShadowUserManager.newUserHandle(10),
+ UserHandle.of(10),
new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
));
mItem2 = new WorkspaceItemInfo(new AppInfo(
new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
"title2",
- LShadowUserManager.newUserHandle(10),
+ UserHandle.of(10),
new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
));
}
@@ -61,16 +62,16 @@
ArrayList<WorkspaceItemInfo> list = new ArrayList<>();
list.add(mItem1);
list.add(mItem2);
- FolderNameInfo[] nameInfos =
- new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderNameInfos nameInfos = new FolderNameInfos();
new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
- assertEquals("Work", nameInfos[0].getLabel());
+ assertEquals("Work", nameInfos.getLabels()[0]);
- nameInfos[0] = new FolderNameInfo("candidate1", 0.9);
- nameInfos[1] = new FolderNameInfo("candidate2", 0.8);
- nameInfos[2] = new FolderNameInfo("candidate3", 0.7);
+ nameInfos.setLabel(0, "candidate1", 1.0f);
+ nameInfos.setLabel(1, "candidate2", 1.0f);
+ nameInfos.setLabel(2, "candidate3", 1.0f);
new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
- assertEquals("Work", nameInfos[3].getLabel());
-
+ assertEquals("Work", nameInfos.getLabels()[3]);
+ assertTrue(nameInfos.hasSuggestions());
+ assertTrue(nameInfos.hasPrimary());
}
}
diff --git a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
index 95a4146..c892618 100644
--- a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -3,12 +3,11 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import com.android.launcher3.util.LauncherRoboTestRunner;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.util.Scheduler;
@@ -21,7 +20,7 @@
/**
* Tests for {@link FileLog}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class FileLogTest {
private File mTempDir;
diff --git a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
index b7f2243..8aa6f37 100644
--- a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -14,23 +14,23 @@
import android.util.Pair;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;
@@ -41,7 +41,7 @@
/**
* Tests for {@link AddWorkspaceItemsTask}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
public class AddWorkspaceItemsTaskTest {
diff --git a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java b/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
index 7072adf..90313ab 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
@@ -43,11 +43,11 @@
import com.android.launcher3.shadows.LShadowBackupManager;
import com.android.launcher3.shadows.LShadowUserManager;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.LooperMode;
import org.robolectric.shadow.api.Shadow;
@@ -55,7 +55,7 @@
/**
* Tests to verify backup and restore flow.
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
@LooperMode(LooperMode.Mode.PAUSED)
public class BackupRestoreTest {
diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index f128e24..5610b0e 100644
--- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -17,19 +17,19 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;
@@ -40,7 +40,7 @@
/**
* Tests for {@link CacheDataUpdatedTask}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
public class CacheDataUpdatedTaskTest {
diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
index 1442c55..bbbe21e 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
@@ -36,11 +36,11 @@
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.io.File;
@@ -48,7 +48,7 @@
/**
* Tests for {@link DbDowngradeHelper}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class DbDowngradeHelperTest {
private static final String SCHEMA_FILE = "test_schema.json";
@@ -130,7 +130,7 @@
}
helper.close();
- helper = new DatabaseHelper(mContext, DB_FILE) {
+ helper = new DatabaseHelper(mContext, DB_FILE, false) {
@Override
public void onOpen(SQLiteDatabase db) { }
};
@@ -161,7 +161,7 @@
DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext);
- DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE) {
+ DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE, false) {
@Override
public void onOpen(SQLiteDatabase db) { }
};
diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 7bc34cf..8f3a83e 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -28,17 +28,17 @@
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;
@@ -46,7 +46,7 @@
/**
* Tests for layout parser for remote layout
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
public class DefaultLayoutProviderTest {
@@ -90,6 +90,22 @@
}
@Test
+ public void testCustomProfileLoaded_with_folder_custom_title() throws Exception {
+ writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder("CustomFolder")
+ .addApp(TEST_PACKAGE, TEST_PACKAGE)
+ .addApp(TEST_PACKAGE, TEST_PACKAGE)
+ .addApp(TEST_PACKAGE, TEST_PACKAGE)
+ .build());
+
+ // Verify folder
+ assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
+ ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
+ assertEquals(3, ((FolderInfo) info).contents.size());
+ assertEquals("CustomFolder", info.title.toString());
+ }
+
+ @Test
public void testCustomProfileLoaded_with_widget() throws Exception {
String pendingAppPkg = "com.test.pending";
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
index f46b849..56ce215 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
@@ -23,17 +23,17 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/**
* Unit tests for {@link GridBackupTable}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class GridBackupTableTest {
private static final int BACKUP_ITEM_COUNT = 12;
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index c426dc5..8e00dcb 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -20,11 +20,11 @@
import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.HashSet;
@@ -33,7 +33,7 @@
/**
* Unit tests for {@link GridSizeMigrationTask}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class GridSizeMigrationTaskTest {
private LauncherModelHelper mModelHelper;
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
index 8f58d8b..cca333c 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
+++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
@@ -39,17 +39,17 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.HashSet;
/** Unit tests for {@link GridSizeMigrationTaskV2} */
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class GridSizeMigrationTaskV2Test {
private LauncherModelHelper mModelHelper;
@@ -59,6 +59,17 @@
private HashSet<String> mValidPackages;
private InvariantDeviceProfile mIdp;
+ private final String testPackage1 = "com.android.launcher3.validpackage1";
+ private final String testPackage2 = "com.android.launcher3.validpackage2";
+ private final String testPackage3 = "com.android.launcher3.validpackage3";
+ private final String testPackage4 = "com.android.launcher3.validpackage4";
+ private final String testPackage5 = "com.android.launcher3.validpackage5";
+ private final String testPackage6 = "com.android.launcher3.validpackage6";
+ private final String testPackage7 = "com.android.launcher3.validpackage7";
+ private final String testPackage8 = "com.android.launcher3.validpackage8";
+ private final String testPackage9 = "com.android.launcher3.validpackage9";
+ private final String testPackage10 = "com.android.launcher3.validpackage10";
+
@Before
public void setUp() {
mModelHelper = new LauncherModelHelper();
@@ -67,6 +78,17 @@
mValidPackages = new HashSet<>();
mValidPackages.add(TEST_PACKAGE);
+ mValidPackages.add(testPackage1);
+ mValidPackages.add(testPackage2);
+ mValidPackages.add(testPackage3);
+ mValidPackages.add(testPackage4);
+ mValidPackages.add(testPackage5);
+ mValidPackages.add(testPackage6);
+ mValidPackages.add(testPackage7);
+ mValidPackages.add(testPackage8);
+ mValidPackages.add(testPackage9);
+ mValidPackages.add(testPackage10);
+
mIdp = InvariantDeviceProfile.INSTANCE.get(mContext);
long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
@@ -78,20 +100,6 @@
@Test
public void testMigration() {
- final String testPackage1 = "com.android.launcher3.validpackage1";
- final String testPackage2 = "com.android.launcher3.validpackage2";
- final String testPackage3 = "com.android.launcher3.validpackage3";
- final String testPackage4 = "com.android.launcher3.validpackage4";
- final String testPackage5 = "com.android.launcher3.validpackage5";
- final String testPackage7 = "com.android.launcher3.validpackage7";
-
- mValidPackages.add(testPackage1);
- mValidPackages.add(testPackage2);
- mValidPackages.add(testPackage3);
- mValidPackages.add(testPackage4);
- mValidPackages.add(testPackage5);
- mValidPackages.add(testPackage7);
-
int[] srcHotseatItems = {
mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
@@ -100,6 +108,10 @@
mModelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI),
};
mModelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage5, 5, TMP_CONTENT_URI);
+ mModelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage6, 6, TMP_CONTENT_URI);
+ mModelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage8, 8, TMP_CONTENT_URI);
+ mModelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage9, 9, TMP_CONTENT_URI);
+ mModelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 3, testPackage10, 10, TMP_CONTENT_URI);
int[] destHotseatItems = {
-1,
@@ -108,21 +120,24 @@
};
mModelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage7);
- mIdp.numHotseatIcons = 3;
- mIdp.numColumns = 3;
- mIdp.numRows = 3;
+ mIdp.numHotseatIcons = 4;
+ mIdp.numColumns = 4;
+ mIdp.numRows = 4;
GridSizeMigrationTaskV2.DbReader srcReader = new GridSizeMigrationTaskV2.DbReader(mDb,
- LauncherSettings.Favorites.TMP_TABLE, mContext, mValidPackages, 5);
+ LauncherSettings.Favorites.TMP_TABLE, mContext, mValidPackages,
+ srcHotseatItems.length);
GridSizeMigrationTaskV2.DbReader destReader = new GridSizeMigrationTaskV2.DbReader(mDb,
- LauncherSettings.Favorites.TABLE_NAME, mContext, mValidPackages, 3);
+ LauncherSettings.Favorites.TABLE_NAME, mContext, mValidPackages,
+ mIdp.numHotseatIcons);
GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
- destReader, 3, new Point(mIdp.numColumns, mIdp.numRows));
+ destReader, mIdp.numHotseatIcons, new Point(mIdp.numColumns, mIdp.numRows));
task.migrate();
+ // Check hotseat items
Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
"container=" + CONTAINER_HOTSEAT, null, null, null);
- assertEquals(c.getCount(), 3);
+ assertEquals(c.getCount(), mIdp.numHotseatIcons);
int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
c.moveToNext();
@@ -134,13 +149,17 @@
c.moveToNext();
assertEquals(c.getInt(screenIndex), 2);
assertTrue(c.getString(intentIndex).contains(testPackage3));
+ c.moveToNext();
+ assertEquals(c.getInt(screenIndex), 3);
+ assertTrue(c.getString(intentIndex).contains(testPackage4));
c.close();
+ // Check workspace items
c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
LauncherSettings.Favorites.INTENT},
"container=" + CONTAINER_DESKTOP, null, null, null);
- assertEquals(c.getCount(), 2);
+ assertEquals(c.getCount(), 6);
intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
int cellXIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLX);
int cellYIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLY);
@@ -148,7 +167,23 @@
c.moveToNext();
assertTrue(c.getString(intentIndex).contains(testPackage7));
c.moveToNext();
+ assertTrue(c.getString(intentIndex).contains(testPackage6));
+ assertEquals(c.getInt(cellXIndex), 0);
+ assertEquals(c.getInt(cellYIndex), 3);
+ c.moveToNext();
+ assertTrue(c.getString(intentIndex).contains(testPackage10));
+ assertEquals(c.getInt(cellXIndex), 1);
+ assertEquals(c.getInt(cellYIndex), 3);
+ c.moveToNext();
assertTrue(c.getString(intentIndex).contains(testPackage5));
+ assertEquals(c.getInt(cellXIndex), 2);
+ assertEquals(c.getInt(cellYIndex), 3);
+ c.moveToNext();
+ assertTrue(c.getString(intentIndex).contains(testPackage9));
+ assertEquals(c.getInt(cellXIndex), 3);
+ assertEquals(c.getInt(cellYIndex), 3);
+ c.moveToNext();
+ assertTrue(c.getString(intentIndex).contains(testPackage8));
assertEquals(c.getInt(cellXIndex), 0);
assertEquals(c.getInt(cellYIndex), 2);
}
diff --git a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 6e41a4f..3a252dc 100644
--- a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -50,17 +50,17 @@
import android.os.Process;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import com.android.launcher3.util.PackageManagerHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;
@@ -68,7 +68,7 @@
/**
* Tests for {@link LoaderCursor}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
public class LoaderCursorTest {
@@ -92,7 +92,8 @@
SCREEN, CELLX, CELLY, RESTORED, INTENT
});
- mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp);
+ mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp,
+ new UserManagerState());
mLoaderCursor.allUsers.put(0, Process.myUserHandle());
}
@@ -127,7 +128,7 @@
new Intent().setComponent(cn), false /* allowMissingTarget */, true))
.get();
assertNotNull(info);
- assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent));
+ assertTrue(PackageManagerHelper.isLauncherAppTarget(info.getIntent()));
}
@Test
@@ -141,7 +142,7 @@
new Intent().setComponent(cn), true /* allowMissingTarget */, true))
.get();
assertNotNull(info);
- assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent));
+ assertTrue(PackageManagerHelper.isLauncherAppTarget(info.getIntent()));
}
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
index c7979b2..87fe3c0 100644
--- a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
@@ -26,20 +26,20 @@
import android.os.Process;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.PagedView;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.ViewOnDrawExecutor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;
@@ -55,7 +55,7 @@
/**
* Tests to verify multiple callbacks in Loader
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
public class ModelMultiCallbacksTest {
@@ -199,7 +199,7 @@
}
@Override
- public void bindAllApplications(AppInfo[] apps) {
+ public void bindAllApplications(AppInfo[] apps, int flags) {
mAppInfos = apps;
}
diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index bd71f01..e43df21 100644
--- a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -2,16 +2,16 @@
import static org.junit.Assert.assertEquals;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.WorkspaceItemInfo;
+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.pm.PackageInstallInfo;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;
@@ -21,7 +21,7 @@
/**
* Tests for {@link PackageInstallStateChangedTask}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
public class PackageInstallStateChangedTaskTest {
diff --git a/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java b/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
index 7612ae1..83bf7da 100644
--- a/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
+++ b/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
@@ -27,10 +27,9 @@
import android.content.pm.ShortcutInfo;
-import com.android.launcher3.util.LauncherRoboTestRunner;
-
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
@@ -40,7 +39,7 @@
/**
* Tests the sorting and filtering of shortcuts in {@link PopupPopulator}.
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class PopupPopulatorTest {
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 7ef670c..ee73b82 100644
--- a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -23,16 +23,16 @@
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/**
* Tests for {@link RestoreDbTask}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class RestoreDbTaskTest {
@Test
@@ -95,7 +95,7 @@
private final long mProfileId;
MyDatabaseHelper(long profileId) {
- super(RuntimeEnvironment.application, null);
+ super(RuntimeEnvironment.application, null, false);
mProfileId = profileId;
}
diff --git a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
new file mode 100644
index 0000000..0ca5ce6
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.secondarydisplay;
+
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+import static com.android.launcher3.util.LauncherUIHelper.doLayout;
+import static com.android.launcher3.util.Preconditions.assertNotNull;
+
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.allapps.AllAppsRecyclerView;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.shadows.ShadowOverrides;
+import com.android.launcher3.util.LauncherLayoutBuilder;
+import com.android.launcher3.util.LauncherModelHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowUserManager;
+
+/**
+ * Tests for {@link SecondaryDisplayLauncher} with work profile
+ */
+@RunWith(RobolectricTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class SDWorkModeTest {
+
+ private static final int SYSTEM_USER = 0;
+ private static final int FLAG_SYSTEM = 0x00000800;
+ private static final int WORK_PROFILE_ID = 10;
+ private static final int FLAG_PROFILE = 0x00001000;
+
+ private Context mTargetContext;
+ private InvariantDeviceProfile mIdp;
+ private LauncherModelHelper mModelHelper;
+
+ private LauncherLayoutBuilder mLayoutBuilder;
+
+ @Before
+ public void setup() throws Exception {
+ mModelHelper = new LauncherModelHelper();
+ mTargetContext = RuntimeEnvironment.application;
+ mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
+ ShadowOverrides.setProvider(UserEventDispatcher.class,
+ c -> mock(UserEventDispatcher.class));
+ Settings.Global.putFloat(mTargetContext.getContentResolver(),
+ Settings.Global.WINDOW_ANIMATION_SCALE, 0);
+
+ mModelHelper.installApp(TEST_PACKAGE);
+ mLayoutBuilder = new LauncherLayoutBuilder();
+ }
+
+ @Test
+ public void testAllAppsList_noWorkProfile() throws Exception {
+ SecondaryDisplayLauncher launcher = loadLauncher();
+ launcher.showAppDrawer(true);
+ doLayout(launcher);
+
+ verifyRecyclerViewCount(launcher.getAppsView().getActiveRecyclerView());
+ }
+
+ @Test
+ public void testAllAppsList_workProfile() throws Exception {
+ ShadowUserManager sum = Shadow.extract(mTargetContext.getSystemService(UserManager.class));
+ sum.addUser(SYSTEM_USER, "me", FLAG_SYSTEM);
+ sum.addUser(WORK_PROFILE_ID, "work", FLAG_PROFILE);
+
+ SecondaryDisplayLauncher launcher = loadLauncher();
+ launcher.showAppDrawer(true);
+ doLayout(launcher);
+
+ AllAppsRecyclerView rv1 = launcher.getAppsView().getActiveRecyclerView();
+ verifyRecyclerViewCount(rv1);
+
+ assertNotNull(launcher.getAppsView().getWorkModeSwitch());
+ assertTrue(launcher.getAppsView().getRecyclerViewContainer() instanceof AllAppsPagedView);
+
+ AllAppsPagedView pagedView =
+ (AllAppsPagedView) launcher.getAppsView().getRecyclerViewContainer();
+ pagedView.snapToPageImmediately(1);
+ doLayout(launcher);
+
+ AllAppsRecyclerView rv2 = launcher.getAppsView().getActiveRecyclerView();
+ verifyRecyclerViewCount(rv2);
+ assertNotSame(rv1, rv2);
+ }
+
+ private SecondaryDisplayLauncher loadLauncher() throws Exception {
+ // Install 100 apps
+ for (int i = 0; i < 100; i++) {
+ mModelHelper.installApp(TEST_PACKAGE + i);
+ }
+ mModelHelper.setupDefaultLayoutProvider(new LauncherLayoutBuilder()).loadModelSync();
+ SecondaryDisplayLauncher launcher =
+ Robolectric.buildActivity(SecondaryDisplayLauncher.class).setup().get();
+ doLayout(launcher);
+ return launcher;
+ }
+
+ private void verifyRecyclerViewCount(AllAppsRecyclerView rv) {
+ int childCount = rv.getChildCount();
+ assertTrue(childCount > 0);
+ assertTrue(childCount < 100);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java
new file mode 100644
index 0000000..ae051f7
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java
@@ -0,0 +1,38 @@
+/*
+ * 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.shadows;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/**
+ * Shadow for {@link AppPredictionManager} which create mock predictors
+ */
+@Implements(value = AppPredictionManager.class)
+public class LShadowAppPredictionManager {
+
+ @Implementation
+ public AppPredictor createAppPredictionSession(AppPredictionContext predictionContext) {
+ return mock(AppPredictor.class);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java
new file mode 100644
index 0000000..da8b919
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java
@@ -0,0 +1,36 @@
+/*
+ * 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.shadows;
+
+import android.os.Process;
+import android.os.UserHandle;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowApplicationPackageManager;
+
+/**
+ * Shadow for {@link ShadowApplicationPackageManager} which create mock predictors
+ */
+@Implements(className = "android.app.ApplicationPackageManager")
+public class LShadowApplicationPackageManager extends ShadowApplicationPackageManager {
+
+ @Implementation
+ public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
+ return Process.myUserHandle().equals(user) ? label : "Work " + label;
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
new file mode 100644
index 0000000..3813fa1
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
@@ -0,0 +1,54 @@
+/*
+ * 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.shadows;
+
+import static org.robolectric.shadow.api.Shadow.directlyOn;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Display;
+
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.ShadowDisplay;
+
+/**
+ * Extension of {@link ShadowDisplay} with missing shadow methods
+ */
+@Implements(value = Display.class)
+public class LShadowDisplay extends ShadowDisplay {
+
+ private final Rect mInsets = new Rect();
+
+ @RealObject Display realObject;
+
+ /**
+ * Sets the insets for the display
+ */
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ }
+
+ @Override
+ protected void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
+ directlyOn(realObject, Display.class).getCurrentSizeRange(outSmallestSize, outLargestSize);
+ outSmallestSize.x -= mInsets.left + mInsets.right;
+ outLargestSize.x -= mInsets.left + mInsets.right;
+
+ outSmallestSize.y -= mInsets.top + mInsets.bottom;
+ outLargestSize.y -= mInsets.top + mInsets.bottom;
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
index 76cb747..6a6f0fb 100644
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
@@ -30,7 +30,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
-import android.os.Process;
import android.os.UserHandle;
import android.util.ArraySet;
@@ -80,14 +79,15 @@
protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
ResolveInfo ri = RuntimeEnvironment.application.getPackageManager()
.resolveActivity(intent, 0);
- return ri == null ? null : getLauncherActivityInfo(ri.activityInfo);
+ return ri == null ? null : getLauncherActivityInfo(ri.activityInfo, user);
}
- public LauncherActivityInfo getLauncherActivityInfo(ActivityInfo activityInfo) {
+ public LauncherActivityInfo getLauncherActivityInfo(
+ ActivityInfo activityInfo, UserHandle user) {
return callConstructor(LauncherActivityInfo.class,
ClassParameter.from(Context.class, RuntimeEnvironment.application),
ClassParameter.from(ActivityInfo.class, activityInfo),
- ClassParameter.from(UserHandle.class, Process.myUserHandle()));
+ ClassParameter.from(UserHandle.class, user));
}
@Implementation
@@ -104,7 +104,7 @@
.setPackage(packageName);
return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
.stream()
- .map(ri -> getLauncherActivityInfo(ri.activityInfo))
+ .map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
.collect(Collectors.toList());
}
@@ -130,7 +130,7 @@
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName);
return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
.stream()
- .map(ri -> getLauncherActivityInfo(ri.activityInfo))
+ .map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
.collect(Collectors.toList());
}
}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java
index 576ddbd..edf8edb 100644
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java
@@ -16,7 +16,6 @@
package com.android.launcher3.shadows;
-import android.os.Parcel;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.SparseBooleanArray;
@@ -51,12 +50,4 @@
public void setUserLocked(UserHandle userHandle, boolean enabled) {
mLockedUsers.put(userHandle.hashCode(), enabled);
}
-
- // Create user handle from parcel since UserHandle.of() was only added in later APIs.
- public static UserHandle newUserHandle(int uid) {
- Parcel userParcel = Parcel.obtain();
- userParcel.writeInt(uid);
- userParcel.setDataPosition(0);
- return new UserHandle(userParcel);
- }
}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
new file mode 100644
index 0000000..a9f2f27
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
@@ -0,0 +1,42 @@
+/*
+ * 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.shadows;
+
+import static org.robolectric.shadow.api.Shadow.invokeConstructor;
+import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
+
+import android.view.View;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+
+/**
+ * Shadow for SurfaceTransactionApplier to override default functionality
+ */
+@Implements(className = "com.android.quickstep.util.SurfaceTransactionApplier",
+ isInAndroidSdk = false)
+public class ShadowSurfaceTransactionApplier {
+
+ @RealObject
+ private Object mRealObject;
+
+ @Implementation
+ protected void __constructor__(View view) {
+ invokeConstructor(mRealObject.getClass(), mRealObject, from(View.class, null));
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
index 209bae0..d330d10 100644
--- a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
+++ b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
@@ -14,23 +14,20 @@
* limitations under the License.
*/package com.android.launcher3.ui;
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+import static com.android.launcher3.util.LauncherUIHelper.buildAndBindLauncher;
+import static com.android.launcher3.util.LauncherUIHelper.doLayout;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.Mockito.mock;
-import android.app.Activity;
import android.content.Context;
import android.os.SystemClock;
import android.provider.Settings;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerProperties;
-import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
@@ -44,24 +41,21 @@
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherLayoutBuilder.FolderBuilder;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LauncherRoboTestRunner;
-import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.widget.WidgetsFullSheet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;
import org.robolectric.shadows.ShadowLooper;
-import org.robolectric.util.ReflectionHelpers;
/**
* Tests scroll behavior at various Launcher UI components
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
public class LauncherUIScrollTest {
@@ -166,23 +160,7 @@
private Launcher loadLauncher() throws Exception {
mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder).loadModelSync();
-
- Launcher launcher = Robolectric.buildActivity(Launcher.class).setup().get();
- doLayout(launcher);
- ViewOnDrawExecutor executor = ReflectionHelpers.getField(launcher, "mPendingExecutor");
- if (executor != null) {
- executor.runAllTasks();
- }
- return launcher;
- }
-
- private static void doLayout(Activity activity) {
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE
- .get(RuntimeEnvironment.application).portraitProfile;
- View view = activity.getWindow().getDecorView();
- view.measure(makeMeasureSpec(dp.widthPx, EXACTLY), makeMeasureSpec(dp.heightPx, EXACTLY));
- view.layout(0, 0, dp.widthPx, dp.heightPx);
- ShadowLooper.idleMainLooper();
+ return buildAndBindLauncher();
}
private static MotionEvent createScrollEvent(int scroll) {
diff --git a/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java b/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
index e453e31..2f3fc37 100644
--- a/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
+++ b/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
@@ -1,16 +1,17 @@
package com.android.launcher3.util;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
/**
* Unit tests for {@link GridOccupancy}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class GridOccupancyTest {
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
index 5974ea5..c08e198 100644
--- a/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
+++ b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
@@ -19,11 +19,12 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
/**
* Robolectric unit tests for {@link IntArray}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class IntArrayTest {
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
index aedf71e..7a8c00b 100644
--- a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
+++ b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
@@ -17,17 +17,18 @@
import static com.google.common.truth.Truth.assertThat;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
/**
* Robolectric unit tests for {@link IntSet}
*/
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class IntSetTest {
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
index d3659eb..4e21dce 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
@@ -47,6 +47,7 @@
private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_CLASS_NAME = "className";
private static final String ATTR_TITLE = "title";
+ private static final String ATTR_TITLE_TEXT = "titleText";
private static final String ATTR_SCREEN = "screen";
// x and y can be specified as negative integers, in which case -1 represents the
@@ -145,8 +146,17 @@
}
public FolderBuilder putFolder(int titleResId) {
- FolderBuilder folderBuilder = new FolderBuilder();
items.put(ATTR_TITLE, Integer.toString(titleResId));
+ return putFolder();
+ }
+
+ public FolderBuilder putFolder(String title) {
+ items.put(ATTR_TITLE_TEXT, title);
+ return putFolder();
+ }
+
+ private FolderBuilder putFolder() {
+ FolderBuilder folderBuilder = new FolderBuilder();
items.put(ATTR_CHILDREN, folderBuilder.mChildren);
mNodes.add(Pair.create(TAG_FOLDER, items));
return folderBuilder;
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
index d593d84..0388087 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -36,9 +36,7 @@
import android.os.Process;
import android.provider.Settings;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
@@ -47,6 +45,8 @@
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.UserCache;
import org.mockito.ArgumentCaptor;
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
deleted file mode 100644
index 744b478b..0000000
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import static org.mockito.Mockito.mock;
-
-import com.android.launcher3.shadows.LShadowAppWidgetManager;
-import com.android.launcher3.shadows.LShadowBackupManager;
-import com.android.launcher3.shadows.LShadowBitmap;
-import com.android.launcher3.shadows.LShadowLauncherApps;
-import com.android.launcher3.shadows.LShadowTypeface;
-import com.android.launcher3.shadows.LShadowUserManager;
-import com.android.launcher3.shadows.LShadowWallpaperManager;
-import com.android.launcher3.shadows.ShadowDeviceFlag;
-import com.android.launcher3.shadows.ShadowLooperExecutor;
-import com.android.launcher3.shadows.ShadowMainThreadInitializedObject;
-import com.android.launcher3.shadows.ShadowOverrides;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-
-import org.junit.runners.model.InitializationError;
-import org.robolectric.DefaultTestLifecycle;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.TestLifecycle;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowLog;
-
-import java.lang.reflect.Method;
-
-import javax.annotation.Nonnull;
-
-/**
- * Test runner with Launcher specific configurations
- */
-public class LauncherRoboTestRunner extends RobolectricTestRunner {
-
- private static final Class<?>[] SHADOWS = new Class<?>[] {
- LShadowAppWidgetManager.class,
- LShadowUserManager.class,
- LShadowLauncherApps.class,
- LShadowBitmap.class,
- LShadowBackupManager.class,
- LShadowTypeface.class,
- LShadowWallpaperManager.class,
- ShadowLooperExecutor.class,
- ShadowMainThreadInitializedObject.class,
- ShadowDeviceFlag.class,
- ShadowOverrides.class
- };
-
- public LauncherRoboTestRunner(Class<?> testClass) throws InitializationError {
- super(testClass);
- }
-
- @Override
- protected Config buildGlobalConfig() {
- return new Config.Builder().setShadows(SHADOWS).build();
- }
-
- @Nonnull
- @Override
- protected Class<? extends TestLifecycle> getTestLifecycleClass() {
- return LauncherTestLifecycle.class;
- }
-
- public static class LauncherTestLifecycle extends DefaultTestLifecycle {
-
- @Override
- public void beforeTest(Method method) {
- super.beforeTest(method);
- ShadowLog.stream = System.out;
-
- // Disable plugins
- PluginManagerWrapper.INSTANCE.initializeForTesting(mock(PluginManagerWrapper.class));
-
- // Initialize mock wallpaper manager
- LShadowWallpaperManager.initializeMock();
- }
-
- @Override
- public void afterTest(Method method) {
- super.afterTest(method);
-
- ShadowLog.stream = null;
- ShadowMainThreadInitializedObject.resetInitializedObjects();
- ShadowOverrides.clearProvider();
- }
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java b/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java
new file mode 100644
index 0000000..6dd4df8
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java
@@ -0,0 +1,54 @@
+/*
+ * 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.util;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.Application;
+
+import com.android.launcher3.shadows.LShadowWallpaperManager;
+import com.android.launcher3.shadows.ShadowMainThreadInitializedObject;
+import com.android.launcher3.shadows.ShadowOverrides;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+
+import org.robolectric.TestLifecycleApplication;
+import org.robolectric.shadows.ShadowLog;
+
+import java.lang.reflect.Method;
+
+public class LauncherTestApplication extends Application implements TestLifecycleApplication {
+
+ @Override
+ public void beforeTest(Method method) {
+ ShadowLog.stream = System.out;
+
+ // Disable plugins
+ PluginManagerWrapper.INSTANCE.initializeForTesting(mock(PluginManagerWrapper.class));
+
+ // Initialize mock wallpaper manager
+ LShadowWallpaperManager.initializeMock();
+ }
+
+ @Override
+ public void prepareTest(Object test) { }
+
+ @Override
+ public void afterTest(Method method) {
+ ShadowLog.stream = null;
+ ShadowMainThreadInitializedObject.resetInitializedObjects();
+ ShadowOverrides.clearProvider();
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
new file mode 100644
index 0000000..f019a20
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
@@ -0,0 +1,102 @@
+/*
+ * 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.util;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.graphics.Point;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.launcher3.Launcher;
+
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.List;
+
+/**
+ * Utility class to help manage Launcher UI and related objects for test.
+ */
+public class LauncherUIHelper {
+
+ /**
+ * Returns the class name for the Launcher activity as defined in the manifest
+ */
+ public static String getLauncherClassName() {
+ Context context = RuntimeEnvironment.application;
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(context.getPackageName())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ List<ResolveInfo> launchers = context.getPackageManager()
+ .queryIntentActivities(homeIntent, 0);
+ if (launchers.size() != 1) {
+ return null;
+ }
+ return launchers.get(0).activityInfo.name;
+ }
+
+ /**
+ * Returns an activity controller for Launcher activity defined in the manifest
+ */
+ public static <T extends Launcher> ActivityController<T> buildLauncher() {
+ try {
+ Class<T> tClass = (Class<T>) Class.forName(getLauncherClassName());
+ return Robolectric.buildActivity(tClass);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates and binds a Launcher activity defined in the manifest.
+ * Note that the model must be bound before calling this
+ */
+ public static <T extends Launcher> T buildAndBindLauncher() {
+ ActivityController<T> controller = buildLauncher();
+
+ T launcher = controller.setup().get();
+ doLayout(launcher);
+ ViewOnDrawExecutor executor = ReflectionHelpers.getField(launcher, "mPendingExecutor");
+ if (executor != null) {
+ executor.runAllTasks();
+ }
+ return launcher;
+ }
+
+ /**
+ * Performs a measure and layout pass for the given activity
+ */
+ public static void doLayout(Activity activity) {
+ Point size = new Point();
+ RuntimeEnvironment.application.getSystemService(WindowManager.class)
+ .getDefaultDisplay().getSize(size);
+ View view = activity.getWindow().getDecorView();
+ view.measure(makeMeasureSpec(size.x, EXACTLY), makeMeasureSpec(size.y, EXACTLY));
+ view.layout(0, 0, size.x, size.y);
+ ShadowLooper.idleMainLooper();
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
index daae818..84c65b1 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -34,15 +34,15 @@
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.util.LauncherRoboTestRunner;
+import com.android.launcher3.model.data.PackageItemInfo;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.util.ReflectionHelpers;
@@ -50,7 +50,7 @@
import java.util.ArrayList;
import java.util.Collections;
-@RunWith(LauncherRoboTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
public class WidgetsListAdapterTest {
@Mock private LayoutInflater mMockLayoutInflater;
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index bed8278..cd27a2d 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -59,6 +59,7 @@
TYPE_DISCOVERY_BOUNCE,
TYPE_SNACKBAR,
TYPE_LISTENER,
+ TYPE_ALL_APPS_EDU,
TYPE_TASK_MENU,
TYPE_OPTIONS_POPUP
@@ -74,25 +75,28 @@
public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6;
public static final int TYPE_SNACKBAR = 1 << 7;
public static final int TYPE_LISTENER = 1 << 8;
+ public static final int TYPE_ALL_APPS_EDU = 1 << 9;
// Popups related to quickstep UI
- public static final int TYPE_TASK_MENU = 1 << 9;
- public static final int TYPE_OPTIONS_POPUP = 1 << 10;
+ public static final int TYPE_TASK_MENU = 1 << 10;
+ public static final int TYPE_OPTIONS_POPUP = 1 << 11;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
- | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER;
+ | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
- | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
+ | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
+ | TYPE_ALL_APPS_EDU;
// Usually we show the back button when a floating view is open. Instead, hide for these types.
public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
| TYPE_SNACKBAR | TYPE_WIDGET_RESIZE_FRAME | TYPE_LISTENER;
- public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER;
+ public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
+ & ~TYPE_ALL_APPS_EDU;
// These view all have particular operation associated with swipe down interaction.
public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
@@ -170,7 +174,8 @@
targetInfo.first, TYPE_WINDOW_STATE_CHANGED, targetInfo.second);
if (mIsOpen) {
- performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ getAccessibilityInitialFocusView().performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
}
ActivityContext.lookupContext(getContext()).getDragLayer()
.sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
@@ -180,6 +185,11 @@
return null;
}
+ /** Returns the View that Accessibility services should focus on first. */
+ protected View getAccessibilityInitialFocusView() {
+ return this;
+ }
+
/**
* Returns a view matching FloatingViewType
*/
@@ -253,4 +263,8 @@
@FloatingViewType int type) {
return getOpenView(activity, type);
}
+
+ public boolean canInterceptEventsInSystemGestureRegion() {
+ return false;
+ }
}
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 73d8a88..6f7b684 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -350,7 +350,7 @@
mWidgetView.requestLayout();
}
- static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
+ public static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
int spanX, int spanY) {
getWidgetSizeRanges(launcher, spanX, spanY, sTmpRect);
widgetView.updateAppWidgetSize(null, sTmpRect.left, sTmpRect.top,
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 71b7206..a55c90d 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -17,6 +17,7 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.ContentWriter;
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index ac43967..432073e 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -44,6 +44,8 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.PackageManagerHelper;
@@ -130,6 +132,7 @@
private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_CLASS_NAME = "className";
private static final String ATTR_TITLE = "title";
+ private static final String ATTR_TITLE_TEXT = "titleText";
private static final String ATTR_SCREEN = "screen";
// x and y can be specified as negative integers, in which case -1 represents the
@@ -583,7 +586,8 @@
if (titleResId != 0) {
title = mSourceRes.getString(titleResId);
} else {
- title = "";
+ String titleText = getAttributeValue(parser, ATTR_TITLE_TEXT);
+ title = TextUtils.isEmpty(titleText) ? "" : titleText;
}
mValues.put(Favorites.TITLE, title);
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 814b728..310c306 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -37,11 +37,7 @@
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.logging.StatsLogUtils;
-import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.ViewCache;
@@ -54,7 +50,7 @@
/**
* Launcher BaseActivity
*/
-public abstract class BaseActivity extends Activity implements LogStateProvider, ActivityContext {
+public abstract class BaseActivity extends Activity implements ActivityContext {
private static final String TAG = "BaseActivity";
@@ -110,9 +106,14 @@
public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4;
/**
+ * State flag indicating if the user will be active shortly.
+ */
+ public static final int ACTIVITY_STATE_USER_WILL_BE_ACTIVE = 1 << 5;
+
+ /**
* State flag indicating that a state transition is in progress
*/
- public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 5;
+ public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 6;
@Retention(SOURCE)
@IntDef(
@@ -143,13 +144,11 @@
return mDeviceProfile;
}
- public int getCurrentState() { return StatsLogUtils.LAUNCHER_STATE_BACKGROUND; }
-
public void modifyUserEvent(LauncherLogProto.LauncherEvent event) {}
public final StatsLogManager getStatsLogManager() {
if (mStatsLogManager == null) {
- mStatsLogManager = StatsLogManager.newInstance(this, this);
+ mStatsLogManager = StatsLogManager.newInstance(this);
}
return mStatsLogManager;
}
@@ -182,6 +181,7 @@
@Override
protected void onResume() {
addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE);
+ removeActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
super.onResume();
}
@@ -332,7 +332,6 @@
return;
}
try {
- TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: shortcut", packageName);
getSystemService(LauncherApps.class).startShortcut(packageName, id, sourceBounds,
startActivityOptions, user);
} catch (SecurityException | IllegalStateException e) {
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 6fa3c28..9cb8cf2 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -16,7 +16,7 @@
package com.android.launcher3;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.APP_LAUNCH_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.util.DefaultDisplay.CHANGE_ROTATION;
import android.app.ActivityOptions;
@@ -24,6 +24,8 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Process;
@@ -31,16 +33,21 @@
import android.os.UserHandle;
import android.util.Log;
import android.view.ActionMode;
+import android.view.Display;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.WindowInsets.Type;
+import android.view.WindowMetrics;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.util.DefaultDisplay;
@@ -49,6 +56,7 @@
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.WindowBounds;
/**
* Extension of BaseActivity allowing support for drag-n-drop
@@ -171,7 +179,6 @@
startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
} else if (user == null || user.equals(Process.myUserHandle())) {
// Could be launching some bookkeeping activity
- TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: activity", intent);
startActivity(intent, optsBundle);
AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
Process.myUserHandle(), sourceContainer);
@@ -182,7 +189,10 @@
sourceContainer);
}
getUserEventDispatcher().logAppLaunch(v, intent, user);
- getStatsLogManager().log(APP_LAUNCH_TAP, item.buildProto(null, null));
+ if (item != null) {
+ InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+ logAppLaunch(item, instanceId);
+ }
return true;
} catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
@@ -191,6 +201,11 @@
return false;
}
+ protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
+ getStatsLogManager().logger().withItemInfo(info).withInstanceId(instanceId)
+ .log(LAUNCHER_APP_LAUNCH_TAP);
+ }
+
private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info,
@Nullable String sourceContainer) {
try {
@@ -269,4 +284,20 @@
}
protected abstract void reapplyUi();
+
+ protected WindowBounds getMultiWindowDisplaySize() {
+ if (Utilities.ATLEAST_R) {
+ WindowMetrics wm = getWindowManager().getCurrentWindowMetrics();
+
+ Insets insets = wm.getWindowInsets().getInsets(Type.systemBars());
+ return new WindowBounds(wm.getBounds(),
+ new Rect(insets.left, insets.top, insets.right, insets.bottom));
+ }
+ // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return
+ // the app window size
+ Display display = getWindowManager().getDefaultDisplay();
+ Point mwSize = new Point();
+ display.getSize(mwSize);
+ return new WindowBounds(new Rect(0, 0, mwSize.x, mwSize.y), new Rect());
+ }
}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 9e8441a..8eceec0 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -16,17 +16,21 @@
package com.android.launcher3;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.RecyclerViewFastScroller;
@@ -179,12 +183,24 @@
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onScrollStateChanged: " + state);
- }
-
if (state == SCROLL_STATE_IDLE) {
AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
}
}
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (isLayoutSuppressed()) info.setScrollable(false);
+ }
+
+ @Override
+ public void setLayoutFrozen(boolean frozen) {
+ final boolean changing = frozen != isLayoutSuppressed();
+ super.setLayoutFrozen(frozen);
+ if (changing) {
+ ActivityContext.lookupContext(getContext()).getDragLayer()
+ .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 921e8ac..60b6da6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -29,6 +29,7 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -53,7 +54,13 @@
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconCache.IconLoadRequest;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
-import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.model.data.PromiseAppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.IconLabelDotView;
@@ -65,7 +72,7 @@
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
- IconLabelDotView, DraggableView {
+ IconLabelDotView, DraggableView, Reorderable {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@@ -73,6 +80,10 @@
private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
+ private final PointF mTranslationForReorderBounce = new PointF(0, 0);
+ private final PointF mTranslationForReorderPreview = new PointF(0, 0);
+
+ private float mScaleForReorderBounce = 1f;
private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
= new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
@@ -667,23 +678,79 @@
return mIconSize;
}
+ private void updateTranslation() {
+ super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
+ super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
+ }
+
+ public void setReorderBounceOffset(float x, float y) {
+ mTranslationForReorderBounce.set(x, y);
+ updateTranslation();
+ }
+
+ public void getReorderBounceOffset(PointF offset) {
+ offset.set(mTranslationForReorderBounce);
+ }
+
+ @Override
+ public void setReorderPreviewOffset(float x, float y) {
+ mTranslationForReorderPreview.set(x, y);
+ updateTranslation();
+ }
+
+ @Override
+ public void getReorderPreviewOffset(PointF offset) {
+ offset.set(mTranslationForReorderPreview);
+ }
+
+ public void setReorderBounceScale(float scale) {
+ mScaleForReorderBounce = scale;
+ super.setScaleX(scale);
+ super.setScaleY(scale);
+ }
+
+ public float getReorderBounceScale() {
+ return mScaleForReorderBounce;
+ }
+
+ public View getView() {
+ return this;
+ }
+
@Override
public int getViewType() {
return DRAGGABLE_ICON;
}
@Override
- public void getVisualDragBounds(Rect bounds) {
+ public void getWorkspaceVisualDragBounds(Rect bounds) {
DeviceProfile grid = mActivity.getDeviceProfile();
BubbleTextView.getIconBounds(this, bounds, grid.iconSizePx);
}
+ private int getIconSizeForDisplay(int display) {
+ DeviceProfile grid = mActivity.getDeviceProfile();
+ switch (display) {
+ case DISPLAY_ALL_APPS:
+ return grid.allAppsIconSizePx;
+ case DISPLAY_WORKSPACE:
+ case DISPLAY_FOLDER:
+ default:
+ return grid.iconSizePx;
+ }
+ }
+
+ public void getSourceVisualDragBounds(Rect bounds) {
+ BubbleTextView.getIconBounds(this, bounds, getIconSizeForDisplay(mDisplay));
+ }
+
@Override
- public void prepareDrawDragView() {
+ public SafeCloseable prepareDrawDragView() {
if (getIcon() instanceof FastBitmapDrawable) {
FastBitmapDrawable icon = (FastBitmapDrawable) getIcon();
icon.setScale(1f);
}
setForceHideDot(true);
+ return () -> { };
}
}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 34d7067..09827d6 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -46,6 +46,7 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 4259196..89d768c 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -33,6 +33,7 @@
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -54,18 +55,17 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.CellAndSpan;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.ParcelableSparseArray;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -98,6 +98,7 @@
// return an (x, y) value from helper functions. Do NOT use them to maintain other state.
@Thunk final int[] mTmpPoint = new int[2];
@Thunk final int[] mTempLocation = new int[2];
+ final PointF mTmpPointF = new PointF();
// Used to visualize / debug the Grid of the CellLayout
private static final boolean VISUALIZE_GRID = false;
@@ -135,7 +136,7 @@
private final Paint mDragOutlinePaint = new Paint();
@Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
- @Thunk final ArrayMap<View, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
+ @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
private boolean mItemPlacementDirty = false;
@@ -181,7 +182,7 @@
private static final Paint sPaint = new Paint();
// Related to accessible drag and drop
- private boolean mUseTouchHelper = false;
+ DragAndDropAccessibilityDelegate mTouchHelper;
public CellLayout(Context context) {
this(context, null);
@@ -289,17 +290,15 @@
addView(mShortcutsAndWidgets);
}
-
/**
* Sets or clears a delegate used for accessible drag and drop
*/
public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) {
setOnClickListener(delegate);
- setOnHoverListener(delegate);
ViewCompat.setAccessibilityDelegate(this, delegate);
- mUseTouchHelper = delegate != null;
- int accessibilityFlag = mUseTouchHelper
+ mTouchHelper = delegate;
+ int accessibilityFlag = mTouchHelper != null
? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO;
setImportantForAccessibility(accessibilityFlag);
getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
@@ -312,9 +311,18 @@
}
@Override
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ // Always attempt to dispatch hover events to accessibility first.
+ if (mTouchHelper != null && mTouchHelper.dispatchHoverEvent(event)) {
+ return true;
+ }
+ return super.dispatchHoverEvent(event);
+ }
+
+ @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (mUseTouchHelper ||
- (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
+ if (mTouchHelper != null
+ || (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
return true;
}
return false;
@@ -851,9 +859,10 @@
int delay, boolean permanent, boolean adjustOccupied) {
ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
- if (clc.indexOfChild(child) != -1) {
+ if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final ItemInfo info = (ItemInfo) child.getTag();
+ final Reorderable item = (Reorderable) child;
// We cancel any existing animations
if (mReorderAnimators.containsKey(lp)) {
@@ -861,13 +870,18 @@
mReorderAnimators.remove(lp);
}
- final int oldX = lp.x;
- final int oldY = lp.y;
+
if (adjustOccupied) {
GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
}
+
+ // Compute the new x and y position based on the new cellX and cellY
+ // We leverage the actual layout logic in the layout params and hence need to modify
+ // state and revert that state.
+ final int oldX = lp.x;
+ final int oldY = lp.y;
lp.isLockedToGrid = true;
if (permanent) {
lp.cellX = info.cellX = cellX;
@@ -877,15 +891,23 @@
lp.tmpCellY = cellY;
}
clc.setupLp(child);
- lp.isLockedToGrid = false;
final int newX = lp.x;
final int newY = lp.y;
-
lp.x = oldX;
lp.y = oldY;
+ lp.isLockedToGrid = false;
+ // End compute new x and y
+
+ item.getReorderPreviewOffset(mTmpPointF);
+ final float initPreviewOffsetX = mTmpPointF.x;
+ final float initPreviewOffsetY = mTmpPointF.y;
+ final float finalPreviewOffsetX = newX - oldX;
+ final float finalPreviewOffsetY = newY - oldY;
+
// Exit early if we're not actually moving the view
- if (oldX == newX && oldY == newY) {
+ if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0
+ && initPreviewOffsetX == 0 && initPreviewOffsetY == 0) {
lp.isLockedToGrid = true;
return true;
}
@@ -898,9 +920,9 @@
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float r = (Float) animation.getAnimatedValue();
- lp.x = (int) ((1 - r) * oldX + r * newX);
- lp.y = (int) ((1 - r) * oldY + r * newY);
- child.requestLayout();
+ float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX;
+ float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY;
+ item.setReorderPreviewOffset(x, y);
}
});
va.addListener(new AnimatorListenerAdapter() {
@@ -911,6 +933,7 @@
// place just yet.
if (!cancelled) {
lp.isLockedToGrid = true;
+ item.setReorderPreviewOffset(0, 0);
child.requestLayout();
}
if (mReorderAnimators.containsKey(lp)) {
@@ -959,8 +982,13 @@
height = r.height();
}
- if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) {
- left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
+ // Center horizontaly
+ left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
+
+ if (v != null && v.getViewType() == DraggableView.DRAGGABLE_WIDGET) {
+ // Center vertically
+ top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
+ } else if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) {
int cHeight = getShortcutsAndWidgets().getCellContentHeight();
int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
top += cellPaddingY;
@@ -1862,10 +1890,11 @@
boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
!= null && !solution.intersectingViews.contains(child);
+
LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (c != null && !skip) {
- ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
- lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
+ if (c != null && !skip && (child instanceof Reorderable)) {
+ ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
+ mode, lp.cellX, lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
rha.animate();
}
}
@@ -1887,7 +1916,7 @@
// Class which represents the reorder preview animations. These animations show that an item is
// in a temporary state, and hint at where the item will return to.
class ReorderPreviewAnimation {
- final View child;
+ final Reorderable child;
float finalDeltaX;
float finalDeltaY;
float initDeltaX;
@@ -1907,8 +1936,8 @@
float animationProgress = 0;
ValueAnimator a;
- public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
- int cellY1, int spanX, int spanY) {
+ public ReorderPreviewAnimation(Reorderable child, int mode, int cellX0, int cellY0,
+ int cellX1, int cellY1, int spanX, int spanY) {
regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
final int x0 = mTmpPoint[0];
final int y0 = mTmpPoint[1];
@@ -1920,63 +1949,60 @@
this.child = child;
this.mode = mode;
+ finalDeltaX = 0;
+ finalDeltaY = 0;
- // TODO issue!
- setInitialAnimationValues(false);
- finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
- finalDeltaX = initDeltaX;
- finalDeltaY = initDeltaY;
+ child.getReorderBounceOffset(mTmpPointF);
+ initDeltaX = mTmpPointF.x;
+ initDeltaY = mTmpPointF.y;
+ initScale = child.getReorderBounceScale();
+ finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
+
int dir = mode == MODE_HINT ? -1 : 1;
if (dX == dY && dX == 0) {
} else {
if (dY == 0) {
- finalDeltaX += - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
+ finalDeltaX = -dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
} else if (dX == 0) {
- finalDeltaY += - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
+ finalDeltaY = -dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
} else {
double angle = Math.atan( (float) (dY) / dX);
- finalDeltaX += (int) (- dir * Math.signum(dX) *
- Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
- finalDeltaY += (int) (- dir * Math.signum(dY) *
- Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
+ finalDeltaX = (int) (-dir * Math.signum(dX)
+ * Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
+ finalDeltaY = (int) (-dir * Math.signum(dY)
+ * Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
}
}
}
- void setInitialAnimationValues(boolean restoreOriginalValues) {
- if (restoreOriginalValues) {
- if (child instanceof LauncherAppWidgetHostView) {
- LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
- initScale = lahv.getScaleToFit();
- initDeltaX = lahv.getTranslationForCentering().x;
- initDeltaY = lahv.getTranslationForCentering().y;
- } else {
- initScale = mChildScale;
- initDeltaX = 0;
- initDeltaY = 0;
- }
- } else {
- initScale = child.getScaleX();
- initDeltaX = child.getTranslationX();
- initDeltaY = child.getTranslationY();
- }
+ void setInitialAnimationValuesToBaseline() {
+ initScale = mChildScale;
+ initDeltaX = 0;
+ initDeltaY = 0;
}
void animate() {
- boolean noMovement = (finalDeltaX == initDeltaX) && (finalDeltaY == initDeltaY);
+ boolean noMovement = (finalDeltaX == 0) && (finalDeltaY == 0);
if (mShakeAnimators.containsKey(child)) {
ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
- oldAnimation.cancel();
mShakeAnimators.remove(child);
+
if (noMovement) {
- completeAnimationImmediately();
+ // A previous animation for this item exists, and no new animation will exist.
+ // Finish the old animation smoothly.
+ oldAnimation.finishAnimation();
return;
+ } else {
+ // A previous animation for this item exists, and a new one will exist. Stop
+ // the old animation in its tracks, and proceed with the new one.
+ oldAnimation.cancel();
}
}
if (noMovement) {
return;
}
+
ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
a = va;
@@ -1993,7 +2019,7 @@
va.addListener(new AnimatorListenerAdapter() {
public void onAnimationRepeat(Animator animation) {
// We make sure to end only after a full period
- setInitialAnimationValues(true);
+ setInitialAnimationValuesToBaseline();
repeating = true;
}
});
@@ -2006,11 +2032,9 @@
float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
- child.setTranslationX(x);
- child.setTranslationY(y);
+ child.setReorderBounceOffset(x, y);
float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
- child.setScaleX(s);
- child.setScaleY(s);
+ child.setReorderBounceScale(s);
}
private void cancel() {
@@ -2019,27 +2043,27 @@
}
}
- @Thunk void completeAnimationImmediately() {
+ /**
+ * Smoothly returns the item to its baseline position / scale
+ */
+ @Thunk void finishAnimation() {
if (a != null) {
a.cancel();
}
- setInitialAnimationValues(true);
- a = new PropertyListBuilder()
- .scale(initScale)
- .translationX(initDeltaX)
- .translationY(initDeltaY)
- .build(child)
- .setDuration(REORDER_ANIMATION_DURATION);
- Launcher.cast(mActivity).getDragController().addFirstFrameAnimationHelper(a);
+ setInitialAnimationValuesToBaseline();
+ ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
+ animationProgress, 0);
+ a = va;
a.setInterpolator(DEACCEL_1_5);
+ a.setDuration(REORDER_ANIMATION_DURATION);
a.start();
}
}
private void completeAndClearReorderPreviewAnimations() {
for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
- a.completeAnimationImmediately();
+ a.finishAnimation();
}
mShakeAnimators.clear();
}
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index ef353f9..ff405ec 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -113,7 +113,10 @@
}
private void triggerLongPress() {
- if ((mView.getParent() != null) && mView.hasWindowFocus() && !mHasPerformedLongPress) {
+ if ((mView.getParent() != null)
+ && mView.hasWindowFocus()
+ && (!mView.isPressed() || mListener == null)
+ && !mHasPerformedLongPress) {
boolean handled;
if (mListener != null) {
handled = mListener.onLongClick(mView);
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 6f0ebd2..2857497 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_CANCEL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.UNDO;
@@ -27,13 +29,20 @@
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.views.Snackbar;
public class DeleteDropTarget extends ButtonDropTarget {
+ private final StatsLogManager mStatsLogManager;
+
private int mControlType = ControlType.DEFAULT_CONTROLTYPE;
public DeleteDropTarget(Context context, AttributeSet attrs) {
@@ -42,6 +51,7 @@
public DeleteDropTarget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ this.mStatsLogManager = StatsLogManager.newInstance(context);
}
@Override
@@ -116,6 +126,9 @@
d.dragInfo.container = NO_ID;
}
super.onDrop(d, options);
+ mStatsLogManager.logger().withInstanceId(d.logInstanceId)
+ .log(mControlType == ControlType.REMOVE_TARGET ? LAUNCHER_ITEM_DROPPED_ON_REMOVE
+ : LAUNCHER_ITEM_DROPPED_ON_CANCEL);
}
@Override
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 4e1e586..e3cd0bd 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -22,22 +22,23 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.util.DisplayMetrics;
import android.view.Surface;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.WindowBounds;
public class DeviceProfile {
+ private static final float TABLET_MIN_DPS = 600;
+ private static final float LARGE_TABLET_MIN_DPS = 720;
+
+
public final InvariantDeviceProfile inv;
- // IDP with no grid override values.
- @Nullable private final InvariantDeviceProfile originalIdp;
+ private final DefaultDisplay.Info mInfo;
// Device properties
public final boolean isTablet;
@@ -49,6 +50,8 @@
public final boolean isLandscape;
public final boolean isMultiWindowMode;
+ public final int windowX;
+ public final int windowY;
public final int widthPx;
public final int heightPx;
public final int availableWidthPx;
@@ -137,14 +140,16 @@
public DotRenderer mDotRendererWorkSpace;
public DotRenderer mDotRendererAllApps;
- public DeviceProfile(Context context, InvariantDeviceProfile inv,
- InvariantDeviceProfile originalIDP, Point minSize, Point maxSize,
- int width, int height, boolean isLandscape, boolean isMultiWindowMode) {
+ DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info,
+ Point minSize, Point maxSize, int width, int height, boolean isLandscape,
+ boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
+ Point windowPosition) {
this.inv = inv;
- this.originalIdp = inv;
this.isLandscape = isLandscape;
this.isMultiWindowMode = isMultiWindowMode;
+ windowX = windowPosition.x;
+ windowY = windowPosition.y;
// Determine sizes.
widthPx = width;
@@ -157,24 +162,24 @@
availableHeightPx = maxSize.y;
}
- Resources res = context.getResources();
- DisplayMetrics dm = res.getDisplayMetrics();
+ mInfo = info;
// Constants from resources
- isTablet = res.getBoolean(R.bool.is_tablet);
- isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
+ float swDPs = Utilities.dpiFromPx(
+ Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics);
+ isTablet = swDPs >= TABLET_MIN_DPS;
+ isLargeTablet = swDPs >= LARGE_TABLET_MIN_DPS;
isPhone = !isTablet && !isLargeTablet;
aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
// Some more constants
- transposeLayoutWithOrientation =
- res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+ this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;
- context = getContext(context, isVerticalBarLayout()
+ context = getContext(context, info, isVerticalBarLayout()
? Configuration.ORIENTATION_LANDSCAPE
: Configuration.ORIENTATION_PORTRAIT);
- res = context.getResources();
+ final Resources res = context.getResources();
edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
@@ -212,13 +217,14 @@
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
// Add a bit of space between nav bar and hotseat in vertical bar layout.
hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
- hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout()
+ hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics)
+ + (isVerticalBarLayout()
? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
: (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+ hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx));
// Calculate all of the remaining variables.
- updateAvailableDimensions(dm, res);
+ updateAvailableDimensions(res);
// Now that we have all of the variables calculated, we can tune certain sizes.
if (!isVerticalBarLayout() && isPhone && isTallDevice) {
@@ -232,20 +238,7 @@
hotseatBarBottomPaddingPx += extraSpace;
// Recalculate the available dimensions using the new hotseat size.
- updateAvailableDimensions(dm, res);
- }
-
- if (originalIDP != null) {
- // Grid size change should not affect All Apps UI, so we use the original profile
- // measurements here.
- DeviceProfile originalProfile = isLandscape
- ? originalIDP.landscapeProfile
- : originalIDP.portraitProfile;
- allAppsIconSizePx = originalProfile.iconSizePx;
- allAppsIconTextSizePx = originalProfile.iconTextSizePx;
- allAppsCellHeightPx = originalProfile.allAppsCellHeightPx;
- allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx;
- allAppsCellWidthPx = allAppsIconSizePx + 2 * allAppsIconDrawablePaddingPx;
+ updateAvailableDimensions(res);
}
updateWorkspacePadding();
@@ -257,22 +250,34 @@
IconShape.DEFAULT_PATH_SIZE);
}
- public DeviceProfile copy(Context context) {
+ public Builder toBuilder(Context context) {
Point size = new Point(availableWidthPx, availableHeightPx);
- return new DeviceProfile(context, inv, originalIdp, size, size, widthPx, heightPx,
- isLandscape, isMultiWindowMode);
+ return new Builder(context, inv, mInfo)
+ .setSizeRange(size, size)
+ .setSize(widthPx, heightPx)
+ .setWindowPosition(windowX, windowY)
+ .setMultiWindowMode(isMultiWindowMode);
}
- public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
+ public DeviceProfile copy(Context context) {
+ return toBuilder(context).build();
+ }
+
+ /**
+ * TODO: Move this to the builder as part of setMultiWindowMode
+ */
+ public DeviceProfile getMultiWindowProfile(Context context, WindowBounds windowBounds) {
// We take the minimum sizes of this profile and it's multi-window variant to ensure that
// the system decor is always excluded.
- mwSize.set(Math.min(availableWidthPx, mwSize.x), Math.min(availableHeightPx, mwSize.y));
+ Point mwSize = new Point(Math.min(availableWidthPx, windowBounds.availableSize.x),
+ Math.min(availableHeightPx, windowBounds.availableSize.y));
- // In multi-window mode, we can have widthPx = availableWidthPx
- // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
- // widthPx and heightPx values where it's needed.
- DeviceProfile profile = new DeviceProfile(context, inv, null, mwSize, mwSize,
- mwSize.x, mwSize.y, isLandscape, true);
+ DeviceProfile profile = toBuilder(context)
+ .setSizeRange(mwSize, mwSize)
+ .setSize(windowBounds.bounds.width(), windowBounds.bounds.height())
+ .setWindowPosition(windowBounds.bounds.left, windowBounds.bounds.top)
+ .setMultiWindowMode(true)
+ .build();
// If there isn't enough vertical cell padding with the labels displayed, hide the labels.
float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
@@ -292,7 +297,7 @@
}
/**
- * Inverse of {@link #getMultiWindowProfile(Context, Point)}
+ * Inverse of {@link #getMultiWindowProfile(Context, WindowBounds)}
* @return device profile corresponding to the current orientation in non multi-window mode.
*/
public DeviceProfile getFullScreenProfile() {
@@ -307,27 +312,30 @@
iconTextSizePx = 0;
iconDrawablePaddingPx = 0;
cellHeightPx = iconSizePx;
+ autoResizeAllAppsCells();
+ }
- // In normal cases, All Apps cell height should equal the Workspace cell height.
- // Since we are removing labels from the Workspace, we need to manually compute the
- // All Apps cell height.
+ /**
+ * Re-computes the all-apps cell size to be independent of workspace
+ */
+ public void autoResizeAllAppsCells() {
int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1);
allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
+ Utilities.calculateTextHeight(allAppsIconTextSizePx)
+ topBottomPadding * 2;
}
- private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
- updateIconSize(1f, res, dm);
+ private void updateAvailableDimensions(Resources res) {
+ updateIconSize(1f, res);
// Check to see if the icons fit within the available height. If not, then scale down.
float usedHeight = (cellHeightPx * inv.numRows);
int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
if (usedHeight > maxHeight) {
float scale = maxHeight / usedHeight;
- updateIconSize(scale, res, dm);
+ updateIconSize(scale, res);
}
- updateAvailableFolderCellDimensions(dm, res);
+ updateAvailableFolderCellDimensions(res);
}
/**
@@ -335,12 +343,13 @@
* iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
* hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
*/
- private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
+ private void updateIconSize(float scale, Resources res) {
// Workspace
final boolean isVerticalLayout = isVerticalBarLayout();
float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
- iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale));
- iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
+ iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, mInfo.metrics)
+ * scale));
+ iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale);
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
cellHeightPx = iconSizePx + iconDrawablePaddingPx
@@ -356,11 +365,19 @@
}
cellWidthPx = iconSizePx + iconDrawablePaddingPx;
- allAppsIconSizePx = iconSizePx;
- allAppsIconTextSizePx = iconTextSizePx;
- allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
- allAppsCellHeightPx = getCellSize().y;
- allAppsCellWidthPx = allAppsIconSizePx + 2 * allAppsIconDrawablePaddingPx;
+ // All apps
+ if (allAppsHasDifferentNumColumns()) {
+ allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, mInfo.metrics);
+ allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, mInfo.metrics);
+ allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y;
+ allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+ } else {
+ allAppsIconSizePx = iconSizePx;
+ allAppsIconTextSizePx = iconTextSizePx;
+ allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
+ allAppsCellHeightPx = getCellSize().y;
+ }
+ allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
if (isVerticalBarLayout()) {
// Always hide the Workspace text with vertical bar layout.
@@ -391,12 +408,12 @@
folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
}
- private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
+ private void updateAvailableFolderCellDimensions(Resources res) {
int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
+ res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
+ Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
- updateFolderCellSize(1f, dm, res);
+ updateFolderCellSize(1f, res);
// Don't let the folder get too close to the edges of the screen.
int folderMargin = edgeMarginPx * 2;
@@ -415,12 +432,12 @@
float scale = Math.min(scaleX, scaleY);
if (scale < 1f) {
- updateFolderCellSize(scale, dm, res);
+ updateFolderCellSize(scale, res);
}
}
- private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
- folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, dm) * scale);
+ private void updateFolderCellSize(float scale, Resources res) {
+ folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) * scale);
folderChildTextSizePx =
(int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
@@ -521,7 +538,6 @@
mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom);
}
} else {
-
// We want the edges of the hotseat to line up with the edges of the workspace, but the
// icons in the hotseat are a different size, and so don't line up perfectly. To account
// for this, we pad the left and right of the hotseat with half of the difference of a
@@ -530,9 +546,11 @@
float hotseatCellWidth = (float) widthPx / inv.numHotseatIcons;
int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
mHotseatPadding.set(
- hotseatAdjustment + workspacePadding.left + cellLayoutPaddingLeftRightPx,
+ hotseatAdjustment + workspacePadding.left + cellLayoutPaddingLeftRightPx
+ + mInsets.left,
hotseatBarTopPaddingPx,
- hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
+ hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx
+ + mInsets.right,
hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx);
}
return mHotseatPadding;
@@ -618,10 +636,11 @@
}
}
- private static Context getContext(Context c, int orientation) {
- Configuration context = new Configuration(c.getResources().getConfiguration());
- context.orientation = orientation;
- return c.createConfigurationContext(context);
+ private static Context getContext(Context c, DefaultDisplay.Info info, int orientation) {
+ Configuration config = new Configuration(c.getResources().getConfiguration());
+ config.orientation = orientation;
+ config.densityDpi = info.metrics.densityDpi;
+ return c.createConfigurationContext(config);
}
/**
@@ -637,4 +656,64 @@
*/
void onDeviceProfileChanged(DeviceProfile dp);
}
+
+ public static class Builder {
+ private Context mContext;
+ private InvariantDeviceProfile mInv;
+ private DefaultDisplay.Info mInfo;
+
+ private final Point mWindowPosition = new Point();
+ private Point mMinSize, mMaxSize;
+ private int mWidth, mHeight;
+
+ private boolean mIsLandscape;
+ private boolean mIsMultiWindowMode = false;
+ private boolean mTransposeLayoutWithOrientation;
+
+ public Builder(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info) {
+ mContext = context;
+ mInv = inv;
+ mInfo = info;
+ mTransposeLayoutWithOrientation = context.getResources()
+ .getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+ }
+
+ public Builder setSizeRange(Point minSize, Point maxSize) {
+ mMinSize = minSize;
+ mMaxSize = maxSize;
+ return this;
+ }
+
+ public Builder setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ mIsLandscape = mWidth > mHeight;
+ return this;
+ }
+
+ public Builder setMultiWindowMode(boolean isMultiWindowMode) {
+ mIsMultiWindowMode = isMultiWindowMode;
+ return this;
+ }
+
+ /**
+ * Sets the window position if not full-screen
+ */
+ public Builder setWindowPosition(int x, int y) {
+ mWindowPosition.set(x, y);
+ return this;
+ }
+
+ public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) {
+ mTransposeLayoutWithOrientation = transposeLayoutWithOrientation;
+ return this;
+ }
+
+ public DeviceProfile build() {
+ return new DeviceProfile(mContext, mInv, mInfo, mMinSize, mMaxSize,
+ mWidth, mHeight, mIsLandscape, mIsMultiWindowMode,
+ mTransposeLayoutWithOrientation, mWindowPosition);
+ }
+ }
+
}
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index ef02e87..b27abc4 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -25,6 +25,10 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.FolderNameProvider;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.Executors;
/**
* Interface defining an object that can receive a drag.
@@ -74,9 +78,16 @@
* DragView represents. May be an actual View class or a virtual stand-in */
public DraggableView originalView = null;
+ /** Used for matching DROP event with its corresponding DRAG event on the server side. */
+ public final InstanceId logInstanceId =
+ new InstanceIdSequence(1 << 20 /*InstanceId.INSTANCE_ID_MAX*/)
+ .newInstanceId();
+
public DragObject(Context context) {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- folderNameProvider = FolderNameProvider.newInstance(context);
+ Executors.MODEL_EXECUTOR.post(() -> {
+ folderNameProvider = FolderNameProvider.newInstance(context);
+ });
}
}
@@ -102,6 +113,18 @@
return res;
}
+
+
+ /**
+ * This is used to determine if an object is dropped at a different location than it was
+ * dragged from
+ */
+ public boolean isMoved() {
+ return dragInfo.cellX != originalDragInfo.cellX
+ || dragInfo.cellY != originalDragInfo.cellY
+ || dragInfo.screenId != originalDragInfo.screenId
+ || dragInfo.container != originalDragInfo.container;
+ }
}
/**
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index a78159f..d3b86de 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -35,6 +35,8 @@
import com.android.launcher3.graphics.PlaceHolderIconDrawable;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.util.Themes;
public class FastBitmapDrawable extends Drawable {
@@ -43,7 +45,6 @@
private static final float DISABLED_DESATURATION = 1f;
private static final float DISABLED_BRIGHTNESS = 0.5f;
- private static final float DISABLED_ALPHA = 0.54f;
public static final int CLICK_FEEDBACK_DURATION = 200;
@@ -55,6 +56,7 @@
private boolean mIsPressed;
private boolean mIsDisabled;
+ private float mDisabledAlpha = 1f;
// Animator and properties for the fast bitmap drawable's scale
private static final Property<FastBitmapDrawable, Float> SCALE
@@ -252,7 +254,7 @@
mat[4] = brightnessI;
mat[9] = brightnessI;
mat[14] = brightnessI;
- mat[18] = DISABLED_ALPHA;
+ mat[18] = mDisabledAlpha;
tempFilterMatrix.preConcat(tempBrightnessMatrix);
sDisabledFColorFilter = new ColorMatrixColorFilter(tempFilterMatrix);
}
@@ -318,12 +320,15 @@
* Creates a drawable for the provided BitmapInfo
*/
public static FastBitmapDrawable newIcon(Context context, BitmapInfo info) {
+ final FastBitmapDrawable drawable;
if (info instanceof Factory) {
- return ((Factory) info).newDrawable();
+ drawable = ((Factory) info).newDrawable();
} else if (info.isLowRes()) {
- return new PlaceHolderIconDrawable(info, context);
+ drawable = new PlaceHolderIconDrawable(info, context);
} else {
- return new FastBitmapDrawable(info);
+ drawable = new FastBitmapDrawable(info);
}
+ drawable.mDisabledAlpha = Themes.getFloat(context, R.attr.disabledIconAlpha, 1f);
+ return drawable;
}
}
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index f07040d..e5aecf7 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -25,6 +25,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderPagedView;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.FocusLogic;
import com.android.launcher3.util.Thunk;
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
deleted file mode 100644
index b75a5e7..0000000
--- a/src/com/android/launcher3/FolderInfo.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.Intent;
-import android.os.Process;
-
-import com.android.launcher3.model.ModelWriter;
-import com.android.launcher3.util.ContentWriter;
-
-import java.util.ArrayList;
-
-/**
- * Represents a folder containing shortcuts or apps.
- */
-public class FolderInfo extends ItemInfo {
-
- public static final int NO_FLAGS = 0x00000000;
-
- /**
- * The folder is locked in sorted mode
- */
- public static final int FLAG_ITEMS_SORTED = 0x00000001;
-
- /**
- * It is a work folder
- */
- public static final int FLAG_WORK_FOLDER = 0x00000002;
-
- /**
- * The multi-page animation has run for this folder
- */
- public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
-
- public static final int FLAG_MANUAL_FOLDER_NAME = 0x00000008;
-
- public static final String EXTRA_FOLDER_SUGGESTIONS = "suggest";
-
- public int options;
-
- public Intent suggestedFolderNames;
-
- /**
- * The apps and shortcuts
- */
- public ArrayList<WorkspaceItemInfo> contents = new ArrayList<>();
-
- private ArrayList<FolderListener> mListeners = new ArrayList<>();
-
- public FolderInfo() {
- itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
- user = Process.myUserHandle();
- }
-
- /**
- * Add an app or shortcut
- *
- * @param item
- */
- public void add(WorkspaceItemInfo item, boolean animate) {
- add(item, contents.size(), animate);
- }
-
- /**
- * Add an app or shortcut for a specified rank.
- */
- public void add(WorkspaceItemInfo item, int rank, boolean animate) {
- rank = Utilities.boundToRange(rank, 0, contents.size());
- contents.add(rank, item);
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAdd(item, rank);
- }
- itemsChanged(animate);
- }
-
- /**
- * Remove an app or shortcut. Does not change the DB.
- *
- * @param item
- */
- public void remove(WorkspaceItemInfo item, boolean animate) {
- contents.remove(item);
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onRemove(item);
- }
- itemsChanged(animate);
- }
-
- @Override
- public void onAddToDatabase(ContentWriter writer) {
- super.onAddToDatabase(writer);
- writer.put(LauncherSettings.Favorites.TITLE, title)
- .put(LauncherSettings.Favorites.OPTIONS, options);
- }
-
- public void addListener(FolderListener listener) {
- mListeners.add(listener);
- }
-
- public void removeListener(FolderListener listener) {
- mListeners.remove(listener);
- }
-
- public void itemsChanged(boolean animate) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onItemsChanged(animate);
- }
- }
-
- public interface FolderListener {
- public void onAdd(WorkspaceItemInfo item, int rank);
- public void onRemove(WorkspaceItemInfo item);
- public void onItemsChanged(boolean animate);
- }
-
- public boolean hasOption(int optionFlag) {
- return (options & optionFlag) != 0;
- }
-
- /**
- * @param option flag to set or clear
- * @param isEnabled whether to set or clear the flag
- * @param writer if not null, save changes to the db.
- */
- public void setOption(int option, boolean isEnabled, ModelWriter writer) {
- int oldOptions = options;
- if (isEnabled) {
- options |= option;
- } else {
- options &= ~option;
- }
- if (writer != null && oldOptions != options) {
- writer.updateItemInDatabase(this);
- }
- }
-
- @Override
- protected String dumpProperties() {
- return super.dumpProperties()
- + " manuallyTypedTitle=" + hasOption(FLAG_MANUAL_FOLDER_NAME);
- }
-}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 78bd2ff..51f3819 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -28,6 +28,7 @@
import android.widget.FrameLayout;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -37,6 +38,8 @@
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mHasVerticalHotseat;
+ private Workspace mWorkspace;
+ private boolean mSendTouchToWorkspace;
public Hotseat(Context context) {
this(context, null);
@@ -88,7 +91,6 @@
public void setInsets(Rect insets) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
DeviceProfile grid = mActivity.getDeviceProfile();
- insets = grid.getInsets();
if (grid.isVerticalBarLayout()) {
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -111,8 +113,35 @@
InsettableFrameLayout.dispatchInsets(this, insets);
}
+ public void setWorkspace(Workspace w) {
+ mWorkspace = w;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // We allow horizontal workspace scrolling from within the Hotseat. We do this by delegating
+ // touch intercept the Workspace, and if it intercepts, delegating touch to the Workspace
+ // for the remainder of the this input stream.
+ int yThreshold = getMeasuredHeight() - getPaddingBottom();
+ if (mWorkspace != null && ev.getY() <= yThreshold) {
+ mSendTouchToWorkspace = mWorkspace.onInterceptTouchEvent(ev);
+ return mSendTouchToWorkspace;
+ }
+ return false;
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
+ // See comment in #onInterceptTouchEvent
+ if (mSendTouchToWorkspace) {
+ final int action = event.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mSendTouchToWorkspace = false;
+ }
+ return mWorkspace.onTouchEvent(event);
+ }
return event.getY() > getCellHeight();
}
}
diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java
index faa18b8..9a66d32 100644
--- a/src/com/android/launcher3/InsettableFrameLayout.java
+++ b/src/com/android/launcher3/InsettableFrameLayout.java
@@ -91,6 +91,9 @@
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
+ if (!isAttachedToWindow()) {
+ return;
+ }
setFrameLayoutChildInsets(child, mInsets, new Rect());
}
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index a5142d8..62c9b4d 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -46,6 +46,10 @@
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.model.data.AppInfo;
+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.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 7414a88..e39e89c 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -17,8 +17,8 @@
package com.android.launcher3;
import static com.android.launcher3.Utilities.getDevicePrefs;
+import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
-import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
@@ -47,7 +47,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.DefaultDisplay;
import com.android.launcher3.util.DefaultDisplay.Info;
@@ -61,7 +60,6 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
public class InvariantDeviceProfile {
@@ -70,6 +68,9 @@
public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
+ public static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
+ public static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count";
+
private static final String KEY_IDP_GRID_NAME = "idp_grid_name";
private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
@@ -107,6 +108,8 @@
public int iconBitmapSize;
public int fillResIconDpi;
public float iconTextSize;
+ public float allAppsIconSize;
+ public float allAppsIconTextSize;
private SparseArray<TypedValue> mExtraAttrs;
@@ -145,10 +148,13 @@
iconSize = p.iconSize;
iconShapePath = p.iconShapePath;
landscapeIconSize = p.landscapeIconSize;
+ iconBitmapSize = p.iconBitmapSize;
iconTextSize = p.iconTextSize;
numHotseatIcons = p.numHotseatIcons;
numAllAppsColumns = p.numAllAppsColumns;
dbFile = p.dbFile;
+ allAppsIconSize = p.allAppsIconSize;
+ allAppsIconTextSize = p.allAppsIconTextSize;
defaultLayoutId = p.defaultLayoutId;
demoModeLayoutId = p.demoModeLayoutId;
mExtraAttrs = p.mExtraAttrs;
@@ -157,13 +163,16 @@
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
- if (context instanceof LauncherPreviewRenderer.PreviewContext) {
- throw new IllegalArgumentException(
- "PreviewContext is passed into this IDP constructor");
- }
-
String gridName = getCurrentGridName(context);
- initGrid(context, gridName);
+ String newGridName = initGrid(context, gridName);
+ if (!newGridName.equals(gridName)) {
+ Utilities.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName).apply();
+ }
+ Utilities.getPrefs(context).edit()
+ .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, numHotseatIcons)
+ .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, getPointString(numColumns, numRows))
+ .apply();
+
mConfigMonitor = new ConfigMonitor(context,
APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
mOverlayMonitor = new OverlayMonitor(context);
@@ -183,13 +192,31 @@
* This constructor should NOT have any monitors by design.
*/
public InvariantDeviceProfile(Context context, Display display) {
- initGrid(context, null, new Info(display));
+ // Ensure that the main device profile is initialized
+ InvariantDeviceProfile originalProfile = INSTANCE.get(context);
+ String gridName = getCurrentGridName(context);
+
+ // Get the display info based on default display and interpolate it to existing display
+ DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
+ DefaultDisplay.INSTANCE.get(context).getInfo(),
+ getPredefinedDeviceProfiles(context, gridName));
+
+ Info myInfo = new Info(context, display);
+ DisplayOption myDisplayOption = invDistWeightedInterpolate(
+ myInfo, getPredefinedDeviceProfiles(context, gridName));
+
+ DisplayOption result = new DisplayOption(defaultDisplayOption.grid)
+ .add(myDisplayOption);
+ result.iconSize = defaultDisplayOption.iconSize;
+ result.landscapeIconSize = defaultDisplayOption.landscapeIconSize;
+ result.allAppsIconSize = Math.min(
+ defaultDisplayOption.allAppsIconSize, myDisplayOption.allAppsIconSize);
+ initGrid(context, myInfo, result);
}
public static String getCurrentGridName(Context context) {
- return Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
- ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
- : null;
+ return Utilities.isGridOptionsEnabled(context)
+ ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) : null;
}
/**
@@ -204,18 +231,47 @@
}
private String initGrid(Context context, String gridName) {
- return initGrid(context, gridName, DefaultDisplay.INSTANCE.get(context).getInfo());
+ DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
+ ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
+
+ DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions);
+ initGrid(context, displayInfo, displayOption);
+ return displayOption.grid.name;
}
- private String initGrid(Context context, String gridName, DefaultDisplay.Info displayInfo) {
- Point smallestSize = new Point(displayInfo.smallestSize);
- Point largestSize = new Point(displayInfo.largestSize);
+ private void initGrid(
+ Context context, DefaultDisplay.Info displayInfo, DisplayOption displayOption) {
+ GridOption closestProfile = displayOption.grid;
+ numRows = closestProfile.numRows;
+ numColumns = closestProfile.numColumns;
+ numHotseatIcons = closestProfile.numHotseatIcons;
+ dbFile = closestProfile.dbFile;
+ defaultLayoutId = closestProfile.defaultLayoutId;
+ demoModeLayoutId = closestProfile.demoModeLayoutId;
+ numFolderRows = closestProfile.numFolderRows;
+ numFolderColumns = closestProfile.numFolderColumns;
+ numAllAppsColumns = closestProfile.numAllAppsColumns;
- // This guarantees that width < height
- float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y),
- displayInfo.metrics);
- float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y),
- displayInfo.metrics);
+ mExtraAttrs = closestProfile.extraAttrs;
+
+ iconSize = displayOption.iconSize;
+ iconShapePath = getIconShapePath(context);
+ landscapeIconSize = displayOption.landscapeIconSize;
+ iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics);
+ iconTextSize = displayOption.iconTextSize;
+ fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
+
+ if (Utilities.isGridOptionsEnabled(context)) {
+ allAppsIconSize = displayOption.allAppsIconSize;
+ allAppsIconTextSize = displayOption.allAppsIconTextSize;
+ } else {
+ allAppsIconSize = iconSize;
+ allAppsIconTextSize = iconTextSize;
+ }
+
+ // If the partner customization apk contains any grid overrides, apply them
+ // Supported overrides: numRows, numColumns, iconSize
+ applyPartnerDeviceProfileOverrides(context, displayInfo.metrics);
Point realSize = new Point(displayInfo.realSize);
// The real size never changes. smallSide and largeSide will remain the
@@ -223,64 +279,12 @@
int smallSide = Math.min(realSize.x, realSize.y);
int largeSide = Math.max(realSize.x, realSize.y);
- // We want a list of all options as well as the list of filtered options. This allows us
- // to have a consistent UI for areas that the grid size change should not affect
- // ie. All Apps should be consistent between grid sizes.
- ArrayList<DisplayOption> allOptions = new ArrayList<>();
- ArrayList<DisplayOption> filteredOptions = new ArrayList<>();
- getPredefinedDeviceProfiles(context, gridName, filteredOptions, allOptions);
+ DeviceProfile.Builder builder = new DeviceProfile.Builder(context, this, displayInfo)
+ .setSizeRange(new Point(displayInfo.smallestSize),
+ new Point(displayInfo.largestSize));
- if (allOptions.isEmpty() && filteredOptions.isEmpty()) {
- throw new RuntimeException("No display option with canBeDefault=true");
- }
-
- // Sort the profiles based on the closeness to the device size
- Comparator<DisplayOption> comparator = (a, b) -> Float.compare(dist(minWidthDps,
- minHeightDps, a.minWidthDps, a.minHeightDps),
- dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps));
-
- // Calculate the device profiles as if there is no grid override.
- Collections.sort(allOptions, comparator);
- DisplayOption interpolatedDisplayOption =
- invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions);
- initGridOption(context, allOptions, interpolatedDisplayOption, displayInfo.metrics);
-
- // Create IDP with no grid override values.
- InvariantDeviceProfile originalIDP = new InvariantDeviceProfile(this);
- originalIDP.landscapeProfile = new DeviceProfile(context, this, null, smallestSize,
- largestSize, largeSide, smallSide, true /* isLandscape */,
- false /* isMultiWindowMode */);
- originalIDP.portraitProfile = new DeviceProfile(context, this, null, smallestSize,
- largestSize, smallSide, largeSide, false /* isLandscape */,
- false /* isMultiWindowMode */);
-
- if (filteredOptions.isEmpty()) {
- filteredOptions = allOptions;
-
- landscapeProfile = originalIDP.landscapeProfile;
- portraitProfile = originalIDP.portraitProfile;
- } else {
- Collections.sort(filteredOptions, comparator);
- interpolatedDisplayOption =
- invDistWeightedInterpolate(minWidthDps, minHeightDps, filteredOptions);
-
- initGridOption(context, filteredOptions, interpolatedDisplayOption,
- displayInfo.metrics);
- numAllAppsColumns = originalIDP.numAllAppsColumns;
-
- landscapeProfile = new DeviceProfile(context, this, originalIDP, smallestSize,
- largestSize, largeSide, smallSide, true /* isLandscape */,
- false /* isMultiWindowMode */);
- portraitProfile = new DeviceProfile(context, this, originalIDP, smallestSize,
- largestSize, smallSide, largeSide, false /* isLandscape */,
- false /* isMultiWindowMode */);
- }
-
- GridOption closestProfile = filteredOptions.get(0).grid;
- if (!closestProfile.name.equals(gridName)) {
- Utilities.getPrefs(context).edit()
- .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
- }
+ landscapeProfile = builder.setSize(largeSide, smallSide).build();
+ portraitProfile = builder.setSize(smallSide, largeSide).build();
// We need to ensure that there is enough extra space in the wallpaper
// for the intended parallax effects
@@ -294,38 +298,8 @@
ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
-
- return closestProfile.name;
}
- private void initGridOption(Context context, ArrayList<DisplayOption> options,
- DisplayOption displayOption, DisplayMetrics metrics) {
- GridOption closestProfile = options.get(0).grid;
- numRows = closestProfile.numRows;
- numColumns = closestProfile.numColumns;
- numHotseatIcons = closestProfile.numHotseatIcons;
- dbFile = closestProfile.dbFile;
- defaultLayoutId = closestProfile.defaultLayoutId;
- demoModeLayoutId = closestProfile.demoModeLayoutId;
- numFolderRows = closestProfile.numFolderRows;
- numFolderColumns = closestProfile.numFolderColumns;
- numAllAppsColumns = numColumns;
-
- mExtraAttrs = closestProfile.extraAttrs;
-
- iconSize = displayOption.iconSize;
- iconShapePath = getIconShapePath(context);
- landscapeIconSize = displayOption.landscapeIconSize;
- iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics);
- iconTextSize = displayOption.iconTextSize;
- fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
-
- // If the partner customization apk contains any grid overrides, apply them
- // Supported overrides: numRows, numColumns, iconSize
- applyPartnerDeviceProfileOverrides(context, metrics);
- }
-
-
@Nullable
public TypedValue getAttrValue(int attr) {
return mExtraAttrs == null ? null : mExtraAttrs.get(attr);
@@ -368,9 +342,7 @@
InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this);
// Re-init grid
- String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
- ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
- : null;
+ String gridName = getCurrentGridName(context);
initGrid(context, gridName);
int changeFlags = 0;
@@ -403,13 +375,7 @@
}
}
- /**
- * @param gridName The current grid name.
- * @param filteredOptionsOut List filled with all the filtered options based on gridName.
- * @param allOptionsOut List filled with all the options that can be the default option.
- */
- static void getPredefinedDeviceProfiles(Context context, String gridName,
- ArrayList<DisplayOption> filteredOptionsOut, ArrayList<DisplayOption> allOptionsOut) {
+ static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context, String gridName) {
ArrayList<DisplayOption> profiles = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
final int depth = parser.getDepth();
@@ -436,19 +402,26 @@
throw new RuntimeException(e);
}
+ ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
if (!TextUtils.isEmpty(gridName)) {
for (DisplayOption option : profiles) {
if (gridName.equals(option.grid.name)) {
- filteredOptionsOut.add(option);
+ filteredProfiles.add(option);
}
}
}
-
- for (DisplayOption option : profiles) {
- if (option.canBeDefault) {
- allOptionsOut.add(option);
+ if (filteredProfiles.isEmpty()) {
+ // No grid found, use the default options
+ for (DisplayOption option : profiles) {
+ if (option.canBeDefault) {
+ filteredProfiles.add(option);
+ }
}
}
+ if (filteredProfiles.isEmpty()) {
+ throw new RuntimeException("No display option with canBeDefault=true");
+ }
+ return filteredProfiles;
}
private int getLauncherIconDensity(int requiredSize) {
@@ -492,8 +465,43 @@
}
@VisibleForTesting
+ static DisplayOption invDistWeightedInterpolate(
+ DefaultDisplay.Info displayInfo, ArrayList<DisplayOption> points) {
+ Point smallestSize = new Point(displayInfo.smallestSize);
+ Point largestSize = new Point(displayInfo.largestSize);
+
+ // This guarantees that width < height
+ float width = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y),
+ displayInfo.metrics);
+ float height = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y),
+ displayInfo.metrics);
+
+ // Sort the profiles based on the closeness to the device size
+ Collections.sort(points, (a, b) ->
+ Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
+ dist(width, height, b.minWidthDps, b.minHeightDps)));
+
+ GridOption closestOption = points.get(0).grid;
+ float weights = 0;
+
+ DisplayOption p = points.get(0);
+ if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) {
+ return p;
+ }
+
+ DisplayOption out = new DisplayOption(closestOption);
+ for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
+ p = points.get(i);
+ float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
+ weights += w;
+ out.add(new DisplayOption().add(p).multiply(w));
+ }
+ return out.multiply(1.0f / weights);
+ }
+
+ @VisibleForTesting
static DisplayOption invDistWeightedInterpolate(float width, float height,
- ArrayList<DisplayOption> points) {
+ ArrayList<DisplayOption> points) {
float weights = 0;
DisplayOption p = points.get(0);
@@ -573,6 +581,8 @@
private final int numHotseatIcons;
private final String dbFile;
+ private final int numAllAppsColumns;
+
private final int defaultLayoutId;
private final int demoModeLayoutId;
@@ -596,6 +606,8 @@
R.styleable.GridDisplayOption_numFolderRows, numRows);
numFolderColumns = a.getInt(
R.styleable.GridDisplayOption_numFolderColumns, numColumns);
+ numAllAppsColumns = a.getInt(
+ R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
a.recycle();
@@ -607,7 +619,6 @@
private static final class DisplayOption {
private final GridOption grid;
- private final String name;
private final float minWidthDps;
private final float minHeightDps;
private final boolean canBeDefault;
@@ -615,6 +626,8 @@
private float iconSize;
private float iconTextSize;
private float landscapeIconSize;
+ private float allAppsIconSize;
+ private float allAppsIconTextSize;
DisplayOption(GridOption grid, Context context, AttributeSet attrs) {
this.grid = grid;
@@ -622,7 +635,6 @@
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ProfileDisplayOption);
- name = a.getString(R.styleable.ProfileDisplayOption_name);
minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0);
minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
canBeDefault = a.getBoolean(
@@ -633,12 +645,19 @@
iconSize);
iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
+ allAppsIconSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
+ iconSize);
+ allAppsIconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize,
+ iconTextSize);
a.recycle();
}
DisplayOption() {
- grid = null;
- name = null;
+ this(null);
+ }
+
+ DisplayOption(GridOption grid) {
+ this.grid = grid;
minWidthDps = 0;
minHeightDps = 0;
canBeDefault = false;
@@ -647,14 +666,18 @@
private DisplayOption multiply(float w) {
iconSize *= w;
landscapeIconSize *= w;
+ allAppsIconSize *= w;
iconTextSize *= w;
+ allAppsIconTextSize *= w;
return this;
}
private DisplayOption add(DisplayOption p) {
iconSize += p.iconSize;
landscapeIconSize += p.landscapeIconSize;
+ allAppsIconSize += p.allAppsIconSize;
iconTextSize += p.iconTextSize;
+ allAppsIconTextSize += p.allAppsIconTextSize;
return this;
}
}
@@ -672,4 +695,4 @@
onConfigChanged(context);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
deleted file mode 100644
index 6f5b941..0000000
--- a/src/com/android/launcher3/ItemInfo.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
-
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.os.Process;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.util.ContentWriter;
-
-
-
-/**
- * Represents an item in the launcher.
- */
-public class ItemInfo {
-
- public static final boolean DEBUG = true;
- public static final int NO_ID = -1;
-
- /**
- * The id in the settings database for this item
- */
- public int id = NO_ID;
-
- /**
- * One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
- * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
- * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
- * {@link LauncherSettings.Favorites#ITEM_TYPE_FOLDER},
- * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET} or
- * {@link LauncherSettings.Favorites#ITEM_TYPE_CUSTOM_APPWIDGET}.
- */
- public int itemType;
-
- /**
- * The id of the container that holds this item. For the desktop, this will be
- * {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. For the all applications folder it
- * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders
- * it will be the id of the folder.
- */
- public int container = NO_ID;
-
- /**
- * Indicates the screen in which the shortcut appears if the container types is
- * {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. (i.e., ignore if the container type is
- * {@link LauncherSettings.Favorites#CONTAINER_HOTSEAT})
- */
- public int screenId = -1;
-
- /**
- * Indicates the X position of the associated cell.
- */
- public int cellX = -1;
-
- /**
- * Indicates the Y position of the associated cell.
- */
- public int cellY = -1;
-
- /**
- * Indicates the X cell span.
- */
- public int spanX = 1;
-
- /**
- * Indicates the Y cell span.
- */
- public int spanY = 1;
-
- /**
- * Indicates the minimum X cell span.
- */
- public int minSpanX = 1;
-
- /**
- * Indicates the minimum Y cell span.
- */
- public int minSpanY = 1;
-
- /**
- * Indicates the position in an ordered list.
- */
- public int rank = 0;
-
- /**
- * Title of the item
- */
- public CharSequence title;
-
- /**
- * Content description of the item.
- */
- public CharSequence contentDescription;
-
- public UserHandle user;
-
- public ItemInfo() {
- user = Process.myUserHandle();
- }
-
- ItemInfo(ItemInfo info) {
- copyFrom(info);
- }
-
- public void copyFrom(ItemInfo info) {
- id = info.id;
- cellX = info.cellX;
- cellY = info.cellY;
- spanX = info.spanX;
- spanY = info.spanY;
- rank = info.rank;
- screenId = info.screenId;
- itemType = info.itemType;
- container = info.container;
- user = info.user;
- contentDescription = info.contentDescription;
- }
-
- public Intent getIntent() {
- return null;
- }
-
- @Nullable
- public ComponentName getTargetComponent() {
- Intent intent = getIntent();
- if (intent != null) {
- return intent.getComponent();
- } else {
- return null;
- }
- }
-
- public void writeToValues(ContentWriter writer) {
- writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
- .put(LauncherSettings.Favorites.CONTAINER, container)
- .put(LauncherSettings.Favorites.SCREEN, screenId)
- .put(LauncherSettings.Favorites.CELLX, cellX)
- .put(LauncherSettings.Favorites.CELLY, cellY)
- .put(LauncherSettings.Favorites.SPANX, spanX)
- .put(LauncherSettings.Favorites.SPANY, spanY)
- .put(LauncherSettings.Favorites.RANK, rank);
- }
-
- public void readFromValues(ContentValues values) {
- itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
- container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
- screenId = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
- cellX = values.getAsInteger(LauncherSettings.Favorites.CELLX);
- cellY = values.getAsInteger(LauncherSettings.Favorites.CELLY);
- spanX = values.getAsInteger(LauncherSettings.Favorites.SPANX);
- spanY = values.getAsInteger(LauncherSettings.Favorites.SPANY);
- rank = values.getAsInteger(LauncherSettings.Favorites.RANK);
- }
-
- /**
- * Write the fields of this item to the DB
- */
- public void onAddToDatabase(ContentWriter writer) {
- if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) {
- // We should never persist an item on the extra empty screen.
- throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
- }
-
- writeToValues(writer);
- writer.put(LauncherSettings.Favorites.PROFILE_ID, user);
- }
-
- @Override
- public final String toString() {
- return getClass().getSimpleName() + "(" + dumpProperties() + ")";
- }
-
- protected String dumpProperties() {
- return "id=" + id
- + " type=" + LauncherSettings.Favorites.itemTypeToString(itemType)
- + " container=" + LauncherSettings.Favorites.containerToString((int)container)
- + " targetComponent=" + getTargetComponent()
- + " screen=" + screenId
- + " cell(" + cellX + "," + cellY + ")"
- + " span(" + spanX + "," + spanY + ")"
- + " minSpan(" + minSpanX + "," + minSpanY + ")"
- + " rank=" + rank
- + " user=" + user
- + " title=" + title;
- }
-
- /**
- * Whether this item is disabled.
- */
- public boolean isDisabled() {
- return false;
- }
-
- public int getViewId() {
- // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
- // This cast is safe as long as the id < 0x00FFFFFF
- // Since we jail all the dynamically generated views, there should be no clashes
- // with any other views.
- return id;
- }
-
- /**
- * Returns if an Item is a predicted item
- */
- public boolean isPredictedItem() {
- return container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
- || container == LauncherSettings.Favorites.CONTAINER_PREDICTION;
- }
-
- /**
- * Can be overridden by inherited classes to fill in {@link LauncherAtom.ItemInfo}
- */
- public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
- }
-
- /**
- * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
- */
- public LauncherAtom.ItemInfo buildProto(Intent intent, FolderInfo fInfo) {
-
- LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
- itemBuilder.setIsWork(user != Process.myUserHandle());
- ComponentName cn = getTargetComponent();
- if (cn == null) return itemBuilder.build();
- switch (itemType) {
- case ITEM_TYPE_APPLICATION:
- itemBuilder.setApplication(LauncherAtom.Application.newBuilder()
- .setComponentName(cn.flattenToShortString())
- .setPackageName(cn.getPackageName()));
- break;
- case ITEM_TYPE_DEEP_SHORTCUT:
- case ITEM_TYPE_SHORTCUT:
- itemBuilder.setShortcut(LauncherAtom.Shortcut.newBuilder()
- .setShortcutName(cn.flattenToShortString()));
- break;
- case ITEM_TYPE_APPWIDGET:
- setItemBuilder(itemBuilder);
- break;
- default:
- break;
-
- }
- if (fInfo != null) {
- LauncherAtom.FolderContainer.Builder folderBuilder =
- LauncherAtom.FolderContainer.newBuilder();
- folderBuilder.setGridX(cellX).setGridY(cellY).setPageIndex(screenId);
-
- switch (fInfo.container) {
- case CONTAINER_HOTSEAT:
- folderBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
- .setIndex(fInfo.screenId));
- break;
- case CONTAINER_DESKTOP:
- folderBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
- .setPageIndex(fInfo.screenId)
- .setGridX(fInfo.cellX).setGridY(fInfo.cellY));
- break;
- }
- itemBuilder.setFolder(folderBuilder);
- } else {
- switch (container) {
- case CONTAINER_HOTSEAT:
- itemBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
- .setIndex(screenId));
- break;
- case CONTAINER_DESKTOP:
- itemBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
- .setGridX(cellX)
- .setGridY(cellY)
- .setPageIndex(screenId));
- break;
- }
- }
- return itemBuilder.build();
- }
-}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 043ea2f..0970dae 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,21 +18,34 @@
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
+import static com.android.launcher3.InstallShortcutReceiver.FLAG_DRAG_AND_DROP;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
+import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
+import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.NO_OFFSET;
+import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
+import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONSTOP;
+import static com.android.launcher3.logging.StatsLogManager.containerTypeToAtomState;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
+import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import android.animation.Animator;
@@ -55,11 +68,9 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Point;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.Handler;
import android.os.Parcelable;
import android.os.Process;
import android.os.StrictMode;
@@ -67,7 +78,6 @@
import android.text.method.TextKeyListener;
import android.util.Log;
import android.util.SparseArray;
-import android.view.Display;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.KeyboardShortcutInfo;
@@ -80,19 +90,19 @@
import android.view.animation.OvershootInterpolator;
import android.widget.Toast;
+import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
@@ -104,12 +114,19 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
+import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.logging.StatsLogUtils;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.PromiseAppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.pm.UserCache;
@@ -117,6 +134,10 @@
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.qsb.QsbContainerView;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
@@ -133,6 +154,7 @@
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
@@ -179,7 +201,7 @@
/**
* Default launcher application.
*/
-public class Launcher extends BaseDraggingActivity implements LauncherExterns,
+public class Launcher extends StatefulActivity<LauncherState> implements LauncherExterns,
Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin> {
public static final String TAG = "Launcher";
@@ -226,7 +248,7 @@
public static final String ON_RESUME_EVT = "Launcher.onResume";
public static final String ON_NEW_INTENT_EVT = "Launcher.onNewIntent";
- private LauncherStateManager mStateManager;
+ private StateManager<LauncherState> mStateManager;
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
@@ -243,7 +265,6 @@
@Thunk
Workspace mWorkspace;
- private View mLauncherView;
@Thunk
DragLayer mDragLayer;
private DragController mDragController;
@@ -294,6 +315,7 @@
// We only want to get the SharedPreferences once since it does an FS stat each time we get
// it from the context.
private SharedPreferences mSharedPrefs;
+ private OnboardingPrefs mOnboardingPrefs;
// Activity result which needs to be processed after workspace has loaded.
private ActivityResultInfo mPendingActivityResult;
@@ -309,10 +331,6 @@
private RotationHelper mRotationHelper;
- final Handler mHandler = new Handler();
- private final Runnable mHandleDeferredResume = this::handleDeferredResume;
- private boolean mDeferredResumePending;
-
private float mCurrentAssistantVisibility = 0f;
protected LauncherOverlayManager mOverlayManager;
@@ -349,6 +367,7 @@
LauncherAppState app = LauncherAppState.getInstance(this);
mOldConfig = new Configuration(getResources().getConfiguration());
mModel = app.getModel();
+
mRotationHelper = new RotationHelper(this);
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
initDeviceProfile(idp);
@@ -359,15 +378,16 @@
mDragController = new DragController(this);
mAllAppsController = new AllAppsTransitionController(this);
- mStateManager = new LauncherStateManager(this);
+ mStateManager = new StateManager<>(this, NORMAL);
+
+ mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);
mAppWidgetManager = new WidgetManagerHelper(this);
mAppWidgetHost = new LauncherAppWidgetHost(this,
appWidgetId -> getWorkspace().removeWidget(appWidgetId));
mAppWidgetHost.startListening();
- mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
-
+ inflateRootView(R.layout.launcher);
setupViews();
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
@@ -404,7 +424,7 @@
// For handling default keys
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
- setContentView(mLauncherView);
+ setContentView(getRootView());
getRootView().dispatchInsets();
// Listen for broadcasts
@@ -422,10 +442,7 @@
mRotationHelper.initialize();
- mStateManager.addStateListener(new LauncherStateManager.StateListener() {
- @Override
- public void onStateTransitionStart(LauncherState toState) {
- }
+ mStateManager.addStateListener(new StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
@@ -452,6 +469,14 @@
return new LauncherOverlayManager() { };
}
+ protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
+ return new OnboardingPrefs<>(this, sharedPrefs);
+ }
+
+ public OnboardingPrefs getOnboardingPrefs() {
+ return mOnboardingPrefs;
+ }
+
@Override
public void onPluginConnected(OverlayPlugin overlayManager, Context context) {
switchOverlay(() -> overlayManager.createOverlayManager(this, this));
@@ -499,16 +524,6 @@
}
@Override
- public void reapplyUi() {
- reapplyUi(true /* cancelCurrentAnimation */);
- }
-
- public void reapplyUi(boolean cancelCurrentAnimation) {
- getRootView().dispatchInsets();
- getStateManager().reapplyState(cancelCurrentAnimation);
- }
-
- @Override
public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
onIdpChanged(idp);
}
@@ -543,12 +558,8 @@
// Load configuration-specific DeviceProfile
mDeviceProfile = idp.getDeviceProfile(this);
if (isInMultiWindowMode()) {
- // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return
- // the app window size
- Display display = getWindowManager().getDefaultDisplay();
- Point mwSize = new Point();
- display.getSize(mwSize);
- mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
+ mDeviceProfile = mDeviceProfile.getMultiWindowProfile(
+ this, getMultiWindowDisplaySize());
}
onDeviceProfileInitiated();
@@ -563,13 +574,9 @@
return mFocusHandler;
}
- public LauncherStateManager getStateManager() {
- return mStateManager;
- }
-
@Override
- public <T extends View> T findViewById(int id) {
- return mLauncherView.findViewById(id);
+ public StateManager<LauncherState> getStateManager() {
+ return mStateManager;
}
private LauncherCallbacks mLauncherCallbacks;
@@ -870,11 +877,7 @@
@Override
protected void onStop() {
- final boolean wasActive = isUserActive();
- final LauncherState origState = getStateManager().getState();
- final int origDragLayerChildCount = mDragLayer.getChildCount();
super.onStop();
-
if (mDeferOverlayCallbacks) {
checkIfOverlayStillDeferred();
} else {
@@ -882,30 +885,8 @@
}
logStopAndResume(Action.Command.STOP);
-
mAppWidgetHost.setListenIfResumed(false);
-
NotificationListener.removeNotificationsChangedListener();
- getStateManager().moveToRestState();
-
- // Workaround for b/78520668, explicitly trim memory once UI is hidden
- onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
-
- if (wasActive) {
- // The expected condition is that this activity is stopped because the device goes to
- // sleep and the UI may have noticeable changes.
- mDragLayer.post(() -> {
- if ((!getStateManager().isInStableState(origState)
- // The drag layer may be animating (e.g. dismissing QSB).
- || mDragLayer.getAlpha() < 1
- // Maybe an ArrowPopup is closed.
- || mDragLayer.getChildCount() != origDragLayerChildCount)) {
- onUiChangedWhileSleeping();
- }
- });
- }
-
- TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Activity.onStop");
}
@Override
@@ -919,47 +900,57 @@
mAppWidgetHost.setListenIfResumed(true);
TraceHelper.INSTANCE.endSection(traceToken);
- TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Activity.onStart");
}
- private void handleDeferredResume() {
- if (hasBeenResumed() && !mStateManager.getState().disableInteraction) {
- logStopAndResume(Action.Command.RESUME);
- getUserEventDispatcher().startSession();
+ @Override
+ @CallSuper
+ protected void onDeferredResumed() {
+ logStopAndResume(Action.Command.RESUME);
+ getUserEventDispatcher().startSession();
- AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
+ AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
- // Process any items that were added while Launcher was away.
- InstallShortcutReceiver.disableAndFlushInstallQueue(
- InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
+ // Process any items that were added while Launcher was away.
+ InstallShortcutReceiver.disableAndFlushInstallQueue(
+ InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
- // Refresh shortcuts if the permission changed.
- mModel.refreshShortcutsIfRequired();
+ // Refresh shortcuts if the permission changed.
+ mModel.refreshShortcutsIfRequired();
- // Set the notification listener and fetch updated notifications when we resume
- NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+ // Set the notification listener and fetch updated notifications when we resume
+ NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
- DiscoveryBounce.showForHomeIfNeeded(this);
-
- onDeferredResumed();
- addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED);
-
- mDeferredResumePending = false;
- } else {
- mDeferredResumePending = true;
- }
+ DiscoveryBounce.showForHomeIfNeeded(this);
}
- protected void onDeferredResumed() { }
private void logStopAndResume(int command) {
+ int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage();
int containerType = mStateManager.getState().containerType;
+
+ StatsLogManager.EventEnum event;
+ StatsLogManager.StatsLogger logger = getStatsLogManager().logger();
+ if (command == Action.Command.RESUME) {
+ logger.withSrcState(LAUNCHER_STATE_BACKGROUND)
+ .withDstState(containerTypeToAtomState(mStateManager.getState().containerType));
+ event = LAUNCHER_ONRESUME;
+ } else { /* command == Action.Command.STOP */
+ logger.withSrcState(containerTypeToAtomState(mStateManager.getState().containerType))
+ .withDstState(LAUNCHER_STATE_BACKGROUND);
+ event = LAUNCHER_ONSTOP;
+ }
+
if (containerType == ContainerType.WORKSPACE && mWorkspace != null) {
getUserEventDispatcher().logActionCommand(command,
- containerType, -1, mWorkspace.isOverlayShown() ? -1 : 0);
+ containerType, -1, pageIndex);
+ logger.withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setWorkspace(
+ LauncherAtom.WorkspaceContainer.newBuilder()
+ .setPageIndex(pageIndex)).build());
} else {
getUserEventDispatcher().logActionCommand(command, containerType, -1);
}
+ logger.log(event);
}
private void scheduleDeferredCheck() {
@@ -971,7 +962,8 @@
if (!mDeferOverlayCallbacks) {
return;
}
- if (isStarted() && (!hasBeenResumed() || mStateManager.getState().disableInteraction)) {
+ if (isStarted() && (!hasBeenResumed()
+ || mStateManager.getState().hasFlag(FLAG_NON_INTERACTIVE))) {
return;
}
mDeferOverlayCallbacks = false;
@@ -998,21 +990,51 @@
return mOverlayManager;
}
+ @Override
public void onStateSetStart(LauncherState state) {
- if (mDeferredResumePending) {
- handleDeferredResume();
- }
+ super.onStateSetStart(state);
if (mDeferOverlayCallbacks) {
scheduleDeferredCheck();
}
addActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
+
+ if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
+ AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
+ }
+
+ if (state == SPRING_LOADED) {
+ // Prevent any Un/InstallShortcutReceivers from updating the db while we are
+ // not on homescreen
+ InstallShortcutReceiver.enableInstallQueue(FLAG_DRAG_AND_DROP);
+ getRotationHelper().setCurrentStateRequest(REQUEST_LOCK);
+
+ mWorkspace.showPageIndicatorAtCurrentScroll();
+ mWorkspace.setClipChildren(false);
+ }
+ // When multiple pages are visible, show persistent page indicator
+ mWorkspace.getPageIndicator().setShouldAutoHide(!state.hasFlag(FLAG_MULTI_PAGE));
}
+ @Override
public void onStateSetEnd(LauncherState state) {
+ super.onStateSetStart(state);
getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
- getWorkspace().setClipChildren(!state.disablePageClipping);
+ getWorkspace().setClipChildren(!state.hasFlag(FLAG_MULTI_PAGE));
+
finishAutoCancelActionMode();
removeActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
+
+ // dispatch window state changed
+ getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
+ AccessibilityManagerCompat.sendStateEventToTest(this, state.ordinal);
+
+ if (state == NORMAL) {
+ // Re-enable any Un/InstallShortcutReceiver and now process any queued items
+ InstallShortcutReceiver.disableAndFlushInstallQueue(FLAG_DRAG_AND_DROP, this);
+
+ // Clear any rotation locks when going to normal state
+ getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
+ }
}
@Override
@@ -1021,9 +1043,6 @@
TraceHelper.FLAG_UI_EVENT);
super.onResume();
- mHandler.removeCallbacks(mHandleDeferredResume);
- Utilities.postAsyncCallback(mHandler, mHandleDeferredResume);
-
if (!mOnResumeCallbacks.isEmpty()) {
final ArrayList<OnResumeCallback> resumeCallbacks = new ArrayList<>(mOnResumeCallbacks);
mOnResumeCallbacks.clear();
@@ -1066,10 +1085,6 @@
}
}
- public boolean isInState(LauncherState state) {
- return mStateManager.getState() == state;
- }
-
/**
* Restores the previous state, if it exists.
*
@@ -1083,7 +1098,7 @@
int stateOrdinal = savedState.getInt(RUNTIME_STATE, NORMAL.ordinal);
LauncherState[] stateValues = LauncherState.values();
LauncherState state = stateValues[stateOrdinal];
- if (!state.disableRestore) {
+ if (!state.shouldDisableRestore()) {
mStateManager.goToState(state, false /* animated */);
}
@@ -1113,10 +1128,7 @@
mWorkspace.initParentViews(mDragLayer);
mOverviewPanel = findViewById(R.id.overview_panel);
mHotseat = findViewById(R.id.hotseat);
-
- mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ mHotseat.setWorkspace(mWorkspace);
// Setup the drag layer
mDragLayer.setup(mDragController, mWorkspace);
@@ -1308,8 +1320,6 @@
}
};
- protected void onUiChangedWhileSleeping() { }
-
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
mWorkspace.updateNotificationDots(updatedDots);
mAppsView.getAppsStore().updateNotificationDots(updatedDots);
@@ -1333,11 +1343,6 @@
}
@Override
- public LauncherRootView getRootView() {
- return (LauncherRootView) mLauncherView;
- }
-
- @Override
public DragLayer getDragLayer() {
return mDragLayer;
}
@@ -1443,6 +1448,8 @@
mLauncherCallbacks.onHomeIntent(internalStateHandled);
}
mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
+ } else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
+ getStateManager().goToState(ALL_APPS, alreadyOnHome);
}
TraceHelper.INSTANCE.endSection(traceToken);
@@ -1526,6 +1533,7 @@
mOverlayManager.onActivityDestroyed(this);
mAppTransitionManager.unregisterRemoteAnimations();
mUserChangedCallbackCloseable.close();
+ mAllAppsController.onActivityDestroyed();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1653,6 +1661,7 @@
private void processShortcutFromDrop(PendingAddShortcutInfo info) {
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName);
setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info));
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: processShortcutFromDrop");
if (!info.activityInfo.startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) {
handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null);
}
@@ -1851,16 +1860,6 @@
}
@Override
- public int getCurrentState() {
- if (mStateManager.getState() == LauncherState.ALL_APPS) {
- return StatsLogUtils.LAUNCHER_STATE_ALLAPPS;
- } else if (mStateManager.getState() == OVERVIEW) {
- return StatsLogUtils.LAUNCHER_STATE_OVERVIEW;
- }
- return StatsLogUtils.LAUNCHER_STATE_HOME;
- }
-
- @Override
public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
@Nullable String sourceContainer) {
if (!hasBeenResumed()) {
@@ -2175,6 +2174,9 @@
workspace.requestLayout();
}
+ @Override
+ public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
+
/**
* Add the views for a widget to the workspace.
*/
@@ -2446,8 +2448,9 @@
*
* Implementation of the method from LauncherModel.Callbacks.
*/
- public void bindAllApplications(AppInfo[] apps) {
- mAppsView.getAppsStore().setApps(apps);
+ @Override
+ public void bindAllApplications(AppInfo[] apps, int flags) {
+ mAppsView.getAppsStore().setApps(apps, flags);
}
/**
@@ -2557,9 +2560,10 @@
writer.println(prefix + "\tmRotationHelper: " + mRotationHelper);
writer.println(prefix + "\tmAppWidgetHost.isListening: " + mAppWidgetHost.isListening());
- // Extra logging for b/116853349
+ // Extra logging for general debugging
mDragLayer.dump(prefix, writer);
mStateManager.dump(prefix, writer);
+ mPopupDataProvider.dump(prefix, writer);
try {
FileLog.flushAll(writer);
@@ -2666,7 +2670,7 @@
return super.onKeyUp(keyCode, event);
}
- protected StateHandler[] createStateHandlers() {
+ protected StateHandler<LauncherState>[] createStateHandlers() {
return new StateHandler[] { getAllAppsController(), getWorkspace() };
}
@@ -2674,10 +2678,6 @@
return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
}
- protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
- return new ScaleAndTranslation(1.1f, 0f, 0f);
- }
-
public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { }
public void onDragLayerHierarchyChanged() { }
@@ -2700,6 +2700,14 @@
return Stream.of(APP_INFO, WIDGETS, INSTALL);
}
+
+ /**
+ * @see LauncherState#getOverviewScaleAndOffset(Launcher)
+ */
+ public float[] getNormalOverviewScaleAndOffset() {
+ return new float[] {NO_SCALE, NO_OFFSET};
+ }
+
public static Launcher getLauncher(Context context) {
return fromContext(context);
}
@@ -2711,6 +2719,7 @@
return (T) activityContext;
}
+
/**
* Callback for listening for onResume
*/
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 2f38037..53e5274 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -33,6 +33,7 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.model.PredictionModel;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.InstallSessionTracker;
@@ -57,6 +58,7 @@
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
+ private final PredictionModel mPredictionModel;
private SecureSettingsObserver mNotificationDotsObserver;
private InstallSessionTracker mInstallSessionTracker;
@@ -127,6 +129,7 @@
mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
+ mPredictionModel = PredictionModel.newInstance(mContext);
}
protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
@@ -182,6 +185,10 @@
return mModel;
}
+ public PredictionModel getPredictionModel() {
+ return mPredictionModel;
+ }
+
public WidgetPreviewLoader getWidgetCache() {
return mWidgetCache;
}
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
index 9148c2f..24e0d14 100644
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -13,11 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.launcher3;
-
-import android.animation.Animator;
import android.app.ActivityOptions;
import android.content.Context;
import android.graphics.Rect;
@@ -58,17 +55,6 @@
}
/**
- * Number of animations which run on state properties.
- */
- public int getStateElementAnimationsCount() {
- return 0;
- }
-
- public Animator createStateElementAnimation(int index, float... values) {
- throw new RuntimeException("Unknown gesture animation " + index);
- }
-
- /**
* Registers remote animations for certain system transitions.
*/
public void registerRemoteAnimations() {
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 9921f76..7ea6851 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -30,6 +30,8 @@
import android.widget.Toast;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.widget.DeferredAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.PendingAppWidgetHostView;
@@ -298,6 +300,7 @@
}
try {
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startConfigActivity");
startAppWidgetConfigureActivityForResult(activity, widgetId, 0, requestCode, null);
} catch (ActivityNotFoundException | SecurityException e) {
Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 56cce78..618b5de 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -13,6 +13,7 @@
import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
/**
* This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index e61b7a8..f434c91 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -51,6 +51,9 @@
import com.android.launcher3.model.PackageUpdatedTask;
import com.android.launcher3.model.ShortcutsChangedTask;
import com.android.launcher3.model.UserLockStateChangedTask;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionTracker;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
@@ -114,7 +117,7 @@
@Override
public void run() {
if (mModelLoaded && hasShortcutsPermission(mApp.getContext())
- != mBgDataModel.hasShortcutHostPermission) {
+ != mBgAllAppsList.hasShortcutHostPermission()) {
forceReload();
}
}
@@ -235,7 +238,8 @@
// we need to run the state change task again.
if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
- enqueueModelUpdateTask(new UserLockStateChangedTask(user));
+ enqueueModelUpdateTask(new UserLockStateChangedTask(
+ user, Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)));
}
}
} else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 8d20bd6..e8b5568 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -20,7 +20,6 @@
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.annotation.TargetApi;
import android.app.backup.BackupManager;
@@ -48,7 +47,6 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -85,6 +83,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class LauncherProvider extends ContentProvider {
@@ -92,8 +91,7 @@
private static final boolean LOGD = false;
private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json";
- private static final String TOKEN_RESTORE_BACKUP_TABLE = "restore_backup_table";
- private static final long RESTORE_BACKUP_TABLE_DELAY = 60000;
+ private static final long RESTORE_BACKUP_TABLE_DELAY = TimeUnit.SECONDS.toMillis(30);
/**
* Represents the schema of the database. Changes in scheme need not be backwards compatible.
@@ -107,6 +105,8 @@
protected DatabaseHelper mOpenHelper;
+ private long mLastRestoreTimestamp = 0L;
+
/**
* $ adb shell dumpsys activity provider com.android.launcher3
*/
@@ -146,7 +146,8 @@
*/
protected synchronized void createDbIfNotExists() {
if (mOpenHelper == null) {
- mOpenHelper = DatabaseHelper.createDatabaseHelper(getContext());
+ mOpenHelper = DatabaseHelper.createDatabaseHelper(
+ getContext(), false /* forMigration */);
if (RestoreDbTask.isPending(getContext())) {
if (!RestoreDbTask.performRestore(getContext(), mOpenHelper,
@@ -406,20 +407,22 @@
return result;
}
case LauncherSettings.Settings.METHOD_REFRESH_BACKUP_TABLE: {
- // TODO(pinyaoting): Update the behavior here.
- if (!MULTI_DB_GRID_MIRATION_ALGO.get()) {
- mOpenHelper.mBackupTableExists =
- tableExists(mOpenHelper.getReadableDatabase(),
- Favorites.BACKUP_TABLE_NAME);
- }
+ mOpenHelper.mBackupTableExists = tableExists(mOpenHelper.getReadableDatabase(),
+ Favorites.BACKUP_TABLE_NAME);
+ return null;
+ }
+ case LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE: {
+ mOpenHelper.mHotseatRestoreTableExists = tableExists(
+ mOpenHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
return null;
}
case LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE: {
- final Handler handler = MODEL_EXECUTOR.getHandler();
- handler.removeCallbacksAndMessages(TOKEN_RESTORE_BACKUP_TABLE);
- handler.postDelayed(() -> RestoreDbTask.restoreIfPossible(
- getContext(), mOpenHelper, new BackupManager(getContext())),
- TOKEN_RESTORE_BACKUP_TABLE, RESTORE_BACKUP_TABLE_DELAY);
+ final long ts = System.currentTimeMillis();
+ if (ts - mLastRestoreTimestamp > RESTORE_BACKUP_TABLE_DELAY) {
+ mLastRestoreTimestamp = ts;
+ RestoreDbTask.restoreIfPossible(
+ getContext(), mOpenHelper, new BackupManager(getContext()));
+ }
return null;
}
case LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER: {
@@ -430,7 +433,8 @@
InvariantDeviceProfile.INSTANCE.get(getContext()).dbFile,
Favorites.TMP_TABLE,
() -> mOpenHelper,
- () -> DatabaseHelper.createDatabaseHelper(getContext())));
+ () -> DatabaseHelper.createDatabaseHelper(
+ getContext(), true /* forMigration */)));
return result;
}
}
@@ -441,7 +445,8 @@
prepForMigration(
arg /* dbFile */,
Favorites.PREVIEW_TABLE_NAME,
- () -> DatabaseHelper.createDatabaseHelper(getContext(), arg),
+ () -> DatabaseHelper.createDatabaseHelper(
+ getContext(), arg, true /* forMigration */),
() -> mOpenHelper));
return result;
}
@@ -451,11 +456,7 @@
}
private void onAddOrDeleteOp(SQLiteDatabase db) {
- if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
- // TODO(pingyaoting): Implement the behavior here.
- } else {
- mOpenHelper.onAddOrDeleteOp(db);
- }
+ mOpenHelper.onAddOrDeleteOp(db);
}
/**
@@ -609,20 +610,23 @@
public static class DatabaseHelper extends NoLocaleSQLiteHelper implements
LayoutParserCallback {
private final Context mContext;
+ private final boolean mForMigration;
private int mMaxItemId = -1;
private int mMaxScreenId = -1;
private boolean mBackupTableExists;
+ private boolean mHotseatRestoreTableExists;
- static DatabaseHelper createDatabaseHelper(Context context) {
- return createDatabaseHelper(context, null);
+ static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
+ return createDatabaseHelper(context, null, forMigration);
}
- static DatabaseHelper createDatabaseHelper(Context context, String dbName) {
+ static DatabaseHelper createDatabaseHelper(Context context, String dbName,
+ boolean forMigration) {
if (dbName == null) {
dbName = MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
context).dbFile : LauncherFiles.LAUNCHER_DB;
}
- DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName);
+ DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
// Table creation sometimes fails silently, which leads to a crash loop.
// This way, we will try to create a table every time after crash, so the device
// would eventually be able to recover.
@@ -635,6 +639,8 @@
databaseHelper.mBackupTableExists = tableExists(
databaseHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
}
+ databaseHelper.mHotseatRestoreTableExists = tableExists(
+ databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
databaseHelper.initIds();
return databaseHelper;
@@ -643,9 +649,10 @@
/**
* Constructor used in tests and for restore.
*/
- public DatabaseHelper(Context context, String dbName) {
+ public DatabaseHelper(Context context, String dbName, boolean forMigration) {
super(context, dbName, SCHEMA_VERSION);
mContext = context;
+ mForMigration = forMigration;
}
protected void initIds() {
@@ -670,14 +677,20 @@
// Fresh and clean launcher DB.
mMaxItemId = initializeMaxItemId(db);
- onEmptyDbCreated();
+ if (!mForMigration) {
+ onEmptyDbCreated();
+ }
}
protected void onAddOrDeleteOp(SQLiteDatabase db) {
- if (!MULTI_DB_GRID_MIRATION_ALGO.get() && mBackupTableExists) {
+ if (mBackupTableExists) {
dropTable(db, Favorites.BACKUP_TABLE_NAME);
mBackupTableExists = false;
}
+ if (mHotseatRestoreTableExists) {
+ dropTable(db, Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
+ mHotseatRestoreTableExists = false;
+ }
}
/**
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index b4fbbc3..51504ce 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -1,21 +1,17 @@
package com.android.launcher3;
-import static com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV;
-import static com.android.launcher3.util.SystemUiController.UI_STATE_ROOT_VIEW;
+import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
import android.annotation.TargetApi;
-import android.app.ActivityManager;
import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
-import android.view.View;
import android.view.ViewDebug;
import android.view.WindowInsets;
+import com.android.launcher3.statemanager.StatefulActivity;
+
import java.util.Collections;
import java.util.List;
@@ -23,18 +19,12 @@
private final Rect mTempRect = new Rect();
- private final Launcher mLauncher;
-
- private final Paint mOpaquePaint;
-
- @ViewDebug.ExportedProperty(category = "launcher")
- private final Rect mConsumedInsets = new Rect();
+ private final StatefulActivity mActivity;
@ViewDebug.ExportedProperty(category = "launcher")
private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
Collections.singletonList(new Rect());
- private View mAlignedView;
private WindowStateListener mWindowStateListener;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mDisallowBackGesture;
@@ -43,64 +33,17 @@
public LauncherRootView(Context context, AttributeSet attrs) {
super(context, attrs);
-
- mOpaquePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mOpaquePaint.setColor(Color.BLACK);
- mOpaquePaint.setStyle(Paint.Style.FILL);
-
- mLauncher = Launcher.getLauncher(context);
- }
-
- @Override
- protected void onFinishInflate() {
- if (getChildCount() > 0) {
- // LauncherRootView contains only one child, which should be aligned
- // based on the horizontal insets.
- mAlignedView = getChildAt(0);
- }
- super.onFinishInflate();
+ mActivity = StatefulActivity.fromContext(context);
}
private void handleSystemWindowInsets(Rect insets) {
- mConsumedInsets.setEmpty();
- boolean drawInsetBar = false;
- if (mLauncher.isInMultiWindowMode()
- && (insets.left > 0 || insets.right > 0 || insets.bottom > 0)) {
- mConsumedInsets.left = insets.left;
- mConsumedInsets.right = insets.right;
- mConsumedInsets.bottom = insets.bottom;
- insets.set(0, insets.top, 0, 0);
- drawInsetBar = true;
- } else if ((insets.right > 0 || insets.left > 0) &&
- getContext().getSystemService(ActivityManager.class).isLowRamDevice()) {
- mConsumedInsets.left = insets.left;
- mConsumedInsets.right = insets.right;
- insets.set(0, insets.top, 0, insets.bottom);
- drawInsetBar = true;
- }
-
- mLauncher.getSystemUiController().updateUiState(
- UI_STATE_ROOT_VIEW, drawInsetBar ? FLAG_DARK_NAV : 0);
-
// Update device profile before notifying th children.
- mLauncher.getDeviceProfile().updateInsets(insets);
+ mActivity.getDeviceProfile().updateInsets(insets);
boolean resetState = !insets.equals(mInsets);
setInsets(insets);
- if (mAlignedView != null) {
- // Apply margins on aligned view to handle consumed insets.
- MarginLayoutParams lp = (MarginLayoutParams) mAlignedView.getLayoutParams();
- if (lp.leftMargin != mConsumedInsets.left || lp.rightMargin != mConsumedInsets.right ||
- lp.bottomMargin != mConsumedInsets.bottom) {
- lp.leftMargin = mConsumedInsets.left;
- lp.rightMargin = mConsumedInsets.right;
- lp.topMargin = mConsumedInsets.top;
- lp.bottomMargin = mConsumedInsets.bottom;
- mAlignedView.setLayoutParams(lp);
- }
- }
if (resetState) {
- mLauncher.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
+ mActivity.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
}
}
@@ -109,12 +52,7 @@
mTempRect.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
handleSystemWindowInsets(mTempRect);
- if (Utilities.ATLEAST_Q) {
- return insets.inset(mConsumedInsets.left, mConsumedInsets.top,
- mConsumedInsets.right, mConsumedInsets.bottom);
- } else {
- return insets.replaceSystemWindowInsets(mTempRect);
- }
+ return insets;
}
@Override
@@ -127,28 +65,10 @@
}
public void dispatchInsets() {
- mLauncher.getDeviceProfile().updateInsets(mInsets);
+ mActivity.getDeviceProfile().updateInsets(mInsets);
super.setInsets(mInsets);
}
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
-
- // If the right inset is opaque, draw a black rectangle to ensure that is stays opaque.
- if (mConsumedInsets.right > 0) {
- int width = getWidth();
- canvas.drawRect(width - mConsumedInsets.right, 0, width, getHeight(), mOpaquePaint);
- }
- if (mConsumedInsets.left > 0) {
- canvas.drawRect(0, 0, mConsumedInsets.left, getHeight(), mOpaquePaint);
- }
- if (mConsumedInsets.bottom > 0) {
- int height = getHeight();
- canvas.drawRect(0, height - mConsumedInsets.bottom, getWidth(), height, mOpaquePaint);
- }
- }
-
public void setWindowStateListener(WindowStateListener listener) {
mWindowStateListener = listener;
}
@@ -184,7 +104,7 @@
@TargetApi(Build.VERSION_CODES.Q)
public void setDisallowBackGesture(boolean disallowBackGesture) {
- if (!Utilities.ATLEAST_Q) {
+ if (!Utilities.ATLEAST_Q || SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
mDisallowBackGesture = disallowBackGesture;
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 5262b18..5512654 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -22,6 +22,8 @@
import android.os.Bundle;
import android.provider.BaseColumns;
+import com.android.launcher3.model.data.ItemInfo;
+
/**
* Settings related utilities.
*/
@@ -98,6 +100,11 @@
public static final String BACKUP_TABLE_NAME = "favorites_bakup";
/**
+ * Backup table created when user hotseat is moved to workspace for hybrid hotseat
+ */
+ public static final String HYBRID_HOTSEAT_BACKUP_TABLE = "hotseat_restore_backup";
+
+ /**
* Temporary table used specifically for grid migrations during wallpaper preview
*/
public static final String PREVIEW_TABLE_NAME = "favorites_preview";
@@ -150,17 +157,28 @@
public static final int CONTAINER_HOTSEAT = -101;
public static final int CONTAINER_PREDICTION = -102;
public static final int CONTAINER_HOTSEAT_PREDICTION = -103;
+ public static final int CONTAINER_ALL_APPS = -104;
+ public static final int CONTAINER_WIDGETS_TRAY = -105;
+ // Represents search results view.
+ public static final int CONTAINER_SEARCH_RESULTS = -106;
+ public static final int CONTAINER_SHORTCUTS = -107;
+ public static final int CONTAINER_SETTINGS = -108;
+ public static final int CONTAINER_TASKSWITCHER = -109;
- static final String containerToString(int container) {
+ public static final String containerToString(int container) {
switch (container) {
case CONTAINER_DESKTOP: return "desktop";
case CONTAINER_HOTSEAT: return "hotseat";
case CONTAINER_PREDICTION: return "prediction";
+ case CONTAINER_ALL_APPS: return "all_apps";
+ case CONTAINER_WIDGETS_TRAY: return "widgets_tray";
+ case CONTAINER_SEARCH_RESULTS: return "search_result";
+ case CONTAINER_SHORTCUTS: return "shortcuts";
default: return String.valueOf(container);
}
}
- static final String itemTypeToString(int type) {
+ public static final String itemTypeToString(int type) {
switch(type) {
case ITEM_TYPE_APPLICATION: return "APP";
case ITEM_TYPE_SHORTCUT: return "SHORTCUT";
@@ -233,6 +251,12 @@
public static final int ITEM_TYPE_DEEP_SHORTCUT = 6;
/**
+ * Type of the item is recents task.
+ * TODO(hyunyoungs): move constants not related to Favorites DB to a better location.
+ */
+ public static final int ITEM_TYPE_TASK = 7;
+
+ /**
* The appWidgetId of the widget
*
* <P>Type: INTEGER</P>
@@ -322,6 +346,8 @@
public static final String METHOD_REFRESH_BACKUP_TABLE = "refresh_backup_table";
+ public static final String METHOD_REFRESH_HOTSEAT_RESTORE_TABLE = "restore_hotseat_table";
+
public static final String METHOD_RESTORE_BACKUP_TABLE = "restore_backup_table";
public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 504666a..c78df62 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -15,52 +15,35 @@
*/
package com.android.launcher3;
-import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
-import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
-import static android.view.View.VISIBLE;
-import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
-import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
-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.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_PEEK_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
import android.content.Context;
-import android.view.View;
import android.view.animation.Interpolator;
-import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.HintState;
import com.android.launcher3.states.SpringLoadedState;
-import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.uioverrides.states.AllAppsState;
import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import java.util.Arrays;
-
/**
* Base state for various states used for the Launcher
*/
-public abstract class LauncherState {
-
+public abstract class LauncherState implements BaseState<LauncherState> {
/**
* Set of elements indicating various workspace elements which change visibility across states
@@ -79,16 +62,27 @@
public static final int APPS_VIEW_ITEM_MASK =
HOTSEAT_SEARCH_BOX | ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
- protected static final int FLAG_MULTI_PAGE = 1 << 0;
- protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 1;
- protected static final int FLAG_DISABLE_RESTORE = 1 << 2;
- protected static final int FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED = 1 << 3;
- protected static final int FLAG_DISABLE_PAGE_CLIPPING = 1 << 4;
- protected static final int FLAG_PAGE_BACKGROUNDS = 1 << 5;
- protected static final int FLAG_DISABLE_INTERACTION = 1 << 6;
- protected static final int FLAG_OVERVIEW_UI = 1 << 7;
- protected static final int FLAG_HIDE_BACK_BUTTON = 1 << 8;
- protected static final int FLAG_HAS_SYS_UI_SCRIM = 1 << 9;
+ // Flag indicating workspace has multiple pages visible.
+ public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
+ // Flag indicating that workspace and its contents are not accessible
+ public static final int FLAG_WORKSPACE_INACCESSIBLE = BaseState.getFlag(1);
+
+ // Flag indicating the state allows workspace icons to be dragged.
+ public static final int FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED = BaseState.getFlag(2);
+ // Flag to indicate that workspace should draw page background
+ public static final int FLAG_WORKSPACE_HAS_BACKGROUNDS = BaseState.getFlag(3);
+ // True if the back button should be hidden when in this state (assuming no floating views are
+ // open, launcher has window focus, etc).
+ public static final int FLAG_HIDE_BACK_BUTTON = BaseState.getFlag(4);
+ // Flag to indicate if the state would have scrim over sysui region: statu sbar and nav bar
+ public static final int FLAG_HAS_SYS_UI_SCRIM = BaseState.getFlag(5);
+ // Flag to inticate that all popups should be closed when this state is enabled.
+ public static final int FLAG_CLOSE_POPUPS = BaseState.getFlag(6);
+ public static final int FLAG_OVERVIEW_UI = BaseState.getFlag(7);
+
+
+ public static final float NO_OFFSET = 0;
+ public static final float NO_SCALE = 1;
protected static final PageAlphaProvider DEFAULT_ALPHA_PROVIDER =
new PageAlphaProvider(ACCEL_2) {
@@ -98,7 +92,7 @@
}
};
- private static final LauncherState[] sAllStates = new LauncherState[8];
+ private static final LauncherState[] sAllStates = new LauncherState[9];
/**
* TODO: Create a separate class for NORMAL state.
@@ -108,7 +102,7 @@
FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HIDE_BACK_BUTTON |
FLAG_HAS_SYS_UI_SCRIM) {
@Override
- public int getTransitionDuration(Launcher launcher) {
+ public int getTransitionDuration(Context context) {
// Arbitrary duration, when going to NORMAL we use the state we're coming from instead.
return 0;
}
@@ -125,6 +119,8 @@
public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL);
public static final LauncherState OVERVIEW_PEEK =
OverviewState.newPeekState(OVERVIEW_PEEK_STATE_ORDINAL);
+ public static final LauncherState OVERVIEW_MODAL_TASK = OverviewState.newModalTaskState(
+ OVERVIEW_MODAL_TASK_STATE_ORDINAL);
public static final LauncherState QUICK_SWITCH =
OverviewState.newSwitchState(QUICK_SWITCH_STATE_ORDINAL);
public static final LauncherState BACKGROUND_APP =
@@ -138,89 +134,34 @@
public final int containerType;
/**
- * True if the state can be persisted across activity restarts.
- */
- public final boolean disableRestore;
-
- /**
- * True if workspace has multiple pages visible.
- */
- public final boolean hasMultipleVisiblePages;
-
- /**
- * Accessibility flag for workspace and its pages.
- * @see android.view.View#setImportantForAccessibility(int)
- */
- public final int workspaceAccessibilityFlag;
-
- /**
- * Properties related to state transition animation
- *
- * @see WorkspaceStateTransitionAnimation
- */
- public final boolean hasWorkspacePageBackground;
-
- /**
- * True if the state allows workspace icons to be dragged.
- */
- public final boolean workspaceIconsCanBeDragged;
-
- /**
- * True if the workspace pages should not be clipped relative to the workspace bounds
- * for this state.
- */
- public final boolean disablePageClipping;
-
- /**
- * True if launcher can not be directly interacted in this state;
- */
- public final boolean disableInteraction;
-
- /**
* True if the state has overview panel visible.
*/
public final boolean overviewUi;
- /**
- * True if the back button should be hidden when in this state (assuming no floating views are
- * open, launcher has window focus, etc).
- */
- public final boolean hideBackButton;
-
- public final boolean hasSysUiScrim;
+ private final int mFlags;
public LauncherState(int id, int containerType, int flags) {
this.containerType = containerType;
-
- this.hasWorkspacePageBackground = (flags & FLAG_PAGE_BACKGROUNDS) != 0;
- this.hasMultipleVisiblePages = (flags & FLAG_MULTI_PAGE) != 0;
- this.workspaceAccessibilityFlag = (flags & FLAG_DISABLE_ACCESSIBILITY) != 0
- ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
- this.disableRestore = (flags & FLAG_DISABLE_RESTORE) != 0;
- this.workspaceIconsCanBeDragged = (flags & FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED) != 0;
- this.disablePageClipping = (flags & FLAG_DISABLE_PAGE_CLIPPING) != 0;
- this.disableInteraction = (flags & FLAG_DISABLE_INTERACTION) != 0;
+ this.mFlags = flags;
this.overviewUi = (flags & FLAG_OVERVIEW_UI) != 0;
- this.hideBackButton = (flags & FLAG_HIDE_BACK_BUTTON) != 0;
- this.hasSysUiScrim = (flags & FLAG_HAS_SYS_UI_SCRIM) != 0;
-
this.ordinal = id;
sAllStates[id] = this;
}
+ /**
+ * Returns if the state has the provided flag
+ */
+ @Override
+ public final boolean hasFlag(int mask) {
+ return (mFlags & mask) != 0;
+ }
+
public static LauncherState[] values() {
return Arrays.copyOf(sAllStates, sAllStates.length);
}
- /**
- * @return How long the animation to this state should take (or from this state to NORMAL).
- * @param launcher
- */
- public abstract int getTransitionDuration(Launcher launcher);
-
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(1, 0, 0);
+ return new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
}
public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
@@ -228,24 +169,24 @@
return getWorkspaceScaleAndTranslation(launcher);
}
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- return launcher.getOverviewScaleAndTranslationForNormalState();
+ /**
+ * Returns an array of two elements.
+ * The first specifies the scale for the overview
+ * The second is the factor ([0, 1], 0 => center-screen; 1 => offscreen) by which overview
+ * should be shifted horizontally.
+ */
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return launcher.getNormalOverviewScaleAndOffset();
}
public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(1, 0, 0);
+ return new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
}
public float getOverviewFullscreenProgress() {
return 0;
}
- public void onStateEnabled(Launcher launcher) {
- dispatchWindowStateChanged(launcher);
- }
-
- public void onStateDisabled(Launcher launcher) { }
-
public int getVisibleElements(Launcher launcher) {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return HOTSEAT_ICONS | VERTICAL_SWIPE_INDICATOR;
@@ -271,12 +212,36 @@
}
/**
+ * For this state, how modal should over view been shown. 0 modalness means all tasks drawn,
+ * 1 modalness means the current task is show on its own.
+ */
+ public float getOverviewModalness() {
+ return 0;
+ }
+
+ /**
* The amount of blur and wallpaper zoom to apply to the background of either the app
* or Launcher surface in this state. Should be a number between 0 and 1, inclusive.
*
* 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs.
*/
- public float getDepth(Context context) {
+ public final float getDepth(Context context) {
+ return getDepth(context,
+ BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode);
+ }
+
+ /**
+ * Returns the amount of blur and wallpaper zoom for this state with {@param isMultiWindowMode}.
+ * @see #getDepth(Context).
+ */
+ public final float getDepth(Context context, boolean isMultiWindowMode) {
+ if (isMultiWindowMode) {
+ return 0;
+ }
+ return getDepthUnchecked(context);
+ }
+
+ protected float getDepthUnchecked(Context context) {
return 0f;
}
@@ -297,82 +262,25 @@
};
}
+ @Override
public LauncherState getHistoryForState(LauncherState previousState) {
// No history is supported
return NORMAL;
}
- /**
- * Called when the start transition ends and the user settles on this particular state.
- */
- public void onStateTransitionEnd(Launcher launcher) {
- if (this == NORMAL) {
- // Clear any rotation locks when going to normal state
- launcher.getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
- }
+ @Override
+ public String toString() {
+ return TestProtocol.stateOrdinalToString(ordinal);
}
public void onBackPressed(Launcher launcher) {
if (this != NORMAL) {
- LauncherStateManager lsm = launcher.getStateManager();
+ StateManager<LauncherState> lsm = launcher.getStateManager();
LauncherState lastState = lsm.getLastState();
lsm.goToState(lastState);
}
}
- /**
- * Prepares for a non-user controlled animation from fromState to this state. Preparations
- * include:
- * - Setting interpolators for various animations included in the state transition.
- * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
- */
- public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
- StateAnimationConfig config) {
- if (this == NORMAL && fromState == OVERVIEW) {
- config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
- config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
- config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
- config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
- config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
- Workspace workspace = launcher.getWorkspace();
-
- // Start from a higher workspace scale, but only if we're invisible so we don't jump.
- boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
- if (isWorkspaceVisible) {
- CellLayout currentChild = (CellLayout) workspace.getChildAt(
- workspace.getCurrentPage());
- isWorkspaceVisible = currentChild.getVisibility() == VISIBLE
- && currentChild.getShortcutsAndWidgets().getAlpha() > 0;
- }
- if (!isWorkspaceVisible) {
- workspace.setScaleX(0.92f);
- workspace.setScaleY(0.92f);
- }
- Hotseat hotseat = launcher.getHotseat();
- boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
- if (!isHotseatVisible) {
- hotseat.setScaleX(0.92f);
- hotseat.setScaleY(0.92f);
- if (ENABLE_OVERVIEW_ACTIONS.get()) {
- AllAppsContainerView qsbContainer = launcher.getAppsView();
- View qsb = qsbContainer.getSearchView();
- boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
- if (!qsbVisible) {
- qsbContainer.setScaleX(0.92f);
- qsbContainer.setScaleY(0.92f);
- }
- }
- }
- } else if (this == NORMAL && fromState == OVERVIEW_PEEK) {
- // Keep fully visible until the very end (when overview is offscreen) to make invisible.
- config.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1);
- }
- }
-
- protected static void dispatchWindowStateChanged(Launcher launcher) {
- launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
- }
-
public static abstract class PageAlphaProvider {
public final Interpolator interpolator;
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 5e47e2f..e29faac 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -38,7 +38,6 @@
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.Surface;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
@@ -55,12 +54,10 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.PagedViewOrientedState;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
import com.android.launcher3.util.OverScroller;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
@@ -74,7 +71,9 @@
public abstract class PagedView<T extends View & PageIndicator> extends ViewGroup {
private static final String TAG = "PagedView";
private static final boolean DEBUG = false;
+ public static final boolean DEBUG_FAILED_QUICKSWITCH = false;
+ public static final int ACTION_MOVE_ALLOW_EASY_FLING = MotionEvent.ACTION_MASK - 1;
public static final int INVALID_PAGE = -1;
protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
@@ -92,14 +91,16 @@
// The following constants need to be scaled based on density. The scaled versions will be
// assigned to the corresponding member variables below.
private static final int FLING_THRESHOLD_VELOCITY = 500;
+ private static final int EASY_FLING_THRESHOLD_VELOCITY = 400;
private static final int MIN_SNAP_VELOCITY = 1500;
private static final int MIN_FLING_VELOCITY = 250;
private boolean mFreeScroll = false;
- protected int mFlingThresholdVelocity;
- protected int mMinFlingVelocity;
- protected int mMinSnapVelocity;
+ protected final int mFlingThresholdVelocity;
+ protected final int mEasyFlingThresholdVelocity;
+ protected final int mMinFlingVelocity;
+ protected final int mMinSnapVelocity;
protected boolean mFirstLayout = true;
@@ -121,13 +122,17 @@
private float mLastMotion;
private float mLastMotionRemainder;
private float mTotalMotion;
- protected PagedOrientationHandler mOrientationHandler = new PortraitPagedViewHandler();
- protected final PagedViewOrientedState mOrientationState = new PagedViewOrientedState();
+ // Used in special cases where the fling checks can be relaxed for an intentional gesture
+ private boolean mAllowEasyFling;
+ protected PagedOrientationHandler mOrientationHandler = PagedOrientationHandler.PORTRAIT;
protected int[] mPageScrolls;
private boolean mIsBeingDragged;
+ // The amount of movement to begin scrolling
protected int mTouchSlop;
+ // The amount of movement to begin paging
+ protected int mPageSlop;
private int mMaximumVelocity;
protected boolean mAllowOverScroll = true;
@@ -144,9 +149,6 @@
protected int mUnboundedScroll;
- protected int mLayoutRotation = Surface.ROTATION_0;
- protected int mDisplayRotation = Surface.ROTATION_0;
-
// Page Indicator
@Thunk int mPageIndicatorViewId;
protected T mPageIndicator;
@@ -177,24 +179,19 @@
setHapticFeedbackEnabled(false);
mIsRtl = Utilities.isRtl(getResources());
- init();
- }
- /**
- * Initializes various states for this workspace.
- */
- protected void init() {
- Context context = getContext();
mScroller = new OverScroller(context);
setDefaultInterpolator(Interpolators.SCROLL);
mCurrentPage = 0;
final ViewConfiguration configuration = ViewConfiguration.get(context);
- mTouchSlop = configuration.getScaledPagingTouchSlop();
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mPageSlop = configuration.getScaledPagingTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
float density = getResources().getDisplayMetrics().density;
mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density);
+ mEasyFlingThresholdVelocity = (int) (EASY_FLING_THRESHOLD_VELOCITY * density);
mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density);
mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density);
@@ -264,13 +261,6 @@
forceFinishScroller(true);
}
- /**
- * Returns left offset of a page. This is the gap between pages and prevents overlap.
- */
- public int scrollOffsetLeft() {
- return mInsets.left + getPaddingLeft();
- }
-
private void abortScrollerAnimation(boolean resetNextPage) {
mScroller.abortAnimation();
// We need to clean up the next page here to avoid computeScrollHelper from
@@ -417,37 +407,6 @@
return mUnboundedScroll;
}
- protected void updateLayoutRotation(int touchRotation) {
- setLayoutRotation(touchRotation, mDisplayRotation);
- }
-
- /** @param touchRotation Must be one of {@link android.view.Surface.ROTATION_0/90/180/270} */
- public void setLayoutRotation(int touchRotation, int displayRotation) {
- if (mLayoutRotation == touchRotation && mDisplayRotation == displayRotation) {
- return;
- }
-
- mOrientationState.update(touchRotation, displayRotation);
- mOrientationHandler = mOrientationState.getOrientationHandler();
- mLayoutRotation = touchRotation;
- mDisplayRotation = displayRotation;
- requestLayout();
- }
-
- public PagedViewOrientedState getPagedViewOrientedState() {
- return mOrientationState;
- }
-
- public PagedOrientationHandler getPagedOrientationHandler() {
- return getPagedViewOrientedState().getOrientationHandler();
- }
-
- public void disableMultipleLayoutRotations(boolean disable) {
- mOrientationState.disableMultipleOrientations(disable);
- mOrientationHandler = mOrientationState.getOrientationHandler();
- requestLayout();
- }
-
@Override
public void scrollBy(int x, int y) {
mOrientationHandler.delegateScrollBy(this, getUnboundedScroll(), x, y);
@@ -955,9 +914,10 @@
// Remember location of down touch
mDownMotionX = x;
mDownMotionY = y;
- mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
+ mDownMotionPrimary = mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
mLastMotionRemainder = 0;
mTotalMotion = 0;
+ mAllowEasyFling = false;
mActivePointerId = ev.getPointerId(0);
updateIsBeingDraggedOnTouchDown();
@@ -989,7 +949,7 @@
private void updateIsBeingDraggedOnTouchDown() {
// mScroller.isFinished should be false when being flinged.
final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos());
- final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
+ final boolean finishedScrolling = (mScroller.isFinished() || xDist < mPageSlop / 3);
if (finishedScrolling) {
mIsBeingDragged = false;
@@ -1022,7 +982,7 @@
final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev, pointerIndex);
final int diff = (int) Math.abs(primaryDirection - mLastMotion);
final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
- boolean moved = diff > touchSlop;
+ boolean moved = diff > touchSlop || ev.getAction() == ACTION_MOVE_ALLOW_EASY_FLING;
if (moved) {
// Scroll if the user moved far enough along the X axis
@@ -1107,16 +1067,28 @@
}
}
+ /**
+ * Returns the amount of overscroll caused by the spring in {@link OverScroller}.
+ */
+ private int getSpringOverScroll(int amount) {
+ if (mScroller.isSpringing()) {
+ return amount < 0
+ ? mScroller.getCurrPos() - mMinScroll
+ : Math.max(0, mScroller.getCurrPos() - mMaxScroll);
+ } else {
+ return 0;
+ }
+ }
+
protected void dampedOverScroll(int amount) {
- mSpringOverScroll = amount;
if (amount == 0) {
return;
}
int size = mOrientationHandler.getMeasuredSize(this);
int overScrollAmount = OverScroll.dampedScroll(amount, size);
- mSpringOverScroll = overScrollAmount;
if (mScroller.isSpringing()) {
+ mSpringOverScroll = getSpringOverScroll(amount);
invalidate();
return;
}
@@ -1128,8 +1100,8 @@
}
protected void overScroll(int amount) {
- mSpringOverScroll = amount;
if (mScroller.isSpringing()) {
+ mSpringOverScroll = getSpringOverScroll(amount);
invalidate();
return;
}
@@ -1193,6 +1165,7 @@
mDownMotionPrimary = mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
mLastMotionRemainder = 0;
mTotalMotion = 0;
+ mAllowEasyFling = false;
mActivePointerId = ev.getPointerId(0);
if (mIsBeingDragged) {
onScrollInteractionBegin();
@@ -1200,8 +1173,14 @@
}
break;
- case MotionEvent.ACTION_MOVE:
- if (mIsBeingDragged) {
+ case ACTION_MOVE_ALLOW_EASY_FLING:
+ // Start scrolling immediately
+ determineScrollingStart(ev);
+ mAllowEasyFling = true;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mIsBeingDragged) {
// Scroll to follow the motion event
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -1231,6 +1210,8 @@
if (mIsBeingDragged) {
final int activePointerId = mActivePointerId;
final int pointerIndex = ev.findPointerIndex(activePointerId);
+ if (pointerIndex == -1) return true;
+
final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev,
pointerIndex);
final VelocityTracker velocityTracker = mVelocityTracker;
@@ -1245,9 +1226,14 @@
SIGNIFICANT_MOVE_THRESHOLD;
mTotalMotion += Math.abs(mLastMotion + mLastMotionRemainder - primaryDirection);
- boolean isFling = mTotalMotion > mTouchSlop && shouldFlingForVelocity(velocity);
+ boolean passedSlop = mAllowEasyFling || mTotalMotion > mPageSlop;
+ boolean isFling = passedSlop && shouldFlingForVelocity(velocity);
boolean isDeltaLeft = mIsRtl ? delta > 0 : delta < 0;
boolean isVelocityLeft = mIsRtl ? velocity > 0 : velocity < 0;
+ if (DEBUG_FAILED_QUICKSWITCH && !isFling && mAllowEasyFling) {
+ Log.d("Quickswitch", "isFling=false vel=" + velocity
+ + " threshold=" + mEasyFlingThresholdVelocity);
+ }
if (!mFreeScroll) {
// In the case that the page is moved far to one direction and then is flung
@@ -1347,7 +1333,8 @@
}
protected boolean shouldFlingForVelocity(int velocity) {
- return Math.abs(velocity) > mFlingThresholdVelocity;
+ float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity;
+ return Math.abs(velocity) > threshold;
}
private void resetTouchState() {
@@ -1424,8 +1411,7 @@
}
private void onSecondaryPointerUp(MotionEvent ev) {
- final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
- MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java
index 76de3e7..be994ee 100644
--- a/src/com/android/launcher3/PendingAddItemInfo.java
+++ b/src/com/android/launcher3/PendingAddItemInfo.java
@@ -18,10 +18,15 @@
import android.content.ComponentName;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.model.data.ItemInfo;
+
+import java.util.Optional;
+
/**
- * Meta data that is used for deferred binding.
- * e.g., this object is used to pass information on draggable targets when they are dropped onto
- * the workspace from another container.
+ * Meta data that is used for deferred binding. e.g., this object is used to pass information on
+ * draggable targets when they are dropped onto the workspace from another container.
*/
public class PendingAddItemInfo extends ItemInfo {
@@ -34,4 +39,22 @@
protected String dumpProperties() {
return super.dumpProperties() + " componentName=" + componentName;
}
+
+ /**
+ * Returns shallow copy of the object.
+ */
+ @Override
+ public ItemInfo makeShallowCopy() {
+ PendingAddItemInfo itemInfo = new PendingAddItemInfo();
+ itemInfo.copyFrom(this);
+ itemInfo.componentName = this.componentName;
+ return itemInfo;
+ }
+
+ @Nullable
+ @Override
+ public ComponentName getTargetComponent() {
+ return Optional.ofNullable(super.getTargetComponent()).orElse(componentName);
+ }
+
}
diff --git a/src/com/android/launcher3/Reorderable.java b/src/com/android/launcher3/Reorderable.java
new file mode 100644
index 0000000..047fb01
--- /dev/null
+++ b/src/com/android/launcher3/Reorderable.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import android.graphics.PointF;
+import android.view.View;
+
+public interface Reorderable {
+
+ /**
+ * Set the offset related to reorder hint and bounce animations
+ */
+ void setReorderBounceOffset(float x, float y);
+
+ void getReorderBounceOffset(PointF offset);
+
+ /**
+ * Set the offset related to previewing the new reordered position
+ */
+ void setReorderPreviewOffset(float x, float y);
+
+ void getReorderPreviewOffset(PointF offset);
+
+ /**
+ * Set the scale related to reorder hint and "bounce" animations
+ */
+ void setReorderBounceScale(float scale);
+ float getReorderBounceScale();
+
+ /**
+ * Get the com.android.view related to this object
+ */
+ View getView();
+}
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index 403d779..c9fb75a 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -22,6 +22,7 @@
public class ResourceUtils {
public static final int DEFAULT_NAVBAR_VALUE = 48;
+ public static final int INVALID_RESOURCE_HANDLE = -1;
public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width";
public static final String NAVBAR_BOTTOM_GESTURE_SIZE = "navigation_bar_gesture_height";
@@ -51,7 +52,13 @@
return val;
}
+ public static int getIntegerByName(String resName, Resources res, int defaultValue) {
+ int resId = res.getIdentifier(resName, "integer", "android");
+ return resId != 0 ? res.getInteger(resId) : defaultValue;
+ }
+
public static int pxFromDp(float size, DisplayMetrics metrics) {
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
+ return size < 0 ? INVALID_RESOURCE_HANDLE : Math.round(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
}
}
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 983c289..499b54f 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -3,12 +3,16 @@
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DISMISS_PREDICTION;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_UNINSTALL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_UNINSTALL_CANCELLED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_UNINSTALL_COMPLETED;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SYSTEM_NO;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
@@ -34,7 +38,11 @@
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.PackageManagerHelper;
@@ -55,7 +63,7 @@
private static final long CACHE_EXPIRE_TIMEOUT = 5000;
private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
-
+ private final StatsLogManager mStatsLogManager;
private final Alarm mCacheExpireAlarm;
private boolean mHadPendingAlarm;
@@ -66,8 +74,8 @@
public SecondaryDropTarget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
-
mCacheExpireAlarm = new Alarm();
+ mStatsLogManager = StatsLogManager.newInstance(context);
}
@Override
@@ -211,6 +219,13 @@
// Defer onComplete
d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
super.onDrop(d, options);
+ if (mCurrentAccessibilityAction == UNINSTALL) {
+ mStatsLogManager.logger().withInstanceId(d.logInstanceId)
+ .log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL);
+ } else if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
+ mStatsLogManager.logger().withInstanceId(d.logInstanceId)
+ .log(LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST);
+ }
}
@Override
@@ -335,8 +350,12 @@
mDragObject.dragInfo.user, PackageManager.MATCH_UNINSTALLED_PACKAGES) == null) {
mDragObject.dragSource = mOriginal;
mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
+ mStatsLogManager.logger().withInstanceId(mDragObject.logInstanceId)
+ .log(LAUNCHER_ITEM_UNINSTALL_COMPLETED);
} else {
sendFailure();
+ mStatsLogManager.logger().withInstanceId(mDragObject.logInstanceId)
+ .log(LAUNCHER_ITEM_UNINSTALL_CANCELLED);
}
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 0cd08d4..bf63788 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,7 +16,7 @@
package com.android.launcher3;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ICON_BADGED;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
@@ -24,10 +24,13 @@
import android.app.Person;
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
@@ -60,12 +63,17 @@
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
+import androidx.core.os.BuildCompat;
+
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
+import com.android.launcher3.graphics.GridOptionsProvider;
import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShortcutCachingLogic;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
@@ -74,6 +82,7 @@
import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.lang.reflect.Method;
+import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
@@ -97,6 +106,8 @@
public static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final Person[] EMPTY_PERSON_ARRAY = new Person[0];
+ public static final boolean ATLEAST_R = BuildCompat.isAtLeastR();
+
public static final boolean ATLEAST_Q = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
public static final boolean ATLEAST_P =
@@ -338,6 +349,30 @@
}
/**
+ * Bounds parameter to the range [0, 1]
+ */
+ public static float saturate(float a) {
+ return boundToRange(a, 0, 1.0f);
+ }
+
+ /**
+ * Returns the compliment (1 - a) of the parameter.
+ */
+ public static float comp(float a) {
+ return 1 - a;
+ }
+
+ /**
+ * Returns the "probabilistic or" of a and b. (a + b - ab).
+ * Useful beyond probability, can be used to combine two unit progresses for example.
+ */
+ public static float or(float a, float b) {
+ float satA = saturate(a);
+ float satB = saturate(b);
+ return satA + satB - (satA * satB);
+ }
+
+ /**
* Trims the string, removing all whitespace at the beginning and end of the string.
* Non-breaking whitespaces are also removed.
*/
@@ -483,6 +518,42 @@
|| e.getCause() instanceof DeadObjectException;
}
+ public static boolean isGridOptionsEnabled(Context context) {
+ return isComponentEnabled(context.getPackageManager(),
+ context.getPackageName(),
+ GridOptionsProvider.class.getName());
+ }
+
+ private static boolean isComponentEnabled(PackageManager pm, String pkgName, String clsName) {
+ ComponentName componentName = new ComponentName(pkgName, clsName);
+ int componentEnabledSetting = pm.getComponentEnabledSetting(componentName);
+
+ switch (componentEnabledSetting) {
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+ return false;
+ case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
+ return true;
+ case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
+ default:
+ // We need to get the application info to get the component's default state
+ try {
+ PackageInfo packageInfo = pm.getPackageInfo(pkgName,
+ PackageManager.GET_PROVIDERS | PackageManager.GET_DISABLED_COMPONENTS);
+
+ if (packageInfo.providers != null) {
+ return Arrays.stream(packageInfo.providers).anyMatch(
+ pi -> pi.name.equals(clsName) && pi.isEnabled());
+ }
+
+ // the component is not declared in the AndroidManifest
+ return false;
+ } catch (PackageManager.NameNotFoundException e) {
+ // the package isn't installed on the device
+ return false;
+ }
+ }
+ }
+
/**
* Utility method to post a runnable on the handler, skipping the synchronization barriers.
*/
@@ -535,6 +606,7 @@
outObj[0] = activityInfo;
return activityInfo.getFullResIcon(appState.getIconCache());
}
+ if (info.getIntent() == null || info.getIntent().getPackage() == null) return null;
List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
.buildRequest(launcher)
.query(ShortcutRequest.ALL);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 7c504a6..1441e0b 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -19,12 +19,18 @@
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
+import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
+import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_INACCESSIBLE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -76,9 +82,18 @@
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.WorkspaceTouchListener;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -109,7 +124,7 @@
*/
public class Workspace extends PagedView<WorkspacePageIndicator>
implements DropTarget, DragSource, View.OnTouchListener,
- DragController.DragListener, Insettable, LauncherStateManager.StateHandler,
+ DragController.DragListener, Insettable, StateHandler<LauncherState>,
WorkspaceLayoutManager {
/** The value that {@link #mTransitionProgress} must be greater than for
@@ -238,6 +253,8 @@
// Handles workspace state transitions
private final WorkspaceStateTransitionAnimation mStateTransitionAnimation;
+ private final StatsLogManager mStatsLogManager;
+
/**
* Used to inflate the Workspace from XML.
*
@@ -270,6 +287,7 @@
// Disable multitouch across the workspace/all apps/customize tray
setMotionEventSplittingEnabled(true);
setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
+ mStatsLogManager = StatsLogManager.newInstance(context);
}
@Override
@@ -349,7 +367,7 @@
}
@Override
- public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ public void onDragStart(DragObject dragObject, DragOptions options) {
if (ENFORCE_DRAG_EVENT_ORDER) {
enforceDragParity("onDragStart", 0, 0);
}
@@ -401,6 +419,9 @@
// Always enter the spring loaded mode
mLauncher.getStateManager().goToState(SPRING_LOADED);
+ mStatsLogManager.logger().withItemInfo(dragObject.dragInfo)
+ .withInstanceId(dragObject.logInstanceId)
+ .log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
}
public void deferRemoveExtraEmptyScreen() {
@@ -982,6 +1003,15 @@
if (!mOverlayShown) {
mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
Action.Direction.LEFT, ContainerType.WORKSPACE, 0);
+ mLauncher.getStatsLogManager().logger()
+ .withSrcState(LAUNCHER_STATE_HOME)
+ .withDstState(LAUNCHER_STATE_HOME)
+ .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setWorkspace(
+ LauncherAtom.WorkspaceContainer.newBuilder()
+ .setPageIndex(0))
+ .build())
+ .log(LAUNCHER_SWIPELEFT);
}
mOverlayShown = true;
// Not announcing the overlay page for accessibility since it announces itself.
@@ -991,6 +1021,15 @@
if (!ued.isPreviousHomeGesture()) {
mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
Action.Direction.RIGHT, ContainerType.WORKSPACE, -1);
+ mLauncher.getStatsLogManager().logger()
+ .withSrcState(LAUNCHER_STATE_HOME)
+ .withDstState(LAUNCHER_STATE_HOME)
+ .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setWorkspace(
+ LauncherAtom.WorkspaceContainer.newBuilder()
+ .setPageIndex(-1))
+ .build())
+ .log(LAUNCHER_SWIPERIGHT);
}
} else if (Float.compare(mOverlayTranslation, 0f) != 0) {
// When arriving to 0 overscroll from non-zero overscroll, announce page for
@@ -1082,9 +1121,20 @@
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
if (prevPage != mCurrentPage) {
- int swipeDirection = (prevPage < mCurrentPage) ? Action.Direction.RIGHT : Action.Direction.LEFT;
+ int swipeDirection = (prevPage < mCurrentPage)
+ ? Action.Direction.RIGHT : Action.Direction.LEFT;
+ StatsLogManager.EventEnum event = (prevPage < mCurrentPage)
+ ? LAUNCHER_SWIPERIGHT : LAUNCHER_SWIPELEFT;
mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
swipeDirection, ContainerType.WORKSPACE, prevPage);
+ mLauncher.getStatsLogManager().logger()
+ .withSrcState(LAUNCHER_STATE_HOME)
+ .withDstState(LAUNCHER_STATE_HOME)
+ .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setWorkspace(
+ LauncherAtom.WorkspaceContainer.newBuilder()
+ .setPageIndex(prevPage)).build())
+ .log(event);
}
}
@@ -1203,7 +1253,7 @@
/** Returns whether a drag should be allowed to be started from the current workspace state. */
public boolean workspaceIconsCanBeDragged() {
- return mLauncher.getStateManager().getState().workspaceIconsCanBeDragged;
+ return mLauncher.getStateManager().getState().hasFlag(FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
}
private void updateChildrenLayersEnabled() {
@@ -1313,14 +1363,13 @@
// Invalidate the pages now, so that we have the visible pages before the
// animation is started
- if (toState.hasMultipleVisiblePages) {
+ if (toState.hasFlag(FLAG_MULTI_PAGE)) {
mForceDrawAdjacentPages = true;
}
invalidate(); // This will call dispatchDraw(), which calls getVisiblePages().
ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
stepAnimator.addUpdateListener(listener);
- stepAnimator.setDuration(config.duration);
stepAnimator.addListener(listener);
animation.add(stepAnimator);
}
@@ -1331,7 +1380,10 @@
public void updateAccessibilityFlags() {
// TODO: Update the accessibility flags appropriately when dragging.
- int accessibilityFlag = mLauncher.getStateManager().getState().workspaceAccessibilityFlag;
+ int accessibilityFlag =
+ mLauncher.getStateManager().getState().hasFlag(FLAG_WORKSPACE_INACCESSIBLE)
+ ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
int total = getPageCount();
for (int i = 0; i < total; i++) {
@@ -1417,6 +1469,10 @@
mOutlineProvider = previewProvider;
+ if (draggableView == null && child instanceof DraggableView) {
+ draggableView = (DraggableView) child;
+ }
+
// The drag bitmap follows the touch point around on the screen
final Bitmap b = previewProvider.createDragBitmap();
int halfPadding = previewProvider.previewPadding / 2;
@@ -1427,12 +1483,8 @@
Point dragVisualizeOffset = null;
Rect dragRect = new Rect();
- if (draggableView == null && child instanceof DraggableView) {
- draggableView = (DraggableView) child;
- }
-
if (draggableView != null) {
- draggableView.getVisualDragBounds(dragRect);
+ draggableView.getSourceVisualDragBounds(dragRect);
dragLayerY += dragRect.top;
dragVisualizeOffset = new Point(- halfPadding, halfPadding);
}
@@ -1625,7 +1677,8 @@
Rect folderLocation = new Rect();
float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
target.removeView(v);
-
+ mStatsLogManager.logger().withItemInfo(destInfo).withInstanceId(d.logInstanceId)
+ .log(LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED);
FolderIcon fi = mLauncher.addFolder(target, container, screenId, targetCell[0],
targetCell[1]);
destInfo.cellX = -1;
@@ -1663,6 +1716,8 @@
if (dropOverView instanceof FolderIcon) {
FolderIcon fi = (FolderIcon) dropOverView;
if (fi.acceptDrop(d.dragInfo)) {
+ mStatsLogManager.logger().withItemInfo(fi.mInfo).withInstanceId(d.logInstanceId)
+ .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
// if the drag started here, we need to remove it from the workspace
@@ -1678,6 +1733,7 @@
@Override
public void prepareAccessibilityDrop() { }
+ @Override
public void onDrop(final DragObject d, DragOptions options) {
mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
CellLayout dropTargetLayout = mDropToLayout;
@@ -1864,6 +1920,8 @@
mLauncher.getStateManager().goToState(
NORMAL, SPRING_LOADED_EXIT_DELAY, onCompleteRunnable);
+ mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
+ .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
}
if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
@@ -2401,6 +2459,9 @@
// widgets/shortcuts/folders in a slightly different way
mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell,
item.spanX, item.spanY);
+ mStatsLogManager.logger().withItemInfo(d.dragInfo)
+ .withInstanceId(d.logInstanceId)
+ .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
}
};
boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
@@ -2489,7 +2550,10 @@
mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this);
resetTransitionTransform();
}
+ mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
+ .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
}
+
}
public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index 0b9d602..c3d4aeb 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -21,6 +21,7 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.touch.ItemLongClickListener;
public interface WorkspaceLayoutManager {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index c4c4377..cd938e1 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -16,16 +16,22 @@
package com.android.launcher3;
+import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE;
+
import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.LauncherState.FLAG_HAS_SYS_UI_SCRIM;
+import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_HAS_BACKGROUNDS;
+import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.ZOOM_OUT;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
-import static com.android.launcher3.graphics.WorkspaceAndHotseatScrim.SCRIM_PROGRESS;
+import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
import static com.android.launcher3.graphics.WorkspaceAndHotseatScrim.SYSUI_PROGRESS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
@@ -33,6 +39,7 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import android.animation.ValueAnimator;
import android.view.View;
import android.view.animation.Interpolator;
@@ -41,8 +48,11 @@
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.DynamicResource;
+import com.android.systemui.plugins.ResourceProvider;
/**
* Manages the animations between each of the workspace states.
@@ -64,7 +74,7 @@
}
/**
- * @see com.android.launcher3.LauncherStateManager.StateHandler#setStateWithAnimation
+ * @see com.android.launcher3.statemanager.StateManager.StateHandler#setStateWithAnimation
*/
public void setStateWithAnimation(
LauncherState toState, StateAnimationConfig config, PendingAnimation animation) {
@@ -102,17 +112,32 @@
View qsbView = qsbScaleView.getSearchView();
if (playAtomicComponent) {
Interpolator scaleInterpolator = config.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
- propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
+ LauncherState fromState = mLauncher.getStateManager().getState();
+ boolean shouldSpring = propertySetter instanceof PendingAnimation
+ && fromState == HINT_STATE && state == NORMAL;
+ if (shouldSpring) {
+ ((PendingAnimation) propertySetter).add(getSpringScaleAnimator(mLauncher,
+ mWorkspace, mNewScale));
+ } else {
+ propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
+ }
setPivotToScaleWithWorkspace(hotseat);
setPivotToScaleWithWorkspace(qsbScaleView);
float hotseatScale = hotseatScaleAndTranslation.scale;
- Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
- scaleInterpolator);
- propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
- hotseatScaleInterpolator);
- propertySetter.setFloat(qsbScaleView, SCALE_PROPERTY, qsbScaleAndTranslation.scale,
- hotseatScaleInterpolator);
+ if (shouldSpring) {
+ PendingAnimation pa = (PendingAnimation) propertySetter;
+ pa.add(getSpringScaleAnimator(mLauncher, hotseat, hotseatScale));
+ pa.add(getSpringScaleAnimator(mLauncher, qsbScaleView,
+ qsbScaleAndTranslation.scale));
+ } else {
+ Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
+ scaleInterpolator);
+ propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
+ hotseatScaleInterpolator);
+ propertySetter.setFloat(qsbScaleView, SCALE_PROPERTY, qsbScaleAndTranslation.scale,
+ hotseatScaleInterpolator);
+ }
float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
@@ -161,7 +186,8 @@
WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim();
propertySetter.setFloat(scrim, SCRIM_PROGRESS, state.getWorkspaceScrimAlpha(mLauncher),
LINEAR);
- propertySetter.setFloat(scrim, SYSUI_PROGRESS, state.hasSysUiScrim ? 1 : 0, LINEAR);
+ propertySetter.setFloat(scrim, SYSUI_PROGRESS,
+ state.hasFlag(FLAG_HAS_SYS_UI_SCRIM) ? 1 : 0, LINEAR);
}
public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
@@ -173,7 +199,8 @@
PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter,
StateAnimationConfig config) {
float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
- int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
+ int drawableAlpha = state.hasFlag(FLAG_WORKSPACE_HAS_BACKGROUNDS)
+ ? Math.round(pageAlpha * 255) : 0;
if (!config.onlyPlayAtomicComponent()) {
// Don't update the scrim during the atomic animation.
@@ -187,4 +214,24 @@
pageAlpha, fadeInterpolator);
}
}
+
+ /**
+ * Returns a spring based animator for the scale property of {@param v}.
+ */
+ public static ValueAnimator getSpringScaleAnimator(Launcher launcher, View v, float scale) {
+ ResourceProvider rp = DynamicResource.provider(launcher);
+ float damping = rp.getFloat(R.dimen.hint_scale_damping_ratio);
+ float stiffness = rp.getFloat(R.dimen.hint_scale_stiffness);
+ float velocityPxPerS = rp.getDimension(R.dimen.hint_scale_velocity_dp_per_s);
+
+ return new SpringAnimationBuilder(v.getContext())
+ .setStiffness(stiffness)
+ .setDampingRatio(damping)
+ .setMinimumVisibleChange(MIN_VISIBLE_CHANGE_SCALE)
+ .setEndValue(scale)
+ .setStartValue(SCALE_PROPERTY.get(v))
+ .setStartVelocity(velocityPxPerS)
+ .build(v, SCALE_PROPERTY);
+
+ }
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 414abab..136d43e 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -19,26 +19,26 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.ButtonDropTarget;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.keyboard.CustomActionsPopup;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.touch.ItemLongClickListener;
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index c0c0b37..d4ba11e 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -23,11 +23,11 @@
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationMainView;
import com.android.launcher3.shortcuts.DeepShortcutView;
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index 17daeb8..65a261d 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -21,17 +21,17 @@
import android.text.TextUtils;
import android.view.View;
-import com.android.launcher3.AppInfo;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
import com.android.launcher3.CellLayout;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType;
import com.android.launcher3.dragndrop.DragLayer;
-
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
/**
* Implementation of {@link DragAndDropAccessibilityDelegate} to support DnD on workspace.
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 8d1a102..c989e7b 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -16,9 +16,14 @@
package com.android.launcher3.allapps;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
@@ -37,12 +42,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
-import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
@@ -50,11 +53,11 @@
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.keyboard.FocusedItemDecorator;
-import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -195,7 +198,7 @@
}
private void resetWorkProfile() {
- mWorkModeSwitch.refresh();
+ mWorkModeSwitch.update(!mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED));
mAH[AdapterHolder.WORK].setupOverlay();
mAH[AdapterHolder.WORK].applyPadding();
}
@@ -277,6 +280,10 @@
}
}
+ public LayoutInflater getLayoutInflater() {
+ return LayoutInflater.from(getContext());
+ }
+
/**
* Resets the state of AllApps.
*/
@@ -349,15 +356,15 @@
}
ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
+ mlp.leftMargin = insets.left;
+ mlp.rightMargin = insets.right;
+ setLayoutParams(mlp);
+
if (grid.isVerticalBarLayout()) {
- mlp.leftMargin = insets.left;
- mlp.rightMargin = insets.right;
setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
} else {
- mlp.leftMargin = mlp.rightMargin = 0;
setPadding(0, 0, 0, 0);
}
- setLayoutParams(mlp);
InsettableFrameLayout.dispatchInsets(this, insets);
}
@@ -427,11 +434,22 @@
}
private void setupWorkToggle() {
- mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
- R.layout.work_mode_switch, this, false);
- this.addView(mWorkModeSwitch);
- mWorkModeSwitch.setInsets(mInsets);
- mWorkModeSwitch.post(() -> mAH[AdapterHolder.WORK].applyPadding());
+ if (Utilities.ATLEAST_P) {
+ mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
+ R.layout.work_mode_switch, this, false);
+ this.addView(mWorkModeSwitch);
+ mWorkModeSwitch.setInsets(mInsets);
+ mWorkModeSwitch.post(() -> mAH[AdapterHolder.WORK].applyPadding());
+ }
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ View overlay = mAH[AdapterHolder.WORK].getOverlayView();
+ int v = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? GONE : VISIBLE;
+ overlay.findViewById(R.id.work_apps_paused_title).setVisibility(v);
+ overlay.findViewById(R.id.work_apps_paused_content).setVisibility(v);
}
private void replaceRVContainer(boolean showTabs) {
@@ -444,7 +462,7 @@
int index = indexOfChild(oldView);
removeView(oldView);
int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
- View newView = LayoutInflater.from(getContext()).inflate(layout, this, false);
+ View newView = getLayoutInflater().inflate(layout, this, false);
addView(newView, index);
if (showTabs) {
mViewPager = (AllAppsPagedView) newView;
@@ -466,7 +484,9 @@
}
reset(true /* animate */);
if (mWorkModeSwitch != null) {
- mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK);
+ mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK
+ && mAllAppsStore.hasModelFlag(
+ FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
}
}
@@ -576,15 +596,8 @@
&& valueAnimator.getAnimatedFraction() >= FLING_ANIMATION_THRESHOLD) {
int searchViewId = getSearchView().getId();
addSpringView(searchViewId);
-
finishWithShiftAndVelocity(1, velocity * FLING_VELOCITY_MULTIPLIER,
- new DynamicAnimation.OnAnimationEndListener() {
- @Override
- public void onAnimationEnd(DynamicAnimation animation,
- boolean canceled, float value, float velocity) {
- removeSpringView(searchViewId);
- }
- });
+ (anim, canceled, value, velocity) -> removeSpringView(searchViewId));
shouldSpring = false;
}
@@ -617,7 +630,7 @@
AdapterHolder(boolean isWork) {
mIsWork = isWork;
appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
- adapter = new AllAppsGridAdapter(mLauncher, appsList);
+ adapter = new AllAppsGridAdapter(mLauncher, getLayoutInflater(), appsList);
appsList.setAdapter(adapter);
layoutManager = adapter.getLayoutManager();
}
@@ -643,18 +656,20 @@
void setupOverlay() {
if (!mIsWork || recyclerView == null) return;
- boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
+ boolean workDisabled = mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED);
if (mWorkDisabled == workDisabled) return;
- recyclerView.setContentDescription(
- workDisabled ? mLauncher.getString(R.string.work_apps_paused_title) : null);
+ recyclerView.setContentDescription(workDisabled ? mLauncher.getString(
+ R.string.work_apps_paused_content_description) : null);
View overlayView = getOverlayView();
recyclerView.setItemAnimator(new DefaultItemAnimator());
if (workDisabled) {
overlayView.setAlpha(0);
- appsList.updateItemFilter((info, cn) -> false);
recyclerView.addAutoSizedOverlay(overlayView);
overlayView.animate().alpha(1).withEndAction(
- () -> recyclerView.setItemAnimator(null)).start();
+ () -> {
+ appsList.updateItemFilter((info, cn) -> false);
+ recyclerView.setItemAnimator(null);
+ }).start();
} else if (mInfoMatcher != null) {
appsList.updateItemFilter(mInfoMatcher);
overlayView.animate().alpha(0).withEndAction(() -> {
@@ -667,8 +682,12 @@
void applyPadding() {
if (recyclerView != null) {
- int bottomOffset =
- mWorkModeSwitch != null && mIsWork ? mWorkModeSwitch.getHeight() : 0;
+ Resources res = getResources();
+ int switchH = res.getDimensionPixelSize(R.dimen.work_profile_footer_padding) * 2
+ + mInsets.bottom + Utilities.calculateTextHeight(
+ res.getDimension(R.dimen.work_profile_footer_text_size));
+
+ int bottomOffset = mWorkModeSwitch != null && mIsWork ? switchH : 0;
recyclerView.setPadding(padding.left, padding.top, padding.right,
padding.bottom + bottomOffset);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 3ee1293..f97eb28 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -15,206 +15,90 @@
*/
package com.android.launcher3.allapps;
-import com.android.launcher3.util.Thunk;
+import androidx.recyclerview.widget.LinearSmoothScroller;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-import java.util.HashSet;
-import java.util.List;
+import com.android.launcher3.allapps.AlphabeticalAppsList.FastScrollSectionInfo;
-import androidx.recyclerview.widget.RecyclerView;
+public class AllAppsFastScrollHelper {
-public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallback {
+ private static final int NO_POSITION = -1;
- private static final int INITIAL_TOUCH_SETTLING_DURATION = 100;
- private static final int REPEAT_TOUCH_SETTLING_DURATION = 200;
+ private int mTargetFastScrollPosition = NO_POSITION;
private AllAppsRecyclerView mRv;
- private AlphabeticalAppsList mApps;
+ private ViewHolder mLastSelectedViewHolder;
- // Keeps track of the current and targeted fast scroll section (the section to scroll to after
- // the initial delay)
- int mTargetFastScrollPosition = -1;
- @Thunk String mCurrentFastScrollSection;
- @Thunk String mTargetFastScrollSection;
-
- // The settled states affect the delay before the fast scroll animation is applied
- private boolean mHasFastScrollTouchSettled;
- private boolean mHasFastScrollTouchSettledAtLeastOnce;
-
- // Set of all views animated during fast scroll. We keep track of these ourselves since there
- // is no way to reset a view once it gets scrapped or recycled without other hacks
- private HashSet<RecyclerView.ViewHolder> mTrackedFastScrollViews = new HashSet<>();
-
- // Smooth fast-scroll animation frames
- @Thunk int mFastScrollFrameIndex;
- @Thunk final int[] mFastScrollFrames = new int[10];
-
- /**
- * This runnable runs a single frame of the smooth scroll animation and posts the next frame
- * if necessary.
- */
- @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
- @Override
- public void run() {
- if (mFastScrollFrameIndex < mFastScrollFrames.length) {
- mRv.scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
- mFastScrollFrameIndex++;
- mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
- }
- }
- };
-
- /**
- * This runnable updates the current fast scroll section to the target fastscroll section.
- */
- Runnable mFastScrollToTargetSectionRunnable = new Runnable() {
- @Override
- public void run() {
- // Update to the target section
- mCurrentFastScrollSection = mTargetFastScrollSection;
- mHasFastScrollTouchSettled = true;
- mHasFastScrollTouchSettledAtLeastOnce = true;
- updateTrackedViewsFastScrollFocusState();
- }
- };
-
- public AllAppsFastScrollHelper(AllAppsRecyclerView rv, AlphabeticalAppsList apps) {
+ public AllAppsFastScrollHelper(AllAppsRecyclerView rv) {
mRv = rv;
- mApps = apps;
- }
-
- public void onSetAdapter(AllAppsGridAdapter adapter) {
- adapter.setBindViewCallback(this);
}
/**
* Smooth scrolls the recycler view to the given section.
- *
- * @return whether the fastscroller can scroll to the new section.
*/
- public boolean smoothScrollToSection(int scrollY, int availableScrollHeight,
- AlphabeticalAppsList.FastScrollSectionInfo info) {
- if (mTargetFastScrollPosition != info.fastScrollToItem.position) {
- mTargetFastScrollPosition = info.fastScrollToItem.position;
- smoothSnapToPosition(scrollY, availableScrollHeight, info);
- return true;
+ public void smoothScrollToSection(FastScrollSectionInfo info) {
+ if (mTargetFastScrollPosition == info.fastScrollToItem.position) {
+ return;
}
- return false;
- }
-
- /**
- * Smoothly snaps to a given position. We do this manually by calculating the keyframes
- * ourselves and animating the scroll on the recycler view.
- */
- private void smoothSnapToPosition(int scrollY, int availableScrollHeight,
- AlphabeticalAppsList.FastScrollSectionInfo info) {
- mRv.removeCallbacks(mSmoothSnapNextFrameRunnable);
- mRv.removeCallbacks(mFastScrollToTargetSectionRunnable);
-
- trackAllChildViews();
- if (mHasFastScrollTouchSettled) {
- // In this case, the user has already settled once (and the fast scroll state has
- // animated) and they are just fine-tuning their section from the last section, so
- // we should make it feel fast and update immediately.
- mCurrentFastScrollSection = info.sectionName;
- mTargetFastScrollSection = null;
- updateTrackedViewsFastScrollFocusState();
- } else {
- // Otherwise, the user has scrubbed really far, and we don't want to distract the user
- // with the flashing fast scroll state change animation in addition to the fast scroll
- // section popup, so reset the views to normal, and wait for the touch to settle again
- // before animating the fast scroll state.
- mCurrentFastScrollSection = null;
- mTargetFastScrollSection = info.sectionName;
- mHasFastScrollTouchSettled = false;
- updateTrackedViewsFastScrollFocusState();
-
- // Delay scrolling to a new section until after some duration. If the user has been
- // scrubbing a while and makes multiple big jumps, then reduce the time needed for the
- // fast scroll to settle so it doesn't feel so long.
- mRv.postDelayed(mFastScrollToTargetSectionRunnable,
- mHasFastScrollTouchSettledAtLeastOnce ?
- REPEAT_TOUCH_SETTLING_DURATION :
- INITIAL_TOUCH_SETTLING_DURATION);
- }
-
- // Calculate the full animation from the current scroll position to the final scroll
- // position, and then run the animation for the duration. If we are scrolling to the
- // first fast scroll section, then just scroll to the top of the list itself.
- List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
- mApps.getFastScrollerSections();
- int newPosition = info.fastScrollToItem.position;
- int newScrollY = fastScrollSections.size() > 0 && fastScrollSections.get(0) == info
- ? 0
- : Math.min(availableScrollHeight, mRv.getCurrentScrollY(newPosition, 0));
- int numFrames = mFastScrollFrames.length;
- int deltaY = newScrollY - scrollY;
- float ySign = Math.signum(deltaY);
- int step = (int) (ySign * Math.ceil((float) Math.abs(deltaY) / numFrames));
- for (int i = 0; i < numFrames; i++) {
- // TODO(winsonc): We can interpolate this as well.
- mFastScrollFrames[i] = (int) (ySign * Math.min(Math.abs(step), Math.abs(deltaY)));
- deltaY -= step;
- }
- mFastScrollFrameIndex = 0;
- mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
+ mTargetFastScrollPosition = info.fastScrollToItem.position;
+ mRv.getLayoutManager().startSmoothScroll(new MyScroller(mTargetFastScrollPosition));
}
public void onFastScrollCompleted() {
- // TODO(winsonc): Handle the case when the user scrolls and releases before the animation
- // runs
-
- // Stop animating the fast scroll position and state
- mRv.removeCallbacks(mSmoothSnapNextFrameRunnable);
- mRv.removeCallbacks(mFastScrollToTargetSectionRunnable);
-
- // Reset the tracking variables
- mHasFastScrollTouchSettled = false;
- mHasFastScrollTouchSettledAtLeastOnce = false;
- mCurrentFastScrollSection = null;
- mTargetFastScrollSection = null;
- mTargetFastScrollPosition = -1;
-
- updateTrackedViewsFastScrollFocusState();
- mTrackedFastScrollViews.clear();
+ mTargetFastScrollPosition = NO_POSITION;
+ setLastHolderSelected(false);
+ mLastSelectedViewHolder = null;
}
- @Override
- public void onBindView(AllAppsGridAdapter.ViewHolder holder) {
- // Update newly bound views to the current fast scroll state if we are fast scrolling
- if (mCurrentFastScrollSection != null || mTargetFastScrollSection != null) {
- mTrackedFastScrollViews.add(holder);
+
+ private void setLastHolderSelected(boolean isSelected) {
+ if (mLastSelectedViewHolder != null) {
+ mLastSelectedViewHolder.itemView.setActivated(isSelected);
+ mLastSelectedViewHolder.setIsRecyclable(!isSelected);
}
}
- /**
- * Starts tracking all the recycler view's children which are FastScrollFocusableViews.
- */
- private void trackAllChildViews() {
- int childCount = mRv.getChildCount();
- for (int i = 0; i < childCount; i++) {
- RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder(mRv.getChildAt(i));
- if (viewHolder != null) {
- mTrackedFastScrollViews.add(viewHolder);
- }
- }
- }
+ private class MyScroller extends LinearSmoothScroller {
- /**
- * Updates the fast scroll focus on all the children.
- */
- private void updateTrackedViewsFastScrollFocusState() {
- for (RecyclerView.ViewHolder viewHolder : mTrackedFastScrollViews) {
- int pos = viewHolder.getAdapterPosition();
- boolean isActive = false;
- if (mCurrentFastScrollSection != null
- && pos > RecyclerView.NO_POSITION
- && pos < mApps.getAdapterItems().size()) {
- AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
- isActive = item != null &&
- mCurrentFastScrollSection.equals(item.sectionName) &&
- item.position == mTargetFastScrollPosition;
+ private final int mTargetPosition;
+
+ public MyScroller(int targetPosition) {
+ super(mRv.getContext());
+
+ mTargetPosition = targetPosition;
+ setTargetPosition(targetPosition);
+ }
+
+ @Override
+ protected int getVerticalSnapPreference() {
+ return SNAP_TO_START;
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mTargetPosition != mTargetFastScrollPosition) {
+ // Target changed, before the last scroll can finish
+ return;
}
- viewHolder.itemView.setActivated(isActive);
+
+ ViewHolder currentHolder = mRv.findViewHolderForAdapterPosition(mTargetPosition);
+ if (currentHolder == mLastSelectedViewHolder) {
+ return;
+ }
+
+ setLastHolderSelected(false);
+ mLastSelectedViewHolder = currentHolder;
+ setLastHolderSelected(true);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (mTargetPosition != mTargetFastScrollPosition) {
+ setLastHolderSelected(false);
+ mLastSelectedViewHolder = null;
+ }
}
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 1f861bc..8ec4d27 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -37,12 +37,12 @@
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.List;
@@ -71,11 +71,6 @@
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
-
- public interface BindViewCallback {
- void onBindView(ViewHolder holder);
- }
-
/**
* ViewHolder for each icon.
*/
@@ -186,7 +181,6 @@
private int mAppsPerRow;
- private BindViewCallback mBindViewCallback;
private OnFocusChangeListener mIconFocusListener;
// The text to show when there are no search results and no market search handler.
@@ -194,7 +188,8 @@
// The intent to send off to the market app, updated each time the search query changes.
private Intent mMarketSearchIntent;
- public AllAppsGridAdapter(BaseDraggingActivity launcher, AlphabeticalAppsList apps) {
+ public AllAppsGridAdapter(BaseDraggingActivity launcher, LayoutInflater inflater,
+ AlphabeticalAppsList apps) {
Resources res = launcher.getResources();
mLauncher = launcher;
mApps = apps;
@@ -202,7 +197,7 @@
mGridSizer = new GridSpanSizer();
mGridLayoutMgr = new AppsGridLayoutManager(launcher);
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
- mLayoutInflater = LayoutInflater.from(launcher);
+ mLayoutInflater = inflater;
mOnIconClickListener = launcher.getItemOnClickListener();
@@ -248,13 +243,6 @@
}
/**
- * Sets the callback for when views are bound.
- */
- public void setBindViewCallback(BindViewCallback cb) {
- mBindViewCallback = cb;
- }
-
- /**
* Returns the grid layout manager.
*/
public GridLayoutManager getLayoutManager() {
@@ -319,9 +307,6 @@
// nothing to do
break;
}
- if (mBindViewCallback != null) {
- mBindViewCallback.onBindView(holder);
- }
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 8fe4633..cbf02b7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -35,11 +35,11 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.views.RecyclerViewFastScroller;
@@ -53,12 +53,12 @@
public class AllAppsRecyclerView extends BaseRecyclerView implements LogContainerProvider {
private AlphabeticalAppsList mApps;
- private AllAppsFastScrollHelper mFastScrollHelper;
private final int mNumAppsPerRow;
// The specific view heights that we use to calculate scroll
- private SparseIntArray mViewHeights = new SparseIntArray();
- private SparseIntArray mCachedScrollPositions = new SparseIntArray();
+ private final SparseIntArray mViewHeights = new SparseIntArray();
+ private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
+ private final AllAppsFastScrollHelper mFastScrollHelper;
// The empty-search result background
private AllAppsBackgroundDrawable mEmptySearchBackground;
@@ -85,6 +85,7 @@
mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
R.dimen.all_apps_empty_search_bg_top_offset);
mNumAppsPerRow = LauncherAppState.getIDP(context).numColumns;
+ mFastScrollHelper = new AllAppsFastScrollHelper(this);
}
/**
@@ -92,7 +93,6 @@
*/
public void setApps(AlphabeticalAppsList apps) {
mApps = apps;
- mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
}
public AlphabeticalAppsList getApps() {
@@ -221,9 +221,6 @@
return "";
}
- // Stop the scroller if it is scrolling
- stopScroll();
-
// Find the fastscroll section that maps to this touch fraction
List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
mApps.getFastScrollerSections();
@@ -236,10 +233,7 @@
lastInfo = info;
}
- // Update the fast scroll
- int scrollY = getCurrentScrollY();
- int availableScrollHeight = getAvailableScrollHeight();
- mFastScrollHelper.smoothScrollToSection(scrollY, availableScrollHeight, lastInfo);
+ mFastScrollHelper.smoothScrollToSection(lastInfo);
return lastInfo.sectionName;
}
@@ -257,7 +251,6 @@
mCachedScrollPositions.clear();
}
});
- mFastScrollHelper.onSetAdapter((AllAppsGridAdapter) adapter);
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index a6ef10a..3ae0a18 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -15,23 +15,23 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
-import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
+import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
+import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.PromiseAppInfo;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.PromiseAppInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -50,14 +50,13 @@
private AppInfo[] mApps = EMPTY_ARRAY;
- private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>();
+ private final List<OnUpdateListener> mUpdateListeners = new CopyOnWriteArrayList<>();
private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
+ private int mModelFlags;
private int mDeferUpdatesFlags = 0;
private boolean mUpdatePending = false;
- private boolean mListenerUpdateInProgress = false;
-
public AppInfo[] getApps() {
return mApps;
}
@@ -65,11 +64,21 @@
/**
* Sets the current set of apps.
*/
- public void setApps(AppInfo[] apps) {
+ public void setApps(AppInfo[] apps, int flags) {
mApps = apps;
+ mModelFlags = flags;
notifyUpdate();
}
+ /**
+ * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_QUIET_MODE_ENABLED
+ * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
+ * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
+ */
+ public boolean hasModelFlag(int mask) {
+ return (mModelFlags & mask) != 0;
+ }
+
public AppInfo getApp(ComponentKey key) {
mTempInfo.componentName = key.componentName;
mTempInfo.user = key.user;
@@ -102,12 +111,9 @@
mUpdatePending = true;
return;
}
- mListenerUpdateInProgress = true;
- int count = mUpdateListeners.size();
- for (int i = 0; i < count; i++) {
- mUpdateListeners.get(i).onAppsUpdated();
+ for (OnUpdateListener listener : mUpdateListeners) {
+ listener.onAppsUpdated();
}
- mListenerUpdateInProgress = false;
}
public void addUpdateListener(OnUpdateListener listener) {
@@ -115,9 +121,6 @@
}
public void removeUpdateListener(OnUpdateListener listener) {
- if (mListenerUpdateInProgress) {
- Log.e("AllAppsStore", "Trying to remove listener during update", new Exception());
- }
mUpdateListeners.remove(listener);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 68b0706..a9b030e 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -6,32 +6,39 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+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.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.content.Context;
import android.util.FloatProperty;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.animation.Interpolator;
+import android.widget.EditText;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.Themes;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.views.ScrimView;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.PluginListener;
/**
* Handles AllApps view transition.
@@ -43,7 +50,8 @@
* If release velocity < THRES1, snap according to either top or bottom depending on whether it's
* closer to top or closer to the page indicator.
*/
-public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener {
+public class AllAppsTransitionController implements StateHandler<LauncherState>,
+ OnDeviceProfileChangeListener, PluginListener<AllAppsSearchPlugin> {
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@@ -65,7 +73,6 @@
private ScrimView mScrimView;
private final Launcher mLauncher;
- private final boolean mIsDarkTheme;
private boolean mIsVerticalLayout;
// Animation in this class is controlled by a single variable {@link mProgress}.
@@ -79,12 +86,15 @@
private float mScrollRangeDelta = 0;
+ // plugin related variables
+ private AllAppsSearchPlugin mPlugin;
+ private View mPluginContent;
+
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
mShiftRange = mLauncher.getDeviceProfile().heightPx;
mProgress = 1f;
- mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout();
mLauncher.addOnDeviceProfileChangeListener(this);
}
@@ -120,15 +130,8 @@
float shiftCurrent = progress * mShiftRange;
mAppsView.setTranslationY(shiftCurrent);
-
- // Use a light system UI (dark icons) if all apps is behind at least half of the
- // status bar.
- boolean forceChange = Math.min(shiftCurrent, mScrimView.getVisualTop())
- <= mLauncher.getDeviceProfile().getInsets().top / 2f;
- if (forceChange) {
- mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme);
- } else {
- mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
+ if (mPlugin != null) {
+ mPlugin.setProgress(progress);
}
}
@@ -156,7 +159,9 @@
StateAnimationConfig config, PendingAnimation builder) {
float targetProgress = toState.getVerticalProgress(mLauncher);
if (Float.compare(mProgress, targetProgress) == 0) {
- setAlphas(toState, config, builder);
+ if (!config.onlyPlayAtomicComponent()) {
+ setAlphas(toState, config, builder);
+ }
// Fail fast
onProgressAnimationEnd();
return;
@@ -172,7 +177,6 @@
: FAST_OUT_SLOW_IN;
Animator anim = createSpringAnimation(mProgress, targetProgress);
- anim.setDuration(config.duration);
anim.setInterpolator(config.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
anim.addListener(getProgressAnimatorListener());
builder.add(anim);
@@ -196,16 +200,25 @@
Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
- setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
- setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
- mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasAllAppsContent,
- setter, headerFade, allAppsFade);
+
+ if (mPlugin == null) {
+ setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
+ setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
+ mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
+ hasAllAppsContent, setter, headerFade, allAppsFade);
+ } else {
+ setter.setViewAlpha(mPluginContent, hasAllAppsContent ? 1 : 0, allAppsFade);
+ setter.setViewAlpha(mAppsView.getContentView(), 0, allAppsFade);
+ setter.setViewAlpha(mAppsView.getScrollBar(), 0, allAppsFade);
+ }
mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
(visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
- setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0, allAppsFade);
+ // Set visibility of the container at the very beginning or end of the transition.
+ setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0,
+ hasAnyVisibleItem ? INSTANT : FINAL_FRAME);
}
public AnimatorListenerAdapter getProgressAnimatorListener() {
@@ -215,6 +228,8 @@
public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) {
mAppsView = appsView;
mScrimView = scrimView;
+ PluginManagerWrapper.INSTANCE.get(mLauncher)
+ .addPluginListener(this, AllAppsSearchPlugin.class, false);
}
/**
@@ -237,5 +252,47 @@
if (Float.compare(mProgress, 1f) == 0) {
mAppsView.reset(false /* animate */);
}
+ updatePluginAnimationEnd();
+ }
+
+ @Override
+ public void onPluginConnected(AllAppsSearchPlugin plugin, Context context) {
+ mPlugin = plugin;
+ mPluginContent = mLauncher.getLayoutInflater().inflate(
+ R.layout.all_apps_content_layout, mAppsView, false);
+ mAppsView.addView(mPluginContent);
+ mPluginContent.setAlpha(0f);
+ mPlugin.setup((ViewGroup) mPluginContent, mLauncher, mShiftRange);
+ }
+
+ @Override
+ public void onPluginDisconnected(AllAppsSearchPlugin plugin) {
+ mPlugin = null;
+ mAppsView.removeView(mPluginContent);
+ }
+
+ public void onActivityDestroyed() {
+ PluginManagerWrapper.INSTANCE.get(mLauncher).removePluginListener(this);
+ }
+
+ /** Used for the plugin to signal when drag starts happens
+ * @param toAllApps*/
+ public void onDragStart(boolean toAllApps) {
+ if (mPlugin == null) return;
+
+ if (toAllApps) {
+ EditText editText = mAppsView.getSearchUiManager().setTextSearchEnabled(true);
+ mPlugin.setEditText(editText);
+ }
+ mPlugin.onDragStart(toAllApps ? 1f : 0f);
+ }
+
+ private void updatePluginAnimationEnd() {
+ if (mPlugin == null) return;
+ mPlugin.onAnimationEnd(mProgress);
+ if (Float.compare(mProgress, 1f) == 0) {
+ mAppsView.getSearchUiManager().setTextSearchEnabled(false);
+ mPlugin.setEditText(null);
+ }
}
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index b501c82..06209bb 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -18,8 +18,8 @@
import android.content.Context;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LabelComparator;
diff --git a/src/com/android/launcher3/allapps/AppInfoComparator.java b/src/com/android/launcher3/allapps/AppInfoComparator.java
index 8baf56c..823f98e 100644
--- a/src/com/android/launcher3/allapps/AppInfoComparator.java
+++ b/src/com/android/launcher3/allapps/AppInfoComparator.java
@@ -19,7 +19,7 @@
import android.os.Process;
import android.os.UserHandle;
-import com.android.launcher3.AppInfo;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.LabelComparator;
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 0f0fc3a..b4ff5ea 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -24,7 +24,6 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
-import android.content.SharedPreferences;
import android.os.Handler;
import android.os.UserManager;
import android.view.MotionEvent;
@@ -32,9 +31,11 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.OnboardingPrefs;
/**
* Abstract base class of floating view responsible for showing discovery bounce animation
@@ -43,17 +44,10 @@
private static final long DELAY_MS = 450;
- public static final String HOME_BOUNCE_SEEN = "launcher.apps_view_shown";
- public static final String SHELF_BOUNCE_SEEN = "launcher.shelf_bounce_seen";
- public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
- public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
-
- public static final int BOUNCE_MAX_COUNT = 3;
-
private final Launcher mLauncher;
private final Animator mDiscoBounceAnimation;
- private final StateListener mStateListener = new StateListener() {
+ private final StateListener<LauncherState> mStateListener = new StateListener<LauncherState>() {
@Override
public void onStateTransitionStart(LauncherState toState) {
handleClose(false);
@@ -142,8 +136,9 @@
}
private static void showForHomeIfNeeded(Launcher launcher, boolean withDelay) {
+ OnboardingPrefs onboardingPrefs = launcher.getOnboardingPrefs();
if (!launcher.isInState(NORMAL)
- || launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)
+ || onboardingPrefs.getBoolean(OnboardingPrefs.HOME_BOUNCE_SEEN)
|| AbstractFloatingView.getTopOpenView(launcher) != null
|| launcher.getSystemService(UserManager.class).isDemoUser()
|| Utilities.IS_RUNNING_IN_TEST_HARNESS) {
@@ -154,35 +149,39 @@
new Handler().postDelayed(() -> showForHomeIfNeeded(launcher, false), DELAY_MS);
return;
}
- incrementHomeBounceCount(launcher);
+ onboardingPrefs.incrementEventCount(OnboardingPrefs.HOME_BOUNCE_COUNT);
new DiscoveryBounce(launcher, 0).show(HOTSEAT);
}
- public static void showForOverviewIfNeeded(Launcher launcher) {
- showForOverviewIfNeeded(launcher, true);
+ public static void showForOverviewIfNeeded(Launcher launcher,
+ PagedOrientationHandler orientationHandler) {
+ showForOverviewIfNeeded(launcher, true, orientationHandler);
}
- private static void showForOverviewIfNeeded(Launcher launcher, boolean withDelay) {
+ private static void showForOverviewIfNeeded(Launcher launcher, boolean withDelay,
+ PagedOrientationHandler orientationHandler) {
+ OnboardingPrefs onboardingPrefs = launcher.getOnboardingPrefs();
if (!launcher.isInState(OVERVIEW)
|| !launcher.hasBeenResumed()
|| launcher.isForceInvisible()
|| launcher.getDeviceProfile().isVerticalBarLayout()
- || launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)
+ || !orientationHandler.isLayoutNaturalToLauncher()
+ || onboardingPrefs.getBoolean(OnboardingPrefs.SHELF_BOUNCE_SEEN)
|| launcher.getSystemService(UserManager.class).isDemoUser()
|| Utilities.IS_RUNNING_IN_TEST_HARNESS) {
return;
}
if (withDelay) {
- new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false), DELAY_MS);
+ new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false,
+ orientationHandler), DELAY_MS);
return;
- } else if (Launcher.ACTIVITY_TRACKER.hasPending()
- || AbstractFloatingView.getTopOpenView(launcher) != null) {
+ } else if (AbstractFloatingView.getTopOpenView(launcher) != null) {
// TODO: Move these checks to the top and call this method after invalidate handler.
return;
}
- incrementShelfBounceCount(launcher);
+ onboardingPrefs.incrementEventCount(OnboardingPrefs.SHELF_BOUNCE_COUNT);
new DiscoveryBounce(launcher, (1 - OVERVIEW.getVerticalProgress(launcher)))
.show(PREDICTION);
@@ -209,22 +208,4 @@
mController.setProgress(progress - mDelta);
}
}
-
- private static void incrementShelfBounceCount(Launcher launcher) {
- SharedPreferences sharedPrefs = launcher.getSharedPrefs();
- int count = sharedPrefs.getInt(SHELF_BOUNCE_COUNT, 0);
- if (count > BOUNCE_MAX_COUNT) {
- return;
- }
- sharedPrefs.edit().putInt(SHELF_BOUNCE_COUNT, count + 1).apply();
- }
-
- private static void incrementHomeBounceCount(Launcher launcher) {
- SharedPreferences sharedPrefs = launcher.getSharedPrefs();
- int count = sharedPrefs.getInt(HOME_BOUNCE_COUNT, 0);
- if (count > BOUNCE_MAX_COUNT) {
- return;
- }
- sharedPrefs.edit().putInt(HOME_BOUNCE_COUNT, count + 1).apply();
- }
}
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index f6766c4..a6bc6cf 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -22,7 +22,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.views.WorkEduView;
/**
@@ -32,7 +32,7 @@
private final Launcher mLauncher;
- private LauncherStateManager.StateListener mWorkTabListener;
+ private StateListener<LauncherState> mWorkTabListener;
public LauncherAllAppsContainerView(Context context) {
this(context, null);
@@ -60,6 +60,14 @@
}
@Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
+ return false;
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
public void setInsets(Rect insets) {
super.setInsets(insets);
mLauncher.getAllAppsController()
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 2515c24..2de425e 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -135,8 +135,8 @@
@Override
public void setActiveMarker(int activePage) {
updateTabTextColor(activePage);
- updateIndicatorPosition(activePage);
if (mContainerView != null && mLastActivePage != activePage) {
+ updateIndicatorPosition(activePage);
mContainerView.onTabChanged(activePage);
}
mLastActivePage = activePage;
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index cf9a088..7d5363f 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -15,9 +15,14 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
+
import android.graphics.Rect;
import android.view.KeyEvent;
import android.view.animation.Interpolator;
+import android.widget.EditText;
+
+import androidx.annotation.Nullable;
import com.android.launcher3.anim.PropertySetter;
@@ -52,4 +57,21 @@
*/
void setContentVisibility(int visibleElements, PropertySetter setter,
Interpolator interpolator);
+
+ /**
+ * Returns true if the QSB should be visible for the given set of visible elements
+ */
+ default boolean isQsbVisible(int visibleElements) {
+ return (visibleElements & ALL_APPS_HEADER) != 0;
+ }
+
+ /**
+ * Called to control how the search UI result should be handled.
+ *
+ * @param isEnabled when {@code true}, the search is all handled inside AOSP
+ * and is not overlayable.
+ * @return the searchbox edit text object
+ */
+ @Nullable
+ EditText setTextSearchEnabled(boolean isEnabled);
}
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 05db18e..4567ee6 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -15,20 +15,19 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
-
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.SharedPreferences;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
import android.widget.Switch;
import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.pm.UserCache;
@@ -46,48 +45,66 @@
private Rect mInsets = new Rect();
+ private final float[] mTouch = new float[2];
+ private int mTouchSlop;
+
public WorkModeSwitch(Context context) {
super(context);
+ init();
}
public WorkModeSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
+ init();
}
public WorkModeSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
+ mTouchSlop = viewConfiguration.getScaledTouchSlop();
}
@Override
- public void setChecked(boolean checked) {
-
- }
+ public void setChecked(boolean checked) { }
@Override
public void toggle() {
- Launcher launcher = Launcher.getLauncher(getContext());
// don't show tip if user uses toggle
- launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply();
+ Utilities.getPrefs(getContext()).edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply();
trySetQuietModeEnabledToAllProfilesAsync(isChecked());
}
- private void setCheckedInternal(boolean checked) {
- super.setChecked(checked);
- setCompoundDrawablesWithIntrinsicBounds(
- checked ? R.drawable.ic_corp : R.drawable.ic_corp_off, 0, 0, 0);
- }
-
- public void refresh() {
- if (!shouldShowWorkSwitch()) return;
- UserCache userManager = UserCache.INSTANCE.get(getContext());
- setCheckedInternal(!userManager.isAnyProfileQuietModeEnabled());
+ /**
+ * Sets the enabled or disabled state of the button
+ * @param isChecked
+ */
+ public void update(boolean isChecked) {
+ super.setChecked(isChecked);
+ setCompoundDrawablesRelativeWithIntrinsicBounds(
+ isChecked ? R.drawable.ic_corp : R.drawable.ic_corp_off, 0, 0, 0);
setEnabled(true);
}
@Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- this.setVisibility(shouldShowWorkSwitch() ? VISIBLE : GONE);
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mTouch[0] = ev.getX();
+ mTouch[1] = ev.getY();
+ } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ if (Math.abs(mTouch[0] - ev.getX()) > mTouchSlop
+ || Math.abs(mTouch[1] - ev.getY()) > mTouchSlop) {
+ int action = ev.getAction();
+ ev.setAction(MotionEvent.ACTION_CANCEL);
+ super.onTouchEvent(ev);
+ ev.setAction(action);
+ return false;
+ }
+ }
+ return super.onTouchEvent(ev);
}
private void trySetQuietModeEnabledToAllProfilesAsync(boolean enabled) {
@@ -106,13 +123,12 @@
* Animates in/out work profile toggle panel based on the tab user is on
*/
public void setWorkTabVisible(boolean workTabVisible) {
- if (!shouldShowWorkSwitch()) return;
clearAnimation();
if (workTabVisible) {
setVisibility(VISIBLE);
setAlpha(0);
animate().alpha(1).start();
- showTipifNeeded();
+ showTipIfNeeded();
} else {
animate().alpha(0).withEndAction(() -> this.setVisibility(GONE)).start();
}
@@ -169,23 +185,18 @@
}
}
- private boolean shouldShowWorkSwitch() {
- Launcher launcher = Launcher.getLauncher(getContext());
- return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
- || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
- == PackageManager.PERMISSION_GRANTED);
- }
-
/**
* Shows a work tip on the Nth work tab open
*/
- public void showTipifNeeded() {
- Launcher launcher = Launcher.getLauncher(getContext());
- int tipCounter = launcher.getSharedPrefs().getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD);
+ public void showTipIfNeeded() {
+ Context context = getContext();
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ int tipCounter = prefs.getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD);
if (tipCounter < 0) return;
if (tipCounter == 0) {
- new ArrowTipView(launcher).show(launcher.getString(R.string.work_switch_tip), getTop());
+ new ArrowTipView(context)
+ .show(context.getString(R.string.work_switch_tip), getTop());
}
- launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply();
+ prefs.edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply();
}
}
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 9e3a862..356c52c 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -19,7 +19,6 @@
import static android.view.View.MeasureSpec.getSize;
import static android.view.View.MeasureSpec.makeMeasureSpec;
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
import static com.android.launcher3.Utilities.prefixTextWithIcon;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
@@ -33,6 +32,7 @@
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.Interpolator;
+import android.widget.EditText;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
@@ -212,6 +212,11 @@
@Override
public void setContentVisibility(int visibleElements, PropertySetter setter,
Interpolator interpolator) {
- setter.setViewAlpha(this, (visibleElements & ALL_APPS_HEADER) != 0 ? 1 : 0, interpolator);
+ setter.setViewAlpha(this, isQsbVisible(visibleElements) ? 1 : 0, interpolator);
+ }
+
+ @Override
+ public EditText setTextSearchEnabled(boolean isEnabled) {
+ return this;
}
}
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 26f6ec3..f72a988 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -17,7 +17,7 @@
import android.os.Handler;
-import com.android.launcher3.AppInfo;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.ComponentKey;
import java.text.Collator;
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index f12789a..ea0ff8b 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.anim;
+import static com.android.launcher3.Utilities.boundToRange;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
@@ -27,12 +28,9 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
-import android.util.FloatProperty;
import androidx.annotation.Nullable;
-import com.android.launcher3.Utilities;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -55,28 +53,12 @@
* to float (animation-fraction * total duration) to int conversion.
*/
public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
- /**
- * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
- */
ArrayList<Holder> childAnims = new ArrayList<>();
- addAnimationHoldersRecur(anim, SpringProperty.DEFAULT, childAnims);
+ addAnimationHoldersRecur(anim, duration, SpringProperty.DEFAULT, childAnims);
return new AnimatorPlaybackController(anim, duration, childAnims);
}
- private static final FloatProperty<ValueAnimator> CURRENT_PLAY_TIME =
- new FloatProperty<ValueAnimator>("current-play-time") {
- @Override
- public void setValue(ValueAnimator animator, float v) {
- animator.setCurrentPlayTime((long) v);
- }
-
- @Override
- public Float get(ValueAnimator animator) {
- return (float) animator.getCurrentPlayTime();
- }
- };
-
// Progress factor after which an animation is considered almost completed.
private static final float ANIMATION_COMPLETE_THRESHOLD = 0.95f;
@@ -166,7 +148,7 @@
float scaleInverse = 1 / Math.abs(scale);
float scaledVelocity = velocity * scaleInverse;
- float nextFrameProgress = Utilities.boundToRange(getProgressFraction()
+ float nextFrameProgress = boundToRange(getProgressFraction()
+ scaledVelocity * getSingleFrameMs(context), 0f, 1f);
// Update setters for spring
@@ -177,21 +159,22 @@
long springDuration = animationDuration;
for (Holder h : mChildAnimations) {
if ((h.springProperty.flags & springFlag) != 0) {
- SpringAnimationBuilder s = new SpringAnimationBuilder(h.anim, CURRENT_PLAY_TIME)
- .setStartValue(clampDuration(mCurrentFraction))
- .setEndValue(goingToEnd ? h.anim.getDuration() : 0)
- .setStartVelocity(scaledVelocity * h.anim.getDuration())
+ SpringAnimationBuilder s = new SpringAnimationBuilder(context)
+ .setStartValue(mCurrentFraction)
+ .setEndValue(goingToEnd ? 1 : 0)
+ .setStartVelocity(scaledVelocity)
.setMinimumVisibleChange(scaleInverse)
.setDampingRatio(h.springProperty.mDampingRatio)
- .setStiffness(h.springProperty.mStiffness);
+ .setStiffness(h.springProperty.mStiffness)
+ .computeParams();
- long expectedDurationL = s.build(context).getDuration();
+ long expectedDurationL = s.getDuration();
springDuration = Math.max(expectedDurationL, springDuration);
float expectedDuration = expectedDurationL;
- h.setter = (a, l) ->
- s.setValue(a, mAnimationPlayer.getCurrentPlayTime() / expectedDuration);
- h.anim.setInterpolator(LINEAR);
+ h.mapper = (progress, globalEndProgress) ->
+ mAnimationPlayer.getCurrentPlayTime() / expectedDuration;
+ h.anim.setInterpolator(s::getInterpolatedValue);
}
}
@@ -250,9 +233,9 @@
if (mTargetCancelled) {
return;
}
- long playPos = clampDuration(fraction);
+ float progress = boundToRange(fraction, 0, 1);
for (Holder holder : mChildAnimations) {
- holder.setter.set(holder.anim, playPos);
+ holder.setProgress(progress);
}
}
@@ -374,14 +357,14 @@
}
/**
- * Interface for setting position of value animator
+ * Interface for mapping progress to animation progress
*/
- private interface PositionSetter {
+ private interface ProgressMapper {
- PositionSetter DEFAULT = (anim, playPos) ->
- anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
+ ProgressMapper DEFAULT = (progress, globalEndProgress) ->
+ progress > globalEndProgress ? 1 : (progress / globalEndProgress);
- void set(ValueAnimator anim, long position);
+ float getProgress(float progress, float globalProgress);
}
/**
@@ -395,27 +378,34 @@
public final TimeInterpolator interpolator;
- public PositionSetter setter;
+ public final float globalEndProgress;
- Holder(Animator anim, SpringProperty springProperty) {
+ public ProgressMapper mapper;
+
+ Holder(Animator anim, float globalDuration, SpringProperty springProperty) {
this.anim = (ValueAnimator) anim;
this.springProperty = springProperty;
this.interpolator = this.anim.getInterpolator();
- this.setter = PositionSetter.DEFAULT;
+ this.globalEndProgress = anim.getDuration() / globalDuration;
+ this.mapper = ProgressMapper.DEFAULT;
+ }
+
+ public void setProgress(float progress) {
+ anim.setCurrentFraction(mapper.getProgress(progress, globalEndProgress));
}
public void reset() {
anim.setInterpolator(interpolator);
- setter = PositionSetter.DEFAULT;
+ mapper = ProgressMapper.DEFAULT;
}
}
- static void addAnimationHoldersRecur(
- Animator anim, SpringProperty springProperty, ArrayList<Holder> out) {
+ static void addAnimationHoldersRecur(Animator anim, long globalDuration,
+ SpringProperty springProperty, ArrayList<Holder> out) {
long forceDuration = anim.getDuration();
TimeInterpolator forceInterpolator = anim.getInterpolator();
if (anim instanceof ValueAnimator) {
- out.add(new Holder(anim, springProperty));
+ out.add(new Holder(anim, globalDuration, springProperty));
} else if (anim instanceof AnimatorSet) {
for (Animator child : ((AnimatorSet) anim).getChildAnimations()) {
if (forceDuration > 0) {
@@ -424,7 +414,7 @@
if (forceInterpolator != null) {
child.setInterpolator(forceInterpolator);
}
- addAnimationHoldersRecur(child, springProperty, out);
+ addAnimationHoldersRecur(child, globalDuration, springProperty, out);
}
} else {
throw new RuntimeException("Unknown animation type " + anim);
diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java
index 06d0f1c..6ea38ec 100644
--- a/src/com/android/launcher3/anim/FlingSpringAnim.java
+++ b/src/com/android/launcher3/anim/FlingSpringAnim.java
@@ -35,6 +35,7 @@
private final FlingAnimation mFlingAnim;
private SpringAnimation mSpringAnim;
+ private final boolean mSkipFlingAnim;
private float mTargetPosition;
@@ -57,6 +58,10 @@
.setMaxValue(maxValue);
mTargetPosition = targetPosition;
+ // We are already past the fling target, so skip it to avoid losing a frame of the spring.
+ mSkipFlingAnim = startPosition <= minValue && startVelocity < 0
+ || startPosition >= maxValue && startVelocity > 0;
+
mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
mSpringAnim = new SpringAnimation(object, property)
.setStartValue(value)
@@ -84,6 +89,9 @@
public void start() {
mFlingAnim.start();
+ if (mSkipFlingAnim) {
+ mFlingAnim.cancel();
+ }
}
public void end() {
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index fccc120..860cceb 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -61,6 +61,11 @@
public static final Interpolator EXAGGERATED_EASE;
public static final Interpolator INSTANT = t -> 1;
+ /**
+ * All values of t map to 0 until t == 1. This is primarily useful for setting view visibility,
+ * which should only happen at the very end of the animation (when it's already hidden).
+ */
+ public static final Interpolator FINAL_FRAME = t -> t < 1 ? 0 : 1;
private static final int MIN_SETTLE_DURATION = 200;
private static final float OVERSHOOT_FACTOR = 0.9f;
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index a95a5e1..4195933 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur;
import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
@@ -51,12 +52,8 @@
private ValueAnimator mProgressAnimator;
public PendingAnimation(long duration) {
- this(duration, new AnimatorSet());
- }
-
- public PendingAnimation(long duration, AnimatorSet targetSet) {
mDuration = duration;
- mAnim = targetSet;
+ mAnim = new AnimatorSet();
}
/**
@@ -72,8 +69,8 @@
}
public void add(Animator a, SpringProperty springProperty) {
- mAnim.play(a);
- addAnimationHoldersRecur(a, springProperty, mAnimHolders);
+ mAnim.play(a.setDuration(mDuration));
+ addAnimationHoldersRecur(a, mDuration, springProperty, mAnimHolders);
}
public void finish(boolean isSuccess, int logAction) {
@@ -90,7 +87,7 @@
}
ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
anim.addListener(new AlphaUpdateListener(view));
- anim.setDuration(mDuration).setInterpolator(interpolator);
+ anim.setInterpolator(interpolator);
add(anim);
}
@@ -105,6 +102,13 @@
add(anim);
}
+ public <T> void addFloat(T target, FloatProperty<T> property, float from, float to,
+ TimeInterpolator interpolator) {
+ Animator anim = ObjectAnimator.ofFloat(target, property, from, to);
+ anim.setInterpolator(interpolator);
+ add(anim);
+ }
+
@Override
public <T> void setInt(T target, IntProperty<T> property, int value,
TimeInterpolator interpolator) {
@@ -112,7 +116,7 @@
return;
}
Animator anim = ObjectAnimator.ofInt(target, property, value);
- anim.setDuration(mDuration).setInterpolator(interpolator);
+ anim.setInterpolator(interpolator);
add(anim);
}
@@ -121,14 +125,33 @@
*/
public void addOnFrameCallback(Runnable runnable) {
if (mProgressAnimator == null) {
- mProgressAnimator = ValueAnimator.ofFloat(0, 1).setDuration(mDuration);
- add(mProgressAnimator);
+ mProgressAnimator = ValueAnimator.ofFloat(0, 1);
}
mProgressAnimator.addUpdateListener(anim -> runnable.run());
}
- public AnimatorSet getAnim() {
+ /**
+ * @see AnimatorSet#addListener(AnimatorListener)
+ */
+ public void addListener(Animator.AnimatorListener listener) {
+ mAnim.addListener(listener);
+ }
+
+ /**
+ * Creates and returns the underlying AnimatorSet
+ */
+ public AnimatorSet buildAnim() {
+ // Add progress animation to the end, so that frame callback is called after all the other
+ // animation update.
+ if (mProgressAnimator != null) {
+ add(mProgressAnimator);
+ mProgressAnimator = null;
+ }
+ if (mAnimHolders.isEmpty()) {
+ // Add a dummy animation to that the duration is respected
+ add(ValueAnimator.ofFloat(0, 1).setDuration(mDuration));
+ }
return mAnim;
}
@@ -136,7 +159,7 @@
* Creates a controller for this animation
*/
public AnimatorPlaybackController createPlaybackController() {
- return new AnimatorPlaybackController(mAnim, mDuration, mAnimHolders);
+ return new AnimatorPlaybackController(buildAnim(), mDuration, mAnimHolders);
}
/**
diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
index f22a9f0..a9702b4 100644
--- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java
+++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
@@ -15,7 +15,10 @@
*/
package com.android.launcher3.anim;
-import android.animation.ObjectAnimator;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.util.FloatProperty;
@@ -28,10 +31,9 @@
* Utility class to build an object animator which follows the same path as a spring animation for
* an underdamped spring.
*/
-public class SpringAnimationBuilder<T> extends FloatProperty<T> {
+public class SpringAnimationBuilder {
- private final T mTarget;
- private final FloatProperty<T> mProperty;
+ private final Context mContext;
private float mStartValue;
private float mEndValue;
@@ -64,27 +66,23 @@
private double mValueThreshold;
private double mVelocityThreshold;
- private float mCurrentTime = 0;
+ private float mDuration = 0;
- public SpringAnimationBuilder(T target, FloatProperty<T> property) {
- super("dynamic-spring-property");
- mTarget = target;
- mProperty = property;
-
- mStartValue = mProperty.get(target);
+ public SpringAnimationBuilder(Context context) {
+ mContext = context;
}
- public SpringAnimationBuilder<T> setEndValue(float value) {
+ public SpringAnimationBuilder setEndValue(float value) {
mEndValue = value;
return this;
}
- public SpringAnimationBuilder<T> setStartValue(float value) {
+ public SpringAnimationBuilder setStartValue(float value) {
mStartValue = value;
return this;
}
- public SpringAnimationBuilder<T> setValues(float... values) {
+ public SpringAnimationBuilder setValues(float... values) {
if (values.length > 1) {
mStartValue = values[0];
mEndValue = values[values.length - 1];
@@ -94,7 +92,7 @@
return this;
}
- public SpringAnimationBuilder<T> setStiffness(
+ public SpringAnimationBuilder setStiffness(
@FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
if (stiffness <= 0) {
throw new IllegalArgumentException("Spring stiffness constant must be positive.");
@@ -103,7 +101,7 @@
return this;
}
- public SpringAnimationBuilder<T> setDampingRatio(
+ public SpringAnimationBuilder setDampingRatio(
@FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
float dampingRatio) {
if (dampingRatio <= 0 || dampingRatio >= 1) {
@@ -113,7 +111,7 @@
return this;
}
- public SpringAnimationBuilder<T> setMinimumVisibleChange(
+ public SpringAnimationBuilder setMinimumVisibleChange(
@FloatRange(from = 0.0, fromInclusive = false) float minimumVisibleChange) {
if (minimumVisibleChange <= 0) {
throw new IllegalArgumentException("Minimum visible change must be positive.");
@@ -122,25 +120,21 @@
return this;
}
- public SpringAnimationBuilder<T> setStartVelocity(float startVelocity) {
+ public SpringAnimationBuilder setStartVelocity(float startVelocity) {
mVelocity = startVelocity;
return this;
}
- @Override
- public void setValue(T object, float time) {
- mCurrentTime = time;
- mProperty.setValue(
- object, (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue);
+ public float getInterpolatedValue(float fraction) {
+ return getValue(mDuration * fraction);
}
- @Override
- public Float get(T t) {
- return mCurrentTime;
+ private float getValue(float time) {
+ return (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue;
}
- public ObjectAnimator build(Context context) {
- int singleFrameMs = DefaultDisplay.getSingleFrameMs(context);
+ public SpringAnimationBuilder computeParams() {
+ int singleFrameMs = DefaultDisplay.getSingleFrameMs(mContext);
double naturalFreq = Math.sqrt(mStiffness);
double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
@@ -187,12 +181,27 @@
}
} while (true);
+ mDuration = (float) duration;
+ return this;
+ }
- long durationMs = (long) (1000.0 * duration);
- ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration);
- animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR);
- animator.addListener(AnimationSuccessListener.forRunnable(
- () -> mProperty.setValue(mTarget, mEndValue)));
+ public long getDuration() {
+ return (long) (1000.0 * mDuration);
+ }
+
+ public <T> ValueAnimator build(T target, FloatProperty<T> property) {
+ computeParams();
+
+ ValueAnimator animator = ValueAnimator.ofFloat(0, mDuration);
+ animator.setDuration(getDuration()).setInterpolator(LINEAR);
+ animator.addUpdateListener(anim ->
+ property.set(target, getInterpolatedValue(anim.getAnimatedFraction())));
+ animator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animation) {
+ property.set(target, mEndValue);
+ }
+ });
return animator;
}
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 737c97b..1d32d1d 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -75,9 +75,6 @@
}
public static void sendScrollFinishedEventToTest(Context context) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendScrollFinishedEventToTest");
- }
final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
if (accessibilityManager == null) return;
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index ec34350..5c4a492 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -36,8 +36,6 @@
private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
public static final String FLAGS_PREF_NAME = "featureFlags";
- public static final String FLAG_ENABLE_FIXED_ROTATION_TRANSFORM =
- "ENABLE_FIXED_ROTATION_TRANSFORM";
private FeatureFlags() { }
@@ -92,23 +90,31 @@
public static final BooleanFlag ENABLE_QUICKSTEP_LIVE_TILE = getDebugFlag(
"ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
- public static final BooleanFlag ENABLE_HINTS_IN_OVERVIEW = getDebugFlag(
- "ENABLE_HINTS_IN_OVERVIEW", false, "Show chip hints and gleams on the overview screen");
+ // Keep as DeviceFlag to allow remote disable in emergency.
+ public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag(
+ "ENABLE_SUGGESTED_ACTIONS_OVERVIEW", false, "Show chip hints on the overview screen");
public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
"FOLDER_NAME_SUGGEST", true,
"Suggests folder names instead of blank text.");
+ public static final BooleanFlag FOLDER_NAME_MAJORITY_RANKING = getDebugFlag(
+ "FOLDER_NAME_MAJORITY_RANKING", true,
+ "Suggests folder names based on majority based ranking.");
+
public static final BooleanFlag APP_SEARCH_IMPROVEMENTS = new DeviceFlag(
"APP_SEARCH_IMPROVEMENTS", true,
"Adds localized title and keyword search and ranking");
public static final BooleanFlag ENABLE_PREDICTION_DISMISS = getDebugFlag(
- "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
+ "ENABLE_PREDICTION_DISMISS", true, "Allow option to dimiss apps from predicted list");
public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag(
"ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture");
+ public static final BooleanFlag ENABLE_QUICK_CAPTURE_WINDOW = getDebugFlag(
+ "ENABLE_QUICK_CAPTURE_WINDOW", false, "Use window to host quick capture");
+
public static final BooleanFlag FORCE_LOCAL_OVERSCROLL_PLUGIN = getDebugFlag(
"FORCE_LOCAL_OVERSCROLL_PLUGIN", false,
"Use a launcher-provided OverscrollPlugin if available");
@@ -117,30 +123,31 @@
"ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
"Allow Launcher to handle nav bar gestures while Assistant is running over it");
- public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag(
- "ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
+ public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = getDebugFlag(
+ "ENABLE_HYBRID_HOTSEAT", true, "Fill gaps in hotseat with predicted apps");
- public static final BooleanFlag HOTSEAT_MIGRATE_TO_FOLDER = new DeviceFlag(
+ public static final BooleanFlag HOTSEAT_MIGRATE_TO_FOLDER = getDebugFlag(
"HOTSEAT_MIGRATE_TO_FOLDER", false, "Should move hotseat items into a folder");
public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
"ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
public static final BooleanFlag MULTI_DB_GRID_MIRATION_ALGO = getDebugFlag(
- "MULTI_DB_GRID_MIRATION_ALGO", false, "Use the multi-db grid migration algorithm");
+ "MULTI_DB_GRID_MIRATION_ALGO", true, "Use the multi-db grid migration algorithm");
public static final BooleanFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = getDebugFlag(
"ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker");
- public static final BooleanFlag USE_SURFACE_VIEW_FOR_GRID_PREVIEW = getDebugFlag(
- "USE_SURFACE_VIEW_FOR_GRID_PREVIEW", true, "Use surface view for grid preview");
-
public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
"ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
+ " As part of this decoupling, also distinguish swipe up from nav bar vs above it.");
- public static final BooleanFlag ENABLE_SELECT_MODE = getDebugFlag(
- "ENABLE_SELECT_MODE", true, "Show Select Mode button in Overview Actions");
+ // Keep as DeviceFlag for remote disable in emergency.
+ public static final BooleanFlag ENABLE_OVERVIEW_SELECTIONS = new DeviceFlag(
+ "ENABLE_OVERVIEW_SELECTIONS", true, "Show Select Mode button in Overview Actions");
+
+ public static final BooleanFlag ENABLE_OVERVIEW_SHARE = getDebugFlag(
+ "ENABLE_OVERVIEW_SHARE", false, "Show Share button in Overview Actions");
public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag(
"ENABLE_DATABASE_RESTORE", true,
@@ -159,15 +166,23 @@
"ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
"Always use hardware optimization for folder animations.");
- public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag(
- FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true,
- "Launch/close apps without rotation animation. Fix Launcher to portrait");
+ public static final BooleanFlag ENABLE_ALL_APPS_EDU = getDebugFlag(
+ "ENABLE_ALL_APPS_EDU", true,
+ "Shows user a tutorial on how to get to All Apps after X amount of attempts.");
+
+ public static final BooleanFlag SEPARATE_RECENTS_ACTIVITY = getDebugFlag(
+ "SEPARATE_RECENTS_ACTIVITY", false,
+ "Uses a separate recents activity instead of using the integrated recents+Launcher UI");
+
+ public static final BooleanFlag USER_EVENT_DISPATCHER = new DeviceFlag(
+ "USER_EVENT_DISPATCHER", true, "User event dispatcher collects logs.");
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
flag.initialize(context);
}
+ sDebugFlags.sort((f1, f2) -> f1.key.compareToIgnoreCase(f2.key));
}
}
@@ -178,10 +193,20 @@
}
public static void dump(PrintWriter pw) {
- pw.println("FeatureFlags:");
+ pw.println("DeviceFlags:");
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
- pw.println(" " + flag.key + "=" + flag.get());
+ if (flag instanceof DeviceFlag) {
+ pw.println(" " + flag.toString());
+ }
+ }
+ }
+ pw.println("DebugFlags:");
+ synchronized (sDebugFlags) {
+ for (DebugFlag flag : sDebugFlags) {
+ if (!(flag instanceof DeviceFlag)) {
+ pw.println(" " + flag.toString());
+ }
}
}
}
@@ -202,13 +227,11 @@
@Override
public String toString() {
- return appendProps(new StringBuilder()
- .append(getClass().getSimpleName()).append('{'))
- .append('}').toString();
+ return appendProps(new StringBuilder()).toString();
}
protected StringBuilder appendProps(StringBuilder src) {
- return src.append("key=").append(key).append(", defaultValue=").append(defaultValue);
+ return src.append(key).append(", defaultValue=").append(defaultValue);
}
public void addChangeListener(Context context, Runnable r) { }
@@ -240,8 +263,7 @@
@Override
protected StringBuilder appendProps(StringBuilder src) {
- return super.appendProps(src).append(", mCurrentValue=").append(mCurrentValue)
- .append(", description=").append(description);
+ return super.appendProps(src).append(", mCurrentValue=").append(mCurrentValue);
}
}
diff --git a/src/com/android/launcher3/dot/DotInfo.java b/src/com/android/launcher3/dot/DotInfo.java
index 4ff0539..fc180d1 100644
--- a/src/com/android/launcher3/dot/DotInfo.java
+++ b/src/com/android/launcher3/dot/DotInfo.java
@@ -16,6 +16,8 @@
package com.android.launcher3.dot;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationKeyData;
@@ -83,4 +85,10 @@
public int getNotificationCount() {
return Math.min(mTotalCount, MAX_COUNT);
}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return Integer.toString(mTotalCount);
+ }
}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 6c40b8a..0df6713 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -170,16 +170,14 @@
}
}, null, View.DRAG_FLAG_GLOBAL);
-
- Intent homeIntent = listener.addToIntent(
- new Intent(Intent.ACTION_MAIN)
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setPackage(getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-
- Launcher.ACTIVITY_TRACKER.schedule(listener);
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Launcher.ACTIVITY_TRACKER.runCallbackWhenActivityExists(listener, homeIntent);
startActivity(homeIntent,
- ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
+ ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out)
+ .toBundle());
mFinishOnPause = true;
return false;
}
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 9b91a1d..707fd06 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -92,7 +92,7 @@
postCleanup();
return false;
}
- if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
+ if (event.getAction() == DragEvent.ACTION_DRAG_STARTED || !mDragController.isDragging()) {
if (onDragStart(event)) {
return true;
} else {
@@ -161,7 +161,6 @@
}
protected void postCleanup() {
- Launcher.ACTIVITY_TRACKER.clearReference(this);
if (mLauncher != null) {
// Remove any drag params from the launcher intent since the drag operation is complete.
Intent newIntent = new Intent(mLauncher.getIntent());
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index db61f59..03028d3 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -37,11 +37,11 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.TouchController;
@@ -186,8 +186,7 @@
mDragObject.dragSource = source;
mDragObject.dragInfo = dragInfo;
- mDragObject.originalDragInfo = new ItemInfo();
- mDragObject.originalDragInfo.copyFrom(dragInfo);
+ mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
if (dragOffset != null) {
dragView.setDragVisualizeOffset(new Point(dragOffset));
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 970c5a0..ddf44ca 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -274,19 +274,29 @@
scale *= childScale;
int toX = Math.round(coord[0]);
int toY = Math.round(coord[1]);
+
float toScale = scale;
if (child instanceof DraggableView) {
+ // This code is fairly subtle. Please verify drag and drop is pixel-perfect in a number
+ // of scenarios before modifying (from all apps, from workspace, different grid-sizes,
+ // shortcuts from in and out of Launcher etc).
DraggableView d = (DraggableView) child;
- d.getVisualDragBounds(dragViewBounds);
+ Rect destRect = new Rect();
+ d.getWorkspaceVisualDragBounds(destRect);
+
+ // In most cases this additional scale factor should be a no-op (1). It mainly accounts
+ // for alternate grids where the source and destination icon sizes are different
+ toScale *= ((1f * destRect.width())
+ / (dragView.getMeasuredWidth() - dragView.getBlurSizeOutline()));
// This accounts for the offset of the DragView created by scaling it about its
// center as it animates into place.
- float scaleShiftX = dragView.getMeasuredWidth() * (1 - scale) / 2;
- float scaleShiftY = dragView.getMeasuredHeight() * (1 - scale) / 2;
+ float scaleShiftX = dragView.getMeasuredWidth() * (1 - toScale) / 2;
+ float scaleShiftY = dragView.getMeasuredHeight() * (1 - toScale) / 2;
- toX += scale * (dragViewBounds.left - dragView.getBlurSizeOutline() / 2) - scaleShiftX;
- toY += scale * (dragViewBounds.top - dragView.getBlurSizeOutline() / 2) - scaleShiftY;
+ toX += scale * destRect.left - toScale * dragView.getBlurSizeOutline() / 2 - scaleShiftX;
+ toY += scale * destRect.top - toScale * dragView.getBlurSizeOutline() / 2 - scaleShiftY;
}
child.setVisibility(INVISIBLE);
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 7c76d34..de0fa1a 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -46,21 +46,21 @@
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.FirstFrameAnimatorHelper;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import java.util.Arrays;
-public class DragView extends View implements LauncherStateManager.StateListener {
+public class DragView extends View implements StateListener<LauncherState> {
private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
@@ -176,9 +176,6 @@
}
@Override
- public void onStateTransitionStart(LauncherState toState) { }
-
- @Override
public void onStateTransitionComplete(LauncherState finalState) {
setVisibility((finalState == LauncherState.NORMAL
|| finalState == LauncherState.SPRING_LOADED) ? VISIBLE : INVISIBLE);
diff --git a/src/com/android/launcher3/dragndrop/DraggableView.java b/src/com/android/launcher3/dragndrop/DraggableView.java
index df99902..f7dcf6b 100644
--- a/src/com/android/launcher3/dragndrop/DraggableView.java
+++ b/src/com/android/launcher3/dragndrop/DraggableView.java
@@ -18,6 +18,10 @@
import android.graphics.Rect;
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.util.SafeCloseable;
+
/**
* Interface defining methods required for drawing and previewing DragViews, drag previews, and
* related animations
@@ -42,9 +46,12 @@
int getViewType();
/**
- * Before rendering as a DragView bitmap, some views need a preparation step.
+ * Before rendering as a DragView bitmap, some views need a preparation step. Returns a
+ * callback to clear any preparation work
*/
- default void prepareDrawDragView() { }
+ @NonNull default SafeCloseable prepareDrawDragView() {
+ return () -> { };
+ }
/**
* If an actual View subclass, this method returns the rectangle (within the View's coordinates)
@@ -53,5 +60,14 @@
*
* @param bounds Visual bounds in the views coordinates will be written here.
*/
- default void getVisualDragBounds(Rect bounds) { }
+ default void getWorkspaceVisualDragBounds(Rect bounds) { }
+
+ /**
+ * Same as above, but accounts for differing icon sizes between source and destination
+ *
+ * @param bounds Visual bounds in the views coordinates will be written here.
+ */
+ default void getSourceVisualDragBounds(Rect bounds) {
+ getWorkspaceVisualDragBounds(bounds);
+ }
}
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index 77c6306..bf3aa7f 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -30,10 +30,10 @@
import android.widget.RemoteViews;
import com.android.launcher3.DragSource;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index f71cfb8..9982b39 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -35,8 +35,8 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
diff --git a/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java b/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java
index 9f617e4..c501ab5 100644
--- a/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java
+++ b/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java
@@ -25,8 +25,8 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.widget.WidgetAddFlowHandler;
/**
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 202836d..d01e189 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -18,24 +18,14 @@
import static android.text.TextUtils.isEmpty;
-import static androidx.core.util.Preconditions.checkNotNull;
-
-import static com.android.launcher3.FolderInfo.FLAG_MANUAL_FOLDER_NAME;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
-import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
-import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
-import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
-
-import static java.util.Arrays.asList;
-import static java.util.Arrays.stream;
-import static java.util.Optional.ofNullable;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -43,7 +33,6 @@
import android.annotation.SuppressLint;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
-import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Rect;
@@ -65,16 +54,12 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Alarm;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ExtendedEditText;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.FolderInfo.FolderListener;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
@@ -84,7 +69,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.Workspace.ItemOperator;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.config.FeatureFlags;
@@ -92,12 +76,16 @@
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.FolderInfo.FolderListener;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
-import com.android.launcher3.userevent.LauncherLogProto.Action;
-import com.android.launcher3.userevent.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.LauncherLogProto.ItemType;
-import com.android.launcher3.userevent.LauncherLogProto.LauncherEvent;
-import com.android.launcher3.userevent.LauncherLogProto.Target;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Thunk;
@@ -109,9 +97,9 @@
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
-import java.util.Optional;
+import java.util.StringJoiner;
import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+import java.util.stream.Stream;
/**
* Represents a set of icons chosen by the user or generated by the system.
@@ -121,6 +109,12 @@
View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
private static final String TAG = "Launcher.Folder";
private static final boolean DEBUG = false;
+
+ /**
+ * Used for separating folder title when logging together.
+ */
+ private static final CharSequence FOLDER_LABEL_DELIMITER = "~";
+
/**
* We avoid measuring {@link #mContent} with a 0 width or height, as this
* results in CellLayout being measured as UNSPECIFIED, which it does not support.
@@ -162,10 +156,13 @@
@Thunk final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
private AnimatorSet mCurrentAnimator;
+ private boolean mIsAnimatingClosed = false;
protected final Launcher mLauncher;
protected DragController mDragController;
public FolderInfo mInfo;
+ private CharSequence mFromTitle;
+ private FromState mFromLabelState;
@Thunk FolderIcon mFolderIcon;
@@ -209,8 +206,7 @@
@Thunk int mScrollHintDir = SCROLL_NONE;
@Thunk int mCurrentScrollDir = SCROLL_NONE;
- private String mPreviousLabel;
- private boolean mIsPreviousLabelSuggested;
+ private StatsLogManager mStatsLogManager;
/**
* Used to inflate the Workspace from XML.
@@ -223,10 +219,12 @@
setAlwaysDrawnWithCacheEnabled(false);
mLauncher = Launcher.getLauncher(context);
+ mStatsLogManager = StatsLogManager.newInstance(context);
// We need this view to be focusable in touch mode so that when text editing of the folder
// name is complete, we have something to focus on, thus hiding the cursor and giving
// reliable behavior when clicking the text field (since it will always gain focus on click).
setFocusableInTouchMode(true);
+
}
@Override
@@ -325,11 +323,7 @@
public void startEditingFolderName() {
post(() -> {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- ofNullable(mInfo)
- .map(info -> info.suggestedFolderNames)
- .map(folderNames -> (FolderNameInfo[]) folderNames
- .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
- .ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false));
+ showLabelSuggestions();
}
mFolderName.setHint("");
mIsEditingName = true;
@@ -344,12 +338,8 @@
if (DEBUG) {
Log.d(TAG, "onBackKey newTitle=" + newTitle);
}
-
- mInfo.title = newTitle;
- mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, getAcceptedSuggestionIndex() < 0,
- mLauncher.getModelWriter());
+ mInfo.setTitle(newTitle, mLauncher.getModelWriter());
mFolderIcon.onTitleChanged(newTitle);
- mLauncher.getModelWriter().updateItemInDatabase(mInfo);
if (TextUtils.isEmpty(mInfo.title)) {
mFolderName.setHint(R.string.folder_hint_text);
@@ -425,6 +415,8 @@
void bind(FolderInfo info) {
mInfo = info;
+ mFromTitle = info.title;
+ mFromLabelState = info.getFromLabelState();
ArrayList<WorkspaceItemInfo> children = info.contents;
Collections.sort(children, ITEM_POS_COMPARATOR);
updateItemLocationsInDatabaseBatch(true);
@@ -437,8 +429,6 @@
}
mItemsInvalidated = true;
mInfo.addListener(this);
- Optional.ofNullable(mInfo.title).ifPresent(title -> mPreviousLabel = title.toString());
- mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
if (!isEmpty(mInfo.title)) {
mFolderName.setText(mInfo.title);
@@ -455,51 +445,29 @@
});
}
- /**
- * Show suggested folder title in FolderEditText, push InputMethodManager suggestions and save
- * the suggestedFolderNames.
- */
- public void showSuggestedTitle(FolderNameInfo[] nameInfos) {
- if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- if (isEmpty(mFolderName.getText().toString())
- && !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME)) {
- showLabelSuggestion(nameInfos, true);
- }
- }
- }
/**
* Show suggested folder title in FolderEditText if the first suggestion is non-empty, push
- * InputMethodManager suggestions.
+ * rest of the suggestions to InputMethodManager.
*/
- private void showLabelSuggestion(FolderNameInfo[] nameInfos, boolean animate) {
- if (nameInfos == null) {
+ private void showLabelSuggestions() {
+ if (mInfo.suggestedFolderNames == null) {
return;
}
- // Open the Folder and Keyboard when the first or second suggestion is valid non-empty
- // string.
- boolean shouldOpen = nameInfos.length > 0 && nameInfos[0] != null && !isEmpty(
- nameInfos[0].getLabel())
- || nameInfos.length > 1 && nameInfos[1] != null && !isEmpty(
- nameInfos[1].getLabel());
-
- if (shouldOpen) {
+ if (mInfo.suggestedFolderNames.hasSuggestions()) {
// update the primary suggestion if the folder name is empty.
if (isEmpty(mFolderName.getText())) {
- CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
- if (!isEmpty(firstLabel)) {
+ if (mInfo.suggestedFolderNames.hasPrimary()) {
mFolderName.setHint("");
- mFolderName.setText(firstLabel);
+ mFolderName.setText(mInfo.suggestedFolderNames.getLabels()[0]);
+ mFolderName.selectAll();
}
}
- if (animate) {
- animateOpen(mInfo.contents, 0, true);
- }
mFolderName.showKeyboard();
mFolderName.displayCompletions(
- asList(nameInfos).subList(0, nameInfos.length).stream()
+ Stream.of(mInfo.suggestedFolderNames.getLabels())
.filter(Objects::nonNull)
- .map(s -> s.getLabel().toString())
+ .map(Object::toString)
.filter(s -> !s.isEmpty())
.filter(s -> !s.equalsIgnoreCase(mFolderName.getText().toString()))
.collect(Collectors.toList()));
@@ -742,15 +710,24 @@
}
private void animateClosed() {
+ if (mIsAnimatingClosed) {
+ return;
+ }
if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
mCurrentAnimator.cancel();
}
AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator();
a.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ mIsAnimatingClosed = true;
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
closeComplete(true);
announceAccessibilityChanges();
+ mIsAnimatingClosed = false;
}
});
startAnimation(a);
@@ -762,6 +739,11 @@
: getContext().getString(R.string.folder_closed));
}
+ @Override
+ protected View getAccessibilityInitialFocusView() {
+ return mContent.getFirstItem();
+ }
+
private void closeComplete(boolean wasAnimated) {
// TODO: Clear all active animations.
DragLayer parent = (DragLayer) getParent();
@@ -1017,16 +999,14 @@
if (!items.isEmpty()) {
mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
}
- if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind) {
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind
+ && total > 1 /* no need to update if there's one icon */) {
Executors.MODEL_EXECUTOR.post(() -> {
- FolderNameInfo[] nameInfos =
- new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderNameInfos nameInfos = new FolderNameInfos();
FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
fnp.getSuggestedFolderName(
getContext(), mInfo.contents, nameInfos);
- mInfo.suggestedFolderNames = new Intent().putExtra(
- FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
- nameInfos);
+ mInfo.suggestedFolderNames = nameInfos;
});
}
}
@@ -1349,6 +1329,8 @@
if (d.stateAnnouncer != null) {
d.stateAnnouncer.completeAction(R.string.item_moved);
}
+ mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
+ .log(LAUNCHER_ITEM_DROP_COMPLETED);
}
// This is used so the item doesn't immediately appear in the folder when added. In one case
@@ -1451,9 +1433,38 @@
public void onFocusChange(View v, boolean hasFocus) {
if (v == mFolderName) {
if (hasFocus) {
+ mFromLabelState = mInfo.getFromLabelState();
+ mFromTitle = mInfo.title;
startEditingFolderName();
} else {
- logEditFolderLabel();
+ StatsLogger statsLogger = mStatsLogManager.logger()
+ .withItemInfo(mInfo)
+ .withFromState(mFromLabelState);
+
+ // If the folder label is suggested, it is logged to improve prediction model.
+ // When both old and new labels are logged together delimiter is used.
+ StringJoiner labelInfoBuilder = new StringJoiner(FOLDER_LABEL_DELIMITER);
+ if (mFromLabelState.equals(FromState.FROM_SUGGESTED)) {
+ labelInfoBuilder.add(mFromTitle);
+ }
+
+ ToState toLabelState;
+ if (mFromTitle != null && mFromTitle.equals(mInfo.title)) {
+ toLabelState = ToState.UNCHANGED;
+ } else {
+ toLabelState = mInfo.getToLabelState();
+ if (toLabelState.toString().startsWith("TO_SUGGESTION")) {
+ labelInfoBuilder.add(mInfo.title);
+ }
+ }
+ statsLogger.withToState(toLabelState);
+
+ if (labelInfoBuilder.length() > 0) {
+ statsLogger.withEditText(labelInfoBuilder.toString());
+ }
+
+ statsLogger.log(LAUNCHER_FOLDER_LABEL_UPDATED);
+ logFolderLabelState(mFromLabelState, toLabelState);
mFolderName.dispatchBackKey();
}
}
@@ -1652,127 +1663,14 @@
return mContent;
}
- private void logEditFolderLabel() {
- LauncherEvent launcherEvent = LauncherEvent.newBuilder()
- .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD))
- .addSrcTarget(newEditTextTargetBuilder()
- .setFromFolderLabelState(getFromFolderLabelState())
- .setToFolderLabelState(getToFolderLabelState()))
- .addSrcTarget(newFolderTargetBuilder())
- .addSrcTarget(newParentContainerTarget())
- .build();
- mLauncher.getUserEventDispatcher().logLauncherEvent(launcherEvent);
- mPreviousLabel = mFolderName.getText().toString();
- mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
- }
-
- private Target.FromFolderLabelState getFromFolderLabelState() {
- return mPreviousLabel == null
- ? FROM_FOLDER_LABEL_STATE_UNSPECIFIED
- : mPreviousLabel.isEmpty()
- ? FROM_EMPTY
- : mIsPreviousLabelSuggested
- ? FROM_SUGGESTED
- : FROM_CUSTOM;
- }
-
- private Target.ToFolderLabelState getToFolderLabelState() {
- String newLabel =
- checkNotNull(mFolderName.getText().toString(),
- "Expected valid folder label, but found null");
-
- Optional<String[]> suggestedLabels = getSuggestedLabels();
- int accepted_suggestion_index = getAcceptedSuggestionIndex();
- boolean hasValidPrimary = suggestedLabels
- .map(labels -> labels.length > 0 && !isEmpty(labels[0]))
- .orElse(false);
- String primarySuffix = hasValidPrimary
- ? "_WITH_VALID_PRIMARY"
- : "_WITH_EMPTY_PRIMARY";
-
- boolean isEmptySuggestions = suggestedLabels
- .map(labels -> stream(labels).allMatch(TextUtils::isEmpty))
- .orElse(true);
- boolean isSuggestionsEnabled = FeatureFlags.FOLDER_NAME_SUGGEST.get();
- String suggestionsSuffix = isSuggestionsEnabled
- ? isEmptySuggestions
- ? "_WITH_EMPTY_SUGGESTIONS"
- : "_WITH_VALID_SUGGESTIONS"
- : "_WITH_SUGGESTIONS_DISABLED";
-
- return newLabel.equals(mPreviousLabel)
- ? Target.ToFolderLabelState.UNCHANGED
- : newLabel.isEmpty()
- ? Target.ToFolderLabelState.valueOf("TO_EMPTY" + suggestionsSuffix)
- : accepted_suggestion_index >= 0
- ? Target.ToFolderLabelState.valueOf("TO_SUGGESTION"
- + accepted_suggestion_index
- + primarySuffix)
- : Target.ToFolderLabelState.valueOf("TO_CUSTOM" + suggestionsSuffix);
- }
-
- private Optional<String[]> getSuggestedLabels() {
- return ofNullable(mInfo)
- .map(info -> info.suggestedFolderNames)
- .map(
- folderNames ->
- (FolderNameInfo[])
- folderNames.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
- .map(
- folderNameInfoArray ->
- stream(folderNameInfoArray)
- .filter(Objects::nonNull)
- .map(FolderNameInfo::getLabel)
- .filter(Objects::nonNull)
- .map(CharSequence::toString)
- .toArray(String[]::new));
- }
-
- private int getAcceptedSuggestionIndex() {
- String newLabel =
- checkNotNull(mFolderName.getText().toString(),
- "Expected valid folder label, but found null");
-
- return getSuggestedLabels()
- .map(suggestionsArray ->
- IntStream.range(0, suggestionsArray.length)
- .filter(index -> newLabel.equalsIgnoreCase(
- suggestionsArray[index]))
- .findFirst()
- .orElse(-1)
- ).orElse(-1);
-
- }
-
-
- private Target.Builder newEditTextTargetBuilder() {
- return Target.newBuilder().setType(Target.Type.ITEM).setItemType(ItemType.EDITTEXT);
- }
-
- private Target.Builder newFolderTargetBuilder() {
- return Target.newBuilder()
- .setType(Target.Type.CONTAINER)
- .setContainerType(ContainerType.FOLDER)
- .setPageIndex(mInfo.screenId)
- .setGridX(mInfo.cellX)
- .setGridY(mInfo.cellY)
- .setCardinality(mInfo.contents.size());
- }
-
- private Target.Builder newParentContainerTarget() {
- Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER);
-
- switch (mInfo.container) {
- case CONTAINER_HOTSEAT:
- return builder.setContainerType(ContainerType.HOTSEAT);
- case CONTAINER_DESKTOP:
- return builder.setContainerType(ContainerType.WORKSPACE);
- default:
- throw new AssertionError(String
- .format("Expected container to be either %s or %s but found %s.",
- CONTAINER_HOTSEAT,
- CONTAINER_DESKTOP,
- mInfo.container));
- }
+ /**
+ * Logs current folder label info.
+ *
+ * @deprecated This method is only used for log validation and soon will be removed.
+ */
+ @Deprecated
+ public void logFolderLabelState(FromState fromState, ToState toState) {
+ mLauncher.getUserEventDispatcher()
+ .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent(fromState, toState));
}
}
diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java
index 9d14a5f..4be82ed 100644
--- a/src/com/android/launcher3/folder/FolderGridOrganizer.java
+++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java
@@ -20,9 +20,9 @@
import android.graphics.Point;
-import com.android.launcher3.FolderInfo;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index eda9545..75275b2 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -18,12 +18,16 @@
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
@@ -38,22 +42,19 @@
import androidx.annotation.NonNull;
import com.android.launcher3.Alarm;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.FolderInfo.FolderListener;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.R;
+import com.android.launcher3.Reorderable;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.FolderDotInfo;
@@ -62,6 +63,16 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.DotRenderer;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.FolderInfo.FolderListener;
+import com.android.launcher3.model.data.FolderInfo.LabelState;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Thunk;
@@ -73,15 +84,16 @@
import java.util.List;
import java.util.function.Predicate;
+
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
- DraggableView {
+ DraggableView, Reorderable {
@Thunk ActivityContext mActivity;
@Thunk Folder mFolder;
- private FolderInfo mInfo;
+ public FolderInfo mInfo;
private CheckLongPressHelper mLongPressHelper;
@@ -117,6 +129,10 @@
private float mDotScale;
private Animator mDotScaleAnim;
+ private final PointF mTranslationForReorderBounce = new PointF(0, 0);
+ private final PointF mTranslationForReorderPreview = new PointF(0, 0);
+ private float mScaleForReorderBounce = 1f;
+
private static final Property<FolderIcon, Float> DOT_SCALE_PROPERTY
= new Property<FolderIcon, Float>(Float.TYPE, "dotScale") {
@Override
@@ -189,8 +205,7 @@
icon.mActivity = activity;
icon.mDotRenderer = grid.mDotRendererWorkSpace;
- icon.setContentDescription(
- group.getContext().getString(R.string.folder_name_format, folderInfo.title));
+ icon.setContentDescription(icon.getAccessiblityTitle(folderInfo.title));
// Keep the notification dot up to date with the sum of all the content's dots.
FolderDotInfo folderDotInfo = new FolderDotInfo();
@@ -224,16 +239,6 @@
mBackground.getBounds(outBounds);
}
- @Override
- public int getViewType() {
- return DRAGGABLE_ICON;
- }
-
- @Override
- public void getVisualDragBounds(Rect bounds) {
- getPreviewBounds(bounds);
- }
-
public float getBackgroundStrokeWidth() {
return mBackground.getStrokeWidth();
}
@@ -387,6 +392,14 @@
float finalAlpha = index < MAX_NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
float finalScale = scale * scaleRelativeToDragLayer;
+
+ // Account for potentially different icon sizes with non-default grid settings
+ if (d.dragSource instanceof AllAppsContainerView) {
+ DeviceProfile grid = mActivity.getDeviceProfile();
+ float containerScale = (1f * grid.iconSizePx / grid.allAppsIconSizePx);
+ finalScale *= containerScale;
+ }
+
dragLayer.animateView(animateView, from, to, finalAlpha,
1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
Interpolators.DEACCEL_2, Interpolators.ACCEL_2,
@@ -397,16 +410,15 @@
if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
final int finalIndex = index;
- FolderNameInfo[] nameInfos =
- new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderNameInfos nameInfos = new FolderNameInfos();
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- Executors.UI_HELPER_EXECUTOR.post(() -> {
+ Executors.MODEL_EXECUTOR.post(() -> {
d.folderNameProvider.getSuggestedFolderName(
getContext(), mInfo.contents, nameInfos);
- showFinalView(finalIndex, item, nameInfos);
+ showFinalView(finalIndex, item, nameInfos, d.logInstanceId);
});
} else {
- showFinalView(finalIndex, item, nameInfos);
+ showFinalView(finalIndex, item, nameInfos, d.logInstanceId);
}
} else {
addItem(item);
@@ -414,15 +426,60 @@
}
private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
- FolderNameInfo[] nameInfos) {
+ FolderNameInfos nameInfos, InstanceId instanceId) {
postDelayed(() -> {
mPreviewItemManager.hidePreviewItem(finalIndex, false);
mFolder.showItem(item);
+ setLabelSuggestion(nameInfos, instanceId);
invalidate();
- mFolder.showSuggestedTitle(nameInfos);
}, DROP_IN_ANIMATION_DURATION);
}
+ /**
+ * Set the suggested folder name.
+ */
+ public void setLabelSuggestion(FolderNameInfos nameInfos, InstanceId instanceId) {
+ if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ return;
+ }
+ if (!mInfo.getLabelState().equals(LabelState.UNLABELED)) {
+ return;
+ }
+ if (nameInfos == null || !nameInfos.hasSuggestions()) {
+ StatsLogManager.newInstance(getContext()).logger()
+ .withInstanceId(instanceId)
+ .withItemInfo(mInfo)
+ .log(LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS);
+ return;
+ }
+ if (!nameInfos.hasPrimary()) {
+ StatsLogManager.newInstance(getContext()).logger()
+ .withInstanceId(instanceId)
+ .withItemInfo(mInfo)
+ .log(LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY);
+ return;
+ }
+ CharSequence newTitle = nameInfos.getLabels()[0];
+ FromState fromState = mInfo.getFromLabelState();
+
+ mInfo.setTitle(newTitle, mFolder.mLauncher.getModelWriter());
+ onTitleChanged(mInfo.title);
+ mFolder.mFolderName.setText(mInfo.title);
+
+ // Logging for folder creation flow
+ StatsLogManager.newInstance(getContext()).logger()
+ .withInstanceId(instanceId)
+ .withItemInfo(mInfo)
+ .withFromState(fromState)
+ .withToState(ToState.TO_SUGGESTION0)
+ // When LAUNCHER_FOLDER_LABEL_UPDATED event.edit_text does not have delimiter,
+ // event is assumed to be folder creation on the server side.
+ .withEditText(newTitle.toString())
+ .log(LAUNCHER_FOLDER_AUTO_LABELED);
+ mFolder.logFolderLabelState(fromState, ToState.TO_SUGGESTION0);
+ }
+
+
public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
WorkspaceItemInfo item;
if (d.dragInfo instanceof AppInfo) {
@@ -631,6 +688,7 @@
mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
boolean isDotted = mDotInfo.hasDot();
updateDotScale(wasDotted, isDotted);
+ setContentDescription(getAccessiblityTitle(mInfo.title));
invalidate();
requestLayout();
}
@@ -641,13 +699,14 @@
mDotInfo.subtractDotInfo(mActivity.getDotInfoForItem(item));
boolean isDotted = mDotInfo.hasDot();
updateDotScale(wasDotted, isDotted);
+ setContentDescription(getAccessiblityTitle(mInfo.title));
invalidate();
requestLayout();
}
public void onTitleChanged(CharSequence title) {
mFolderName.setText(title);
- setContentDescription(getContext().getString(R.string.folder_name_format, title));
+ setContentDescription(getAccessiblityTitle(title));
}
@Override
@@ -692,4 +751,66 @@
public void onFolderClose(int currentPage) {
mPreviewItemManager.onFolderClose(currentPage);
}
+
+ private void updateTranslation() {
+ super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
+ super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
+ }
+
+ public void setReorderBounceOffset(float x, float y) {
+ mTranslationForReorderBounce.set(x, y);
+ updateTranslation();
+ }
+
+ public void getReorderBounceOffset(PointF offset) {
+ offset.set(mTranslationForReorderBounce);
+ }
+
+ @Override
+ public void setReorderPreviewOffset(float x, float y) {
+ mTranslationForReorderPreview.set(x, y);
+ updateTranslation();
+ }
+
+ @Override
+ public void getReorderPreviewOffset(PointF offset) {
+ offset.set(mTranslationForReorderPreview);
+ }
+
+ public void setReorderBounceScale(float scale) {
+ mScaleForReorderBounce = scale;
+ super.setScaleX(scale);
+ super.setScaleY(scale);
+ }
+
+ public float getReorderBounceScale() {
+ return mScaleForReorderBounce;
+ }
+
+ public View getView() {
+ return this;
+ }
+
+ @Override
+ public int getViewType() {
+ return DRAGGABLE_ICON;
+ }
+
+ @Override
+ public void getWorkspaceVisualDragBounds(Rect bounds) {
+ getPreviewBounds(bounds);
+ }
+
+ /**
+ * Returns a formatted accessibility title for folder
+ */
+ public String getAccessiblityTitle(CharSequence title) {
+ int size = mInfo.contents.size();
+ if (size < MAX_NUM_ITEMS_IN_PREVIEW) {
+ return getContext().getString(R.string.folder_name_format_exact, title, size);
+ } else {
+ return getContext().getString(R.string.folder_name_format_overflow, title,
+ MAX_NUM_ITEMS_IN_PREVIEW);
+ }
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderNameEditText.java b/src/com/android/launcher3/folder/FolderNameEditText.java
index edf2c70..6038a05 100644
--- a/src/com/android/launcher3/folder/FolderNameEditText.java
+++ b/src/com/android/launcher3/folder/FolderNameEditText.java
@@ -70,8 +70,11 @@
for (int i = 0; i < cnt; i++) {
cInfo[i] = new CompletionInfo(i, i, suggestList.get(i));
}
- post(() -> getContext().getSystemService(InputMethodManager.class)
- .displayCompletions(this, cInfo));
+ // post it to future frame so that onSelectionChanged, onFocusChanged, all other
+ // TextView flag change and IME animation has settled. Ideally, there should be IMM
+ // callback to notify when the IME animation and state handling is finished.
+ postDelayed(() -> getContext().getSystemService(InputMethodManager.class)
+ .displayCompletions(this, cInfo), 40 /* 2~3 frame delay */);
}
/**
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
deleted file mode 100644
index 1287219..0000000
--- a/src/com/android/launcher3/folder/FolderNameInfo.java
+++ /dev/null
@@ -1,89 +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.folder;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-
-/**
- * Information about a single label suggestions of the Folder.
- */
-
-public final class FolderNameInfo implements Parcelable {
- private final double mScore;
- private final CharSequence mLabel;
-
- /**
- * Create a simple completion with label.
- *
- * @param label The text that should be inserted into the editor and pushed to
- * InputMethodManager suggestions.
- * @param score The score for the label between 0.0 and 1.0.
- */
- public FolderNameInfo(CharSequence label, double score) {
- mScore = score;
- mLabel = label;
- }
-
- private FolderNameInfo(Parcel source) {
- mScore = source.readDouble();
- mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- }
-
- public CharSequence getLabel() {
- return mLabel;
- }
-
- /**
- * Used to package this object into a {@link Parcel}.
- *
- * @param dest The {@link Parcel} to be written.
- * @param flags The flags used for parceling.
- */
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeDouble(mScore);
- TextUtils.writeToParcel(mLabel, dest, flags);
- }
-
- /**
- * Used to make this class parcelable.
- */
- @NonNull
- public static final Parcelable.Creator<FolderNameInfo> CREATOR =
- new Parcelable.Creator<FolderNameInfo>() {
- public FolderNameInfo createFromParcel(Parcel source) {
- return new FolderNameInfo(source);
- }
-
- public FolderNameInfo[] newArray(int size) {
- return new FolderNameInfo[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- @NonNull
- public String toString() {
- return String.format("%s:%.2f", mLabel, mScore);
- }
-}
diff --git a/src/com/android/launcher3/folder/FolderNameInfos.java b/src/com/android/launcher3/folder/FolderNameInfos.java
new file mode 100644
index 0000000..457ae87
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameInfos.java
@@ -0,0 +1,117 @@
+/*
+ * 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.folder;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Information about a label suggestions of a Folder.
+ */
+
+public class FolderNameInfos {
+ public static final int SUCCESS = 1;
+ public static final int HAS_PRIMARY = 1 << 1;
+ public static final int HAS_SUGGESTIONS = 1 << 2;
+ public static final int ERROR_NO_PROVIDER = 1 << 3;
+ public static final int ERROR_APP_LOOKUP_FAILED = 1 << 4;
+ public static final int ERROR_ALL_APP_LOOKUP_FAILED = 1 << 5;
+ public static final int ERROR_NO_LABELS_GENERATED = 1 << 6;
+ public static final int ERROR_LABEL_LOOKUP_FAILED = 1 << 7;
+ public static final int ERROR_ALL_LABEL_LOOKUP_FAILED = 1 << 8;
+ public static final int ERROR_NO_PACKAGES = 1 << 9;
+
+ private int mStatus;
+ private final CharSequence[] mLabels;
+ private final Float[] mScores;
+
+ public FolderNameInfos() {
+ mStatus = 0;
+ mLabels = new CharSequence[FolderNameProvider.SUGGEST_MAX];
+ mScores = new Float[FolderNameProvider.SUGGEST_MAX];
+ }
+
+ /**
+ * set the status of FolderNameInfos.
+ */
+ public void setStatus(int statusBit) {
+ mStatus = mStatus | statusBit;
+ }
+
+ /**
+ * returns status of FolderNameInfos generations.
+ */
+ public int status() {
+ return mStatus;
+ }
+
+ /**
+ * return true if the first suggestion is a Primary suggestion.
+ */
+ public boolean hasPrimary() {
+ return (mStatus & HAS_PRIMARY) > 0 && (mLabels[0] != null);
+ }
+
+ /**
+ * return true if there is at least one valid suggestion.
+ */
+ public boolean hasSuggestions() {
+ for (CharSequence l : mLabels) {
+ if (l != null && !TextUtils.isEmpty(l)) return true;
+ }
+ return false;
+ }
+
+ /**
+ * assign label and score in the specified index.
+ */
+ public void setLabel(int index, CharSequence label, Float score) {
+ if (index < mLabels.length) {
+ mLabels[index] = label;
+ mScores[index] = score;
+ }
+ }
+
+ /**
+ * returns true if the label is found in label suggestions/
+ */
+ public boolean contains(CharSequence label) {
+ return Arrays.stream(mLabels)
+ .filter(Objects::nonNull)
+ .anyMatch(l -> l.toString().equalsIgnoreCase(label.toString()));
+ }
+
+
+ public CharSequence[] getLabels() {
+ return mLabels;
+ }
+
+ public Float[] getScores() {
+ return mScores;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return String.format("status=%s, labels=%s", Integer.toBinaryString(mStatus),
+ Arrays.toString(mLabels));
+ }
+}
+
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 07161da..d166e27 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -22,15 +22,16 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.FolderInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.ResourceBasedOverride;
import java.util.ArrayList;
@@ -64,6 +65,7 @@
public static FolderNameProvider newInstance(Context context) {
FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
context.getApplicationContext(), R.string.folder_name_provider_class);
+ Preconditions.assertWorkerThread();
fnp.load(context);
return fnp;
@@ -71,6 +73,7 @@
public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
IntSparseArrayMap<FolderInfo> folderInfos) {
+ Preconditions.assertWorkerThread();
FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
context.getApplicationContext(), R.string.folder_name_provider_class);
fnp.load(appInfos, folderInfos);
@@ -93,10 +96,10 @@
*/
public void getSuggestedFolderName(Context context,
ArrayList<WorkspaceItemInfo> workspaceItemInfos,
- FolderNameInfo[] nameInfos) {
-
+ FolderNameInfos nameInfos) {
+ Preconditions.assertWorkerThread();
if (DEBUG) {
- Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
+ Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
}
// If all the icons are from work profile,
// Then, suggest "Work" as the folder name
@@ -121,7 +124,7 @@
info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
}
if (DEBUG) {
- Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
+ Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
}
}
@@ -135,39 +138,37 @@
.findAny();
}
- private void setAsFirstSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
- if (nameInfos.length == 0 || contains(nameInfos, label)) {
+ private void setAsFirstSuggestion(FolderNameInfos nameInfos, CharSequence label) {
+ if (nameInfos == null || nameInfos.contains(label)) {
return;
}
- for (int i = nameInfos.length - 1; i > 0; i--) {
- if (nameInfos[i - 1] != null && !TextUtils.isEmpty(nameInfos[i - 1].getLabel())) {
- nameInfos[i] = nameInfos[i - 1];
+ nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY);
+ nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS);
+ CharSequence[] labels = nameInfos.getLabels();
+ Float[] scores = nameInfos.getScores();
+ for (int i = labels.length - 1; i > 0; i--) {
+ if (labels[i - 1] != null && !TextUtils.isEmpty(labels[i - 1])) {
+ nameInfos.setLabel(i, labels[i - 1], scores[i - 1]);
}
}
- nameInfos[0] = new FolderNameInfo(label, 1.0);
+ nameInfos.setLabel(0, label, 1.0f);
}
- private void setAsLastSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
- if (nameInfos.length == 0 || contains(nameInfos, label)) {
+ private void setAsLastSuggestion(FolderNameInfos nameInfos, CharSequence label) {
+ if (nameInfos == null || nameInfos.contains(label)) {
return;
}
-
- for (int i = 0; i < nameInfos.length; i++) {
- if (nameInfos[i] == null || TextUtils.isEmpty(nameInfos[i].getLabel())) {
- nameInfos[i] = new FolderNameInfo(label, 1.0);
+ nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY);
+ nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS);
+ CharSequence[] labels = nameInfos.getLabels();
+ for (int i = 0; i < labels.length; i++) {
+ if (labels[i] == null || TextUtils.isEmpty(labels[i])) {
+ nameInfos.setLabel(i, label, 1.0f);
return;
}
}
// Overwrite the last suggestion.
- int lastIndex = nameInfos.length - 1;
- nameInfos[lastIndex] = new FolderNameInfo(label, 1.0);
- }
-
- private boolean contains(FolderNameInfo[] nameInfos, CharSequence label) {
- return Arrays.stream(nameInfos)
- .filter(Objects::nonNull)
- .anyMatch(nameInfo -> nameInfo.getLabel().toString().equalsIgnoreCase(
- label.toString()));
+ nameInfos.setLabel(labels.length - 1, label, 1.0f);
}
private class FolderNameWorker extends BaseModelUpdateTask {
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index dcd0e14..32531c0 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -36,7 +36,6 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.PagedView;
@@ -44,9 +43,10 @@
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace.ItemOperator;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Thunk;
@@ -153,6 +153,7 @@
CellLayout page = (CellLayout) getChildAt(i);
ShortcutAndWidgetContainer container = page.getShortcutsAndWidgets();
for (int j = container.getChildCount() - 1; j >= 0; j--) {
+ container.getChildAt(j).setVisibility(View.VISIBLE);
mViewCache.recycleView(R.layout.folder_application, container.getChildAt(j));
}
page.removeAllViews();
diff --git a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
index caf6e55..a14a0d8 100644
--- a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
+++ b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
@@ -17,7 +17,7 @@
import android.graphics.drawable.Drawable;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
/**
* Manages the parameters used to draw a Folder preview item.
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 27aa43e..7f8a15c 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -38,8 +38,8 @@
import androidx.annotation.NonNull;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index ed9dfbb..21822a3 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -35,6 +35,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import java.nio.ByteBuffer;
@@ -76,11 +77,12 @@
if (mView instanceof DraggableView) {
DraggableView dv = (DraggableView) mView;
- dv.prepareDrawDragView();
- dv.getVisualDragBounds(mTempRect);
- destCanvas.translate(blurSizeOutline / 2 - mTempRect.left,
- blurSizeOutline / 2 - mTempRect.top);
- mView.draw(destCanvas);
+ try (SafeCloseable t = dv.prepareDrawDragView()) {
+ dv.getSourceVisualDragBounds(mTempRect);
+ destCanvas.translate(blurSizeOutline / 2 - mTempRect.left,
+ blurSizeOutline / 2 - mTempRect.top);
+ mView.draw(destCanvas);
+ }
}
destCanvas.restoreToCount(saveCount);
}
@@ -92,8 +94,10 @@
public Bitmap createDragBitmap() {
int width = 0;
int height = 0;
+ // Assume scaleX == scaleY, which is always the case for workspace items.
+ float scale = mView.getScaleX();
if (mView instanceof DraggableView) {
- ((DraggableView) mView).getVisualDragBounds(mTempRect);
+ ((DraggableView) mView).getSourceVisualDragBounds(mTempRect);
width = mTempRect.width();
height = mTempRect.height();
} else {
@@ -102,7 +106,7 @@
}
return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
- height + blurSizeOutline, (c) -> drawDragView(c, 1));
+ height + blurSizeOutline, (c) -> drawDragView(c, scale));
}
public final void generateDragOutline(Bitmap preview) {
diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java
index 607aba9..08d7e4c 100644
--- a/src/com/android/launcher3/graphics/GridOptionsProvider.java
+++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java
@@ -1,36 +1,28 @@
package com.android.launcher3.graphics;
-import static com.android.launcher3.config.FeatureFlags.USE_SURFACE_VIEW_FOR_GRID_PREVIEW;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
import android.content.ContentProvider;
import android.content.ContentValues;
+import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.MatrixCursor;
-import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
-import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.GridOption;
import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.PreviewSurfaceRenderer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Future;
/**
* Exposes various launcher grid options and allows the caller to change them.
@@ -61,26 +53,7 @@
private static final String KEY_LIST_OPTIONS = "/list_options";
private static final String KEY_DEFAULT_GRID = "/default_grid";
- private static final String KEY_PREVIEW = "preview";
- private static final String MIME_TYPE_PNG = "image/png";
-
private static final String METHOD_GET_PREVIEW = "get_preview";
- private static final String METADATA_KEY_PREVIEW_VERSION = "preview_version";
-
-
-
- public static final PipeDataWriter<Future<Bitmap>> BITMAP_WRITER =
- new PipeDataWriter<Future<Bitmap>>() {
- @Override
- public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String s,
- Bundle bundle, Future<Bitmap> bitmap) {
- try (AutoCloseOutputStream os = new AutoCloseOutputStream(output)) {
- bitmap.get().compress(Bitmap.CompressFormat.PNG, 100, os);
- } catch (Exception e) {
- Log.w(TAG, "fail to write to pipe", e);
- }
- }
- };
@Override
public boolean onCreate() {
@@ -105,10 +78,6 @@
.add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
&& idp.numRows == gridOption.numRows);
}
- Bundle metadata = new Bundle();
- metadata.putString(METADATA_KEY_PREVIEW_VERSION,
- USE_SURFACE_VIEW_FOR_GRID_PREVIEW.get() ? "V2" : "V1");
- cursor.setExtras(metadata);
return cursor;
}
@@ -133,10 +102,6 @@
@Override
public String getType(Uri uri) {
- List<String> segments = uri.getPathSegments();
- if (segments.size() > 0 && KEY_PREVIEW.equals(segments.get(0))) {
- return MIME_TYPE_PNG;
- }
return "vnd.android.cursor.dir/launcher_grid";
}
@@ -174,39 +139,17 @@
}
@Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- List<String> segments = uri.getPathSegments();
- if (segments.size() < 2 || !KEY_PREVIEW.equals(segments.get(0))) {
- throw new FileNotFoundException("Invalid preview url");
- }
- String profileName = segments.get(1);
- if (TextUtils.isEmpty(profileName)) {
- throw new FileNotFoundException("Invalid preview url");
+ public Bundle call(String method, String arg, Bundle extras) {
+ if (getContext().checkPermission("android.permission.BIND_WALLPAPER",
+ Binder.getCallingPid(), Binder.getCallingUid())
+ != PackageManager.PERMISSION_GRANTED) {
+ return null;
}
- InvariantDeviceProfile idp;
- try {
- idp = new InvariantDeviceProfile(getContext(), profileName);
- } catch (Exception e) {
- throw new FileNotFoundException(e.getMessage());
- }
-
- try {
- return openPipeHelper(uri, MIME_TYPE_PNG, null,
- UI_HELPER_EXECUTOR.submit(new LauncherPreviewRenderer(getContext(), idp)),
- BITMAP_WRITER);
- } catch (Exception e) {
- throw new FileNotFoundException(e.getMessage());
- }
- }
-
- @Override
- public Bundle call(String method, String arg, Bundle extras) {
if (!METHOD_GET_PREVIEW.equals(method)) {
return null;
}
- PreviewSurfaceRenderer.render(getContext(), extras);
- return null;
+ return new PreviewSurfaceRenderer(getContext(), extras).render();
}
}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 7d4eb0e..885fb66 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -20,8 +20,8 @@
import static android.view.View.VISIBLE;
import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
-import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -33,8 +33,6 @@
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
@@ -54,39 +52,38 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.FolderInfo;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.WorkspaceLayoutManager;
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.GridSizeMigrationTask;
-import com.android.launcher3.model.GridSizeMigrationTaskV2;
import com.android.launcher3.model.LoaderResults;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.uioverrides.PredictedAppIconInflater;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -101,7 +98,6 @@
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
@@ -117,7 +113,7 @@
* 4) Measure and draw the view on a canvas
*/
@TargetApi(Build.VERSION_CODES.O)
-public class LauncherPreviewRenderer implements Callable<Bitmap> {
+public class LauncherPreviewRenderer {
private static final String TAG = "LauncherPreviewRenderer";
@@ -209,15 +205,17 @@
private final Context mContext;
private final InvariantDeviceProfile mIdp;
private final DeviceProfile mDp;
+ private final boolean mMigrated;
private final Rect mInsets;
private final WorkspaceItemInfo mWorkspaceItemInfo;
- public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) {
+ public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) {
mUiHandler = new Handler(Looper.getMainLooper());
mContext = context;
mIdp = idp;
mDp = idp.portraitProfile.copy(context);
+ mMigrated = migrated;
// TODO: get correct insets once display cutout API is available.
mInsets = new Rect();
@@ -239,28 +237,6 @@
context.getString(R.string.label_application);
}
- @Override
- public Bitmap call() {
- return BitmapRenderer.createHardwareBitmap(mDp.widthPx, mDp.heightPx, c -> {
-
- if (Looper.myLooper() == Looper.getMainLooper()) {
- new MainThreadRenderer(mContext).renderScreenShot(c);
- } else {
- CountDownLatch latch = new CountDownLatch(1);
- Utilities.postAsyncCallback(mUiHandler, () -> {
- new MainThreadRenderer(mContext).renderScreenShot(c);
- latch.countDown();
- });
-
- try {
- latch.await();
- } catch (Exception e) {
- Log.e(TAG, "Error drawing on main thread", e);
- }
- }
- });
- }
-
/** Populate preview and render it. */
public View getRenderedView() {
MainThreadRenderer renderer = new MainThreadRenderer(mContext);
@@ -376,6 +352,13 @@
addInScreenFromBind(view, info);
}
+ private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
+ View view = PredictedAppIconInflater.inflate(mHomeElementInflater, mWorkspace, info);
+ if (view != null) {
+ addInScreenFromBind(view, info);
+ }
+ }
+
private void dispatchVisibilityAggregated(View view, boolean isVisible) {
// Similar to View.dispatchVisibilityAggregated implementation.
final boolean thisVisible = view.getVisibility() == VISIBLE;
@@ -396,20 +379,9 @@
private void populate() {
if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
- boolean needsToMigrate =
- MULTI_DB_GRID_MIRATION_ALGO.get()
- ? GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)
- : GridSizeMigrationTask.needsToMigrate(mContext, mIdp);
- boolean success = false;
- if (needsToMigrate) {
- success = MULTI_DB_GRID_MIRATION_ALGO.get()
- ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp)
- : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp);
- }
-
WorkspaceFetcher fetcher;
PreviewContext previewContext = null;
- if (needsToMigrate && success) {
+ if (mMigrated) {
previewContext = new PreviewContext(mContext, mIdp);
LauncherAppState appForPreview = new LauncherAppState(
previewContext, null /* iconCacheFileName */);
@@ -468,6 +440,21 @@
break;
}
}
+
+ IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
+ mIdp.numHotseatIcons);
+ int count = Math.min(ranks.size(), workspaceResult.mCachedPredictedItems.size());
+ for (int i = 0; i < count; i++) {
+ AppInfo appInfo = workspaceResult.mCachedPredictedItems.get(i);
+ int rank = ranks.get(i);
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(appInfo);
+ itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ itemInfo.rank = rank;
+ itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
+ itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
+ itemInfo.screenId = rank;
+ inflateAndAddPredictedIcon(itemInfo);
+ }
} else {
// Add hotseat icons
for (int i = 0; i < mIdp.numHotseatIcons; i++) {
@@ -509,12 +496,6 @@
// Additional measure for views which use auto text size API
measureView(mRootView, mDp.widthPx, mDp.heightPx);
}
-
- private void renderScreenShot(Canvas canvas) {
- populate();
- mRootView.draw(canvas);
- dispatchVisibilityAggregated(mRootView, false);
- }
}
private static void measureView(View view, int width, int height) {
@@ -561,7 +542,7 @@
}
return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
- mBgDataModel.widgetsModel);
+ mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel);
}
}
@@ -590,7 +571,7 @@
loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
mBgDataModel.widgetsModel.update(mApp, null);
return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
- mBgDataModel.widgetsModel);
+ mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel);
}
}
@@ -610,12 +591,15 @@
private static class WorkspaceResult {
private final ArrayList<ItemInfo> mWorkspaceItems;
private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
+ private final ArrayList<AppInfo> mCachedPredictedItems;
private final WidgetsModel mWidgetsModel;
private WorkspaceResult(ArrayList<ItemInfo> workspaceItems,
- ArrayList<LauncherAppWidgetInfo> appWidgets, WidgetsModel widgetsModel) {
+ ArrayList<LauncherAppWidgetInfo> appWidgets,
+ ArrayList<AppInfo> cachedPredictedItems, WidgetsModel widgetsModel) {
mWorkspaceItems = workspaceItems;
mAppWidgets = appWidgets;
+ mCachedPredictedItems = cachedPredictedItems;
mWidgetsModel = widgetsModel;
}
}
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index b0e1db1..e85b056 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -31,12 +31,13 @@
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Rect;
+import android.util.Pair;
import android.util.Property;
import android.util.SparseArray;
import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import java.lang.ref.WeakReference;
@@ -73,7 +74,8 @@
private static final float SMALL_SCALE = 0.6f;
- private static final SparseArray<WeakReference<Bitmap>> sShadowCache = new SparseArray<>();
+ private static final SparseArray<WeakReference<Pair<Path, Bitmap>>> sShadowCache =
+ new SparseArray<>();
private final Matrix mTmpMatrix = new Matrix();
private final PathMeasure mPathMeasure = new PathMeasure();
@@ -81,7 +83,7 @@
private final ItemInfoWithIcon mItem;
// Path in [0, 100] bounds.
- private final Path mProgressPath;
+ private final Path mShapePath;
private final Path mScaledTrackPath;
private final Path mScaledProgressPath;
@@ -105,7 +107,7 @@
public PreloadIconDrawable(ItemInfoWithIcon info, Context context) {
super(info.bitmap);
mItem = info;
- mProgressPath = getShapePath();
+ mShapePath = getShapePath();
mScaledTrackPath = new Path();
mScaledProgressPath = new Path();
@@ -127,7 +129,7 @@
bounds.left + PROGRESS_WIDTH + PROGRESS_GAP,
bounds.top + PROGRESS_WIDTH + PROGRESS_GAP);
- mProgressPath.transform(mTmpMatrix, mScaledTrackPath);
+ mShapePath.transform(mTmpMatrix, mScaledTrackPath);
float scale = bounds.width() / DEFAULT_PATH_SIZE;
mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale);
@@ -141,8 +143,9 @@
private Bitmap getShadowBitmap(int width, int height, float shadowRadius) {
int key = (width << 16) | height;
- WeakReference<Bitmap> shadowRef = sShadowCache.get(key);
- Bitmap shadow = shadowRef != null ? shadowRef.get() : null;
+ WeakReference<Pair<Path, Bitmap>> shadowRef = sShadowCache.get(key);
+ Pair<Path, Bitmap> cache = shadowRef != null ? shadowRef.get() : null;
+ Bitmap shadow = cache != null && cache.first.equals(mShapePath) ? cache.second : null;
if (shadow != null) {
return shadow;
}
@@ -155,7 +158,7 @@
mProgressPaint.clearShadowLayer();
c.setBitmap(null);
- sShadowCache.put(key, new WeakReference<>(shadow));
+ sShadowCache.put(key, new WeakReference<>(Pair.create(mShapePath, shadow)));
return shadow;
}
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
new file mode 100644
index 0000000..fdc3a94
--- /dev/null
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -0,0 +1,168 @@
+/*
+ * 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.graphics;
+
+import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.view.Display;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.GridSizeMigrationTaskV2;
+
+import java.util.concurrent.TimeUnit;
+
+/** Render preview using surface view. */
+public class PreviewSurfaceRenderer implements IBinder.DeathRecipient {
+
+ private static final int FADE_IN_ANIMATION_DURATION = 200;
+
+ private static final String KEY_HOST_TOKEN = "host_token";
+ private static final String KEY_VIEW_WIDTH = "width";
+ private static final String KEY_VIEW_HEIGHT = "height";
+ private static final String KEY_DISPLAY_ID = "display_id";
+ private static final String KEY_SURFACE_PACKAGE = "surface_package";
+ private static final String KEY_CALLBACK = "callback";
+
+ private final Context mContext;
+ private final InvariantDeviceProfile mIdp;
+ private final IBinder mHostToken;
+ private final int mWidth;
+ private final int mHeight;
+ private final Display mDisplay;
+
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+
+ PreviewSurfaceRenderer(Context context, Bundle bundle) {
+ mContext = context;
+
+ String gridName = bundle.getString("name");
+ bundle.remove("name");
+ if (gridName == null) {
+ gridName = InvariantDeviceProfile.getCurrentGridName(context);
+ }
+ mIdp = new InvariantDeviceProfile(context, gridName);
+
+ mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
+ mWidth = bundle.getInt(KEY_VIEW_WIDTH);
+ mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
+
+ final DisplayManager displayManager = (DisplayManager) context.getSystemService(
+ Context.DISPLAY_SERVICE);
+ mDisplay = displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID));
+ }
+
+ /** Handle a received surface view request. */
+ Bundle render() {
+ if (mSurfaceControlViewHost != null) {
+ binderDied();
+ }
+
+ SurfaceControlViewHost.SurfacePackage surfacePackage;
+ try {
+ mSurfaceControlViewHost = MAIN_EXECUTOR
+ .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))
+ .get(5, TimeUnit.SECONDS);
+ surfacePackage = mSurfaceControlViewHost.getSurfacePackage();
+ mHostToken.linkToDeath(this, 0);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ MODEL_EXECUTOR.post(() -> {
+ final boolean success = doGridMigrationIfNecessary();
+
+ MAIN_EXECUTOR.post(() -> {
+ // If mSurfaceControlViewHost is null due to any reason (e.g. binder died,
+ // happening when user leaves the preview screen before preview rendering finishes),
+ // we should return here.
+ SurfaceControlViewHost host = mSurfaceControlViewHost;
+ if (host == null) {
+ return;
+ }
+
+ View view = new LauncherPreviewRenderer(mContext, mIdp, success).getRenderedView();
+ // This aspect scales the view to fit in the surface and centers it
+ final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
+ mHeight / (float) view.getMeasuredHeight());
+ view.setScaleX(scale);
+ view.setScaleY(scale);
+ view.setPivotX(0);
+ view.setPivotY(0);
+ view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
+ view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
+ view.setAlpha(0);
+ view.animate().alpha(1)
+ .setInterpolator(new AccelerateDecelerateInterpolator())
+ .setDuration(FADE_IN_ANIMATION_DURATION)
+ .start();
+ host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());
+ });
+ });
+
+ Bundle result = new Bundle();
+ result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage);
+
+ Handler handler = new Handler(Looper.getMainLooper(), message -> {
+ binderDied();
+ return true;
+ });
+ Messenger messenger = new Messenger(handler);
+ Message msg = Message.obtain();
+ msg.replyTo = messenger;
+ result.putParcelable(KEY_CALLBACK, msg);
+ return result;
+ }
+
+ @Override
+ public void binderDied() {
+ if (mSurfaceControlViewHost != null) {
+ MAIN_EXECUTOR.execute(() -> {
+ mSurfaceControlViewHost.release();
+ mSurfaceControlViewHost = null;
+ });
+ }
+ mHostToken.unlinkToDeath(this, 0);
+ }
+
+ private boolean doGridMigrationIfNecessary() {
+ boolean needsToMigrate =
+ MULTI_DB_GRID_MIRATION_ALGO.get()
+ ? GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)
+ : GridSizeMigrationTask.needsToMigrate(mContext, mIdp);
+ if (!needsToMigrate) {
+ return false;
+ }
+ return MULTI_DB_GRID_MIRATION_ALGO.get()
+ ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp)
+ : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp);
+ }
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index f27c9da..ff0f773 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -38,18 +38,18 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherFiles;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.icons.cache.HandlerRunnable;
-import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
index 938955c..800598e 100644
--- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java
+++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
@@ -24,9 +24,9 @@
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
import java.util.ArrayList;
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index bfeb1dc..6bc1ecb 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -235,6 +235,9 @@
* Gets files used for FileLog
*/
public static File[] getLogFiles() {
+ try {
+ flushAll(null);
+ } catch (InterruptedException e) { }
File[] files = new File[LOG_DAYS];
for (int i = 0; i < LOG_DAYS; i++) {
files[i] = new File(sLogsDirectory, FILE_NAME_PREFIX + i);
diff --git a/src/com/android/launcher3/logging/InstanceId.java b/src/com/android/launcher3/logging/InstanceId.java
new file mode 100644
index 0000000..e720d75
--- /dev/null
+++ b/src/com/android/launcher3/logging/InstanceId.java
@@ -0,0 +1,108 @@
+/*
+ * 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.logging;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+
+/**
+ * An opaque identifier used to disambiguate which logs refer to a particular instance of some
+ * UI element. Useful when there might be multiple instances simultaneously active.
+ * Obtain from InstanceIdSequence. Clipped to range [0, INSTANCE_ID_MAX].
+ *
+ * Copy of frameworks/base/core/java/com/android/internal/logging/InstanceId.java.
+ */
+public final class InstanceId implements Parcelable {
+ // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
+ static final int INSTANCE_ID_MAX = 1 << 20;
+
+ private final int mId;
+ InstanceId(int id) {
+ mId = min(max(0, id), INSTANCE_ID_MAX);
+ }
+
+ private InstanceId(Parcel in) {
+ this(in.readInt());
+ }
+
+ @VisibleForTesting
+ public int getId() {
+ return mId;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return mId + "";
+ }
+
+ /**
+ * Create a fake instance ID for testing purposes. Not for production use. See also
+ * InstanceIdSequenceFake, which is a testing replacement for InstanceIdSequence.
+ * @param id The ID you want to assign.
+ * @return new InstanceId.
+ */
+ @VisibleForTesting
+ public static InstanceId fakeInstanceId(int id) {
+ return new InstanceId(id);
+ }
+
+ @Override
+ public int hashCode() {
+ return mId;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof InstanceId)) {
+ return false;
+ }
+ return mId == ((InstanceId) obj).mId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mId);
+ }
+
+ public static final Parcelable.Creator<InstanceId> CREATOR =
+ new Parcelable.Creator<InstanceId>() {
+ @Override
+ public InstanceId createFromParcel(Parcel in) {
+ return new InstanceId(in);
+ }
+
+ @Override
+ public InstanceId[] newArray(int size) {
+ return new InstanceId[size];
+ }
+ };
+
+}
diff --git a/src/com/android/launcher3/logging/InstanceIdSequence.java b/src/com/android/launcher3/logging/InstanceIdSequence.java
new file mode 100644
index 0000000..ee6a5a4
--- /dev/null
+++ b/src/com/android/launcher3/logging/InstanceIdSequence.java
@@ -0,0 +1,71 @@
+/*
+ * 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.logging;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * Generates random InstanceIds in range [1, instanceIdMax] for passing to
+ * UiEventLogger.logWithInstanceId(). Holds a SecureRandom, which self-seeds on first use; try to
+ * give it a long lifetime. Safe for concurrent use.
+ *
+ * Copy of frameworks/base/core/java/com/android/internal/logging/InstanceIdSequence.java
+ */
+public class InstanceIdSequence {
+ protected final int mInstanceIdMax;
+ private final Random mRandom = new SecureRandom();
+
+ /**
+ * Constructs a sequence with identifiers [1, instanceIdMax]. Capped at INSTANCE_ID_MAX.
+ * @param instanceIdMax Limiting value of identifiers. Normally positive: otherwise you get
+ * an all-1 sequence.
+ */
+ public InstanceIdSequence(int instanceIdMax) {
+ mInstanceIdMax = min(max(1, instanceIdMax), InstanceId.INSTANCE_ID_MAX);
+ }
+
+ /**
+ * Constructs a sequence with identifiers [1, InstanceId.INSTANCE_ID_MAX].
+ */
+ public InstanceIdSequence() {
+ this(InstanceId.INSTANCE_ID_MAX);
+ }
+
+ /**
+ * Gets the next instance from the sequence. Safe for concurrent use.
+ * @return new InstanceId
+ */
+ public InstanceId newInstanceId() {
+ return newInstanceIdInternal(1 + mRandom.nextInt(mInstanceIdMax));
+ }
+
+ /**
+ * Factory function for instance IDs, used for testing.
+ * @param id
+ * @return new InstanceId(id)
+ */
+ @VisibleForTesting
+ protected InstanceId newInstanceIdInternal(int id) {
+ return new InstanceId(id);
+ }
+}
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 1b70fde..cd4f034 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -19,10 +19,10 @@
import android.util.SparseArray;
import android.view.View;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.ButtonDropTarget;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogExtensions.TargetExtension;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
@@ -41,6 +41,7 @@
private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
private static final String UNKNOWN = "UNKNOWN";
private static final int DEFAULT_PREDICTED_RANK = 10000;
+ private static final String DELIMITER_DOT = "\\.";
public static String getFieldName(int value, Class c) {
SparseArray<String> cache;
@@ -173,4 +174,17 @@
targets.toArray(targetsArray);
return newLauncherEvent(action, targetsArray);
}
+
+ /**
+ * String conversion for only the helpful parts of {@link Object#toString()} method
+ * @param stringToExtract "foo.bar.baz.MyObject@1234"
+ * @return "MyObject@1234"
+ */
+ public static String extractObjectNameAndAddress(String stringToExtract) {
+ String[] superStringParts = stringToExtract.split(DELIMITER_DOT);
+ if (superStringParts.length == 0) {
+ return "";
+ }
+ return superStringParts[superStringParts.length - 1];
+ }
}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 2829951..8e23b65 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -15,62 +15,410 @@
*/
package com.android.launcher3.logging;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_DOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_OPEN_UP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
+
import android.content.Context;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.R;
-import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.logger.LauncherAtom.ItemInfo;
-import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.userevent.LauncherLogProto;
import com.android.launcher3.util.ResourceBasedOverride;
/**
* Handles the user event logging in R+.
+ * All of the event ids are defined here.
+ * Most of the methods are dummy methods for Launcher3
+ * Actual call happens only for Launcher variant that implements QuickStep.
*/
public class StatsLogManager implements ResourceBasedOverride {
- interface EventEnum {
+ public static final int LAUNCHER_STATE_UNSPECIFIED = 0;
+ public static final int LAUNCHER_STATE_BACKGROUND = 1;
+ public static final int LAUNCHER_STATE_HOME = 2;
+ public static final int LAUNCHER_STATE_OVERVIEW = 3;
+ public static final int LAUNCHER_STATE_ALLAPPS = 4;
+ public static final int LAUNCHER_STATE_UNCHANGED = 5;
+
+ /**
+ * Returns proper launcher state enum for {@link StatsLogManager}
+ * (to be removed during UserEventDispatcher cleanup)
+ */
+ public static int containerTypeToAtomState(int containerType) {
+ switch (containerType) {
+ case LauncherLogProto.ContainerType.ALLAPPS_VALUE:
+ return LAUNCHER_STATE_ALLAPPS;
+ case LauncherLogProto.ContainerType.OVERVIEW_VALUE:
+ return LAUNCHER_STATE_OVERVIEW;
+ case LauncherLogProto.ContainerType.WORKSPACE_VALUE:
+ return LAUNCHER_STATE_HOME;
+ case LauncherLogProto.ContainerType.APP_VALUE:
+ return LAUNCHER_STATE_BACKGROUND;
+ }
+ return LAUNCHER_STATE_UNSPECIFIED;
+ }
+
+ /**
+ * Returns event enum based on the two {@link ContainerType} transition information when
+ * swipe gesture happens.
+ * (to be removed during UserEventDispatcher cleanup)
+ */
+ public static EventEnum getLauncherAtomEvent(int startContainerType,
+ int targetContainerType, EventEnum fallbackEvent) {
+ if (startContainerType == LauncherLogProto.ContainerType.WORKSPACE.getNumber()
+ && targetContainerType == LauncherLogProto.ContainerType.WORKSPACE.getNumber()) {
+ return LAUNCHER_HOME_GESTURE;
+ } else if (startContainerType != LauncherLogProto.ContainerType.TASKSWITCHER.getNumber()
+ && targetContainerType == LauncherLogProto.ContainerType.TASKSWITCHER.getNumber()) {
+ return LAUNCHER_OVERVIEW_GESTURE;
+ } else if (startContainerType != LauncherLogProto.ContainerType.ALLAPPS.getNumber()
+ && targetContainerType == LauncherLogProto.ContainerType.ALLAPPS.getNumber()) {
+ return LAUNCHER_ALLAPPS_OPEN_UP;
+ } else if (startContainerType == LauncherLogProto.ContainerType.ALLAPPS.getNumber()
+ && targetContainerType != LauncherLogProto.ContainerType.ALLAPPS.getNumber()) {
+ return LAUNCHER_ALLAPPS_CLOSE_DOWN;
+ }
+ return fallbackEvent; // TODO fix
+ }
+
+ public interface EventEnum {
int getId();
}
public enum LauncherEvent implements EventEnum {
- @LauncherUiEvent(doc = "App launched from workspace, hotseat or folder in launcher")
- APP_LAUNCH_TAP(1),
- @LauncherUiEvent(doc = "Task launched from overview using TAP")
- TASK_LAUNCH_TAP(2),
- @LauncherUiEvent(doc = "Task launched from overview using SWIPE DOWN")
- TASK_LAUNCH_SWIPE_DOWN(2),
- @LauncherUiEvent(doc = "TASK dismissed from overview using SWIPE UP")
- TASK_DISMISS_SWIPE_UP(3);
+ /* Used to prevent double logging. */
+ IGNORE(-1),
+
+ @UiEvent(doc = "App launched from workspace, hotseat or folder in launcher")
+ LAUNCHER_APP_LAUNCH_TAP(338),
+
+ @UiEvent(doc = "Task launched from overview using TAP")
+ LAUNCHER_TASK_LAUNCH_TAP(339),
+
+ @UiEvent(doc = "User tapped on notification inside popup context menu.")
+ LAUNCHER_NOTIFICATION_LAUNCH_TAP(516),
+
+ @UiEvent(doc = "Task launched from overview using SWIPE DOWN")
+ LAUNCHER_TASK_LAUNCH_SWIPE_DOWN(340),
+
+ @UiEvent(doc = "TASK dismissed from overview using SWIPE UP")
+ LAUNCHER_TASK_DISMISS_SWIPE_UP(341),
+
+ @UiEvent(doc = "User dragged a launcher item")
+ LAUNCHER_ITEM_DRAG_STARTED(383),
+
+ @UiEvent(doc = "A dragged launcher item is successfully dropped")
+ LAUNCHER_ITEM_DROP_COMPLETED(385),
+
+ @UiEvent(doc = "A dragged launcher item is successfully dropped on another item "
+ + "resulting in a new folder creation")
+ LAUNCHER_ITEM_DROP_FOLDER_CREATED(386),
+
+ @UiEvent(doc = "Folder's label is automatically assigned.")
+ LAUNCHER_FOLDER_AUTO_LABELED(591),
+
+ @UiEvent(doc = "Could not auto-label a folder because primary suggestion is null or empty.")
+ LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY(592),
+
+ @UiEvent(doc = "Could not auto-label a folder because no suggestions exist.")
+ LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS(593),
+
+ @UiEvent(doc = "User manually updated the folder label.")
+ LAUNCHER_FOLDER_LABEL_UPDATED(460),
+
+ @UiEvent(doc = "User long pressed on the workspace empty space.")
+ LAUNCHER_WORKSPACE_LONGPRESS(461),
+
+ @UiEvent(doc = "User tapped or long pressed on a wallpaper icon inside launcher settings.")
+ LAUNCHER_WALLPAPER_BUTTON_TAP_OR_LONGPRESS(462),
+
+ @UiEvent(doc = "User tapped or long pressed on settings icon inside launcher settings.")
+ LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS(463),
+
+ @UiEvent(doc = "User tapped or long pressed on widget tray icon inside launcher settings.")
+ LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS(464),
+
+ @UiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar")
+ LAUNCHER_ITEM_DROPPED_ON_REMOVE(465),
+
+ @UiEvent(doc = "A dragged item is dropped on 'Cancel' button in the target bar")
+ LAUNCHER_ITEM_DROPPED_ON_CANCEL(466),
+
+ @UiEvent(doc = "A predicted item is dragged and dropped on 'Don't suggest app'"
+ + " button in the target bar")
+ LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST(467),
+
+ @UiEvent(doc = "A dragged item is dropped on 'Uninstall' button in target bar")
+ LAUNCHER_ITEM_DROPPED_ON_UNINSTALL(468),
+
+ @UiEvent(doc = "User completed uninstalling the package after dropping on "
+ + "the icon onto 'Uninstall' button in the target bar")
+ LAUNCHER_ITEM_UNINSTALL_COMPLETED(469),
+
+ @UiEvent(doc = "User cancelled uninstalling the package after dropping on "
+ + "the icon onto 'Uninstall' button in the target bar")
+ LAUNCHER_ITEM_UNINSTALL_CANCELLED(470),
+
+ @UiEvent(doc = "User tapped or long pressed on the task icon(aka package icon) "
+ + "from overview to open task menu.")
+ LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS(517),
+
+ @UiEvent(doc = "User opened package specific widgets list by tapping on widgets system "
+ + "shortcut inside popup context menu.")
+ LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP(514),
+
+ @UiEvent(doc = "User tapped on app info system shortcut.")
+ LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP(515),
+
+ @UiEvent(doc = "User tapped on split screen icon on a task menu.")
+ LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP(518),
+
+ @UiEvent(doc = "User tapped on free form icon on a task menu.")
+ LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP(519),
+
+ @UiEvent(doc = "User tapped on pause app system shortcut.")
+ LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
+
+ @UiEvent(doc = "User tapped on pin system shortcut.")
+ LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP(522),
+
+ @UiEvent(doc = "User is shown All Apps education view.")
+ LAUNCHER_ALL_APPS_EDU_SHOWN(523),
+
+ @UiEvent(doc = "User opened a folder.")
+ LAUNCHER_FOLDER_OPEN(551),
+
+ @UiEvent(doc = "Hotseat education half sheet seen")
+ LAUNCHER_HOTSEAT_EDU_SEEN(479),
+
+ @UiEvent(doc = "Hotseat migration accepted")
+ LAUNCHER_HOTSEAT_EDU_ACCEPT(480),
+
+ @UiEvent(doc = "Hotseat migration denied")
+ LAUNCHER_HOTSEAT_EDU_DENY(481),
+
+ @UiEvent(doc = "Hotseat education tip shown")
+ LAUNCHER_HOTSEAT_EDU_ONLY_TIP(482),
+
+ /**
+ * @deprecated LauncherUiChanged.rank field is repurposed to store all apps rank, so no
+ * separate event is required.
+ */
+ @Deprecated
+ @UiEvent(doc = "App launch ranking logged for all apps predictions")
+ LAUNCHER_ALL_APPS_RANKED(552),
+
+ @UiEvent(doc = "App launch ranking logged for hotseat predictions)")
+ LAUNCHER_HOTSEAT_RANKED(553),
+ @UiEvent(doc = "Launcher is now in background. e.g., Screen off event")
+ LAUNCHER_ONSTOP(562),
+
+ @UiEvent(doc = "Launcher is now in foreground. e.g., Screen on event, back button")
+ LAUNCHER_ONRESUME(563),
+
+ @UiEvent(doc = "User swipes or fling in LEFT direction on workspace.")
+ LAUNCHER_SWIPELEFT(564),
+
+ @UiEvent(doc = "User swipes or fling in RIGHT direction on workspace.")
+ LAUNCHER_SWIPERIGHT(565),
+
+ @UiEvent(doc = "User swipes or fling in UP direction in unknown way.")
+ LAUNCHER_UNKNOWN_SWIPEUP(566),
+
+ @UiEvent(doc = "User swipes or fling in DOWN direction in unknown way.")
+ LAUNCHER_UNKNOWN_SWIPEDOWN(567),
+
+ @UiEvent(doc = "User swipes or fling in UP direction to open apps drawer.")
+ LAUNCHER_ALLAPPS_OPEN_UP(568),
+
+ @UiEvent(doc = "User swipes or fling in DOWN direction to close apps drawer.")
+ LAUNCHER_ALLAPPS_CLOSE_DOWN(569),
+
+ @UiEvent(doc = "User swipes or fling in UP direction and hold from the bottom bazel area")
+ LAUNCHER_OVERVIEW_GESTURE(570),
+
+ @UiEvent(doc = "User swipes or fling in LEFT direction on the bottom bazel area.")
+ LAUNCHER_QUICKSWITCH_LEFT(571),
+
+ @UiEvent(doc = "User swipes or fling in RIGHT direction on the bottom bazel area.")
+ LAUNCHER_QUICKSWITCH_RIGHT(572),
+
+ @UiEvent(doc = "User swipes or fling in DOWN direction on the bottom bazel area.")
+ LAUNCHER_SWIPEDOWN_NAVBAR(573),
+
+ @UiEvent(doc = "User swipes or fling in UP direction from bottom bazel area.")
+ LAUNCHER_HOME_GESTURE(574),
+
+ @UiEvent(doc = "User's workspace layout information is snapshot in the background.")
+ LAUNCHER_WORKSPACE_SNAPSHOT(579),
+
+ @UiEvent(doc = "User tapped on the screenshot button on overview)")
+ LAUNCHER_OVERVIEW_ACTIONS_SCREENSHOT(580),
+
+ @UiEvent(doc = "User tapped on the select button on overview)")
+ LAUNCHER_OVERVIEW_ACTIONS_SELECT(581),
+
+ @UiEvent(doc = "User tapped on the share button on overview")
+ LAUNCHER_OVERVIEW_ACTIONS_SHARE(582),
+
+ @UiEvent(doc = "User tapped on the close button in select mode")
+ LAUNCHER_SELECT_MODE_CLOSE(583),
+
+ @UiEvent(doc = "User tapped on the highlight items in select mode")
+ LAUNCHER_SELECT_MODE_ITEM(584);
+
// ADD MORE
private final int mId;
+
LauncherEvent(int id) {
mId = id;
}
+
public int getId() {
return mId;
}
}
- protected LogStateProvider mStateProvider;
+ /**
+ * Launcher specific ranking related events.
+ */
+ public enum LauncherRankingEvent implements EventEnum {
- public static StatsLogManager newInstance(Context context, LogStateProvider stateProvider) {
+ UNKNOWN(0);
+ // ADD MORE
+
+ private final int mId;
+
+ LauncherRankingEvent(int id) {
+ mId = id;
+ }
+
+ public int getId() {
+ return mId;
+ }
+ }
+
+ /**
+ * Helps to construct and write the log message.
+ */
+ public interface StatsLogger {
+
+ /**
+ * Sets log fields from provided {@link ItemInfo}.
+ */
+ default StatsLogger withItemInfo(ItemInfo itemInfo) {
+ return this;
+ }
+
+
+ /**
+ * Sets {@link InstanceId} of log message.
+ */
+ default StatsLogger withInstanceId(InstanceId instanceId) {
+ return this;
+ }
+
+ /**
+ * Sets rank field of log message.
+ */
+ default StatsLogger withRank(int rank) {
+ return this;
+ }
+
+ /**
+ * Sets source launcher state field of log message.
+ */
+ default StatsLogger withSrcState(int srcState) {
+ return this;
+ }
+
+ /**
+ * Sets destination launcher state field of log message.
+ */
+ default StatsLogger withDstState(int dstState) {
+ return this;
+ }
+
+ /**
+ * Sets FromState field of log message.
+ */
+ default StatsLogger withFromState(FromState fromState) {
+ return this;
+ }
+
+ /**
+ * Sets ToState field of log message.
+ */
+ default StatsLogger withToState(ToState toState) {
+ return this;
+ }
+
+ /**
+ * Sets editText field of log message.
+ */
+ default StatsLogger withEditText(String editText) {
+ return this;
+ }
+
+ /**
+ * Sets the final value for container related fields of log message.
+ *
+ * By default container related fields are derived from {@link ItemInfo}, this method would
+ * override those values.
+ */
+ default StatsLogger withContainerInfo(ContainerInfo containerInfo) {
+ return this;
+ }
+
+ /**
+ * Builds the final message and logs it as {@link EventEnum}.
+ */
+ default void log(EventEnum event) {
+ }
+ }
+
+ /**
+ * Returns new logger object.
+ */
+ public StatsLogger logger() {
+ return new StatsLogger() {
+ };
+ }
+
+ /**
+ * Creates a new instance of {@link StatsLogManager} based on provided context.
+ */
+ public static StatsLogManager newInstance(Context context) {
StatsLogManager mgr = Overrides.getObject(StatsLogManager.class,
context.getApplicationContext(), R.string.stats_log_manager_class);
- mgr.mStateProvider = stateProvider;
- mgr.verify();
return mgr;
}
/**
- * Logs an event and accompanying {@link ItemInfo}
+ * Log an event with ranked-choice information along with package. Does nothing if event.getId()
+ * <= 0.
+ *
+ * @param rankingEvent an enum implementing EventEnum interface.
+ * @param instanceId An identifier obtained from an InstanceIdSequence.
+ * @param packageName the package name of the relevant app, if known (null otherwise).
+ * @param position the position picked.
*/
- public void log(LauncherEvent eventId, LauncherAtom.ItemInfo itemInfo) { }
+ public void log(EventEnum rankingEvent, InstanceId instanceId, @Nullable String packageName,
+ int position) {
+ }
/**
* Logs snapshot, or impression of the current workspace.
*/
- public void logSnapshot() { }
-
- public void verify() {} // TODO: should move into robo tests
+ public void logSnapshot() {
+ }
}
diff --git a/src/com/android/launcher3/logging/StatsLogUtils.java b/src/com/android/launcher3/logging/StatsLogUtils.java
index 8449612..a5cc7ea 100644
--- a/src/com/android/launcher3/logging/StatsLogUtils.java
+++ b/src/com/android/launcher3/logging/StatsLogUtils.java
@@ -1,38 +1,20 @@
package com.android.launcher3.logging;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.DEFAULT_CONTAINERTYPE;
-
import android.view.View;
import android.view.ViewParent;
import androidx.annotation.Nullable;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import java.util.ArrayList;
-
public class StatsLogUtils {
-
- // Defined in android.stats.launcher.nano
- // As they cannot be linked in this file, defining again.
- public final static int LAUNCHER_STATE_BACKGROUND = 0;
- public final static int LAUNCHER_STATE_HOME = 1;
- public final static int LAUNCHER_STATE_OVERVIEW = 2;
- public final static int LAUNCHER_STATE_ALLAPPS = 3;
-
private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
- public interface LogStateProvider {
- int getCurrentState();
- }
-
/**
* Implemented by containers to provide a container source for a given child.
- *
- * Currently,
*/
public interface LogContainerProvider {
@@ -64,20 +46,4 @@
}
return null;
}
-
- public static int getContainerTypeFromState(int state) {
- int containerType = DEFAULT_CONTAINERTYPE;
- switch (state) {
- case StatsLogUtils.LAUNCHER_STATE_ALLAPPS:
- containerType = ContainerType.ALLAPPS;
- break;
- case StatsLogUtils.LAUNCHER_STATE_HOME:
- containerType = ContainerType.WORKSPACE;
- break;
- case StatsLogUtils.LAUNCHER_STATE_OVERVIEW:
- containerType = ContainerType.OVERVIEW;
- break;
- }
- return containerType;
- }
}
diff --git a/src/com/android/launcher3/logging/LauncherUiEvent.java b/src/com/android/launcher3/logging/UiEvent.java
similarity index 80%
rename from src/com/android/launcher3/logging/LauncherUiEvent.java
rename to src/com/android/launcher3/logging/UiEvent.java
index 4507ff7..20d6c72 100644
--- a/src/com/android/launcher3/logging/LauncherUiEvent.java
+++ b/src/com/android/launcher3/logging/UiEvent.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.launcher3.logging;
import static java.lang.annotation.ElementType.FIELD;
@@ -23,8 +24,11 @@
@Retention(SOURCE)
@Target(FIELD)
-public @interface LauncherUiEvent {
- /** An explanation, suitable for Android analysts, of the UI event that this log represents. */
+// Copy of frameworks/base/core/java/com/android/internal/logging/UiEvent.java
+public @interface UiEvent {
+
+ /**
+ * An explanation, suitable for Android analysts, of the UI event that this log represents.
+ */
String doc();
}
-
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 89077ee..e094cab 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -46,11 +46,10 @@
import androidx.annotation.Nullable;
import com.android.launcher3.DropTarget;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
@@ -143,12 +142,6 @@
fillIntentInfo(itemTarget, intent, userHandle);
}
LauncherEvent event = newLauncherEvent(action, targets);
- ItemInfo info = v == null ? null : (ItemInfo) v.getTag();
- if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
- FileLog.d(TAG, "appLaunch: packageName:" + info.getTargetComponent().getPackageName()
- + ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals(
- userHandle)) + ",launchLocation:" + info.container);
- }
dispatchUserEvent(event, intent);
mAppOrTaskLaunch = true;
}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index eb95395..c236fa6 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -23,16 +23,16 @@
import android.util.LongSparseArray;
import android.util.Pair;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.FolderInfo;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 9f1843f..eb5d106 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -16,8 +16,8 @@
package com.android.launcher3.model;
-import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
-import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
+import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
+import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
import android.content.ComponentName;
import android.content.Context;
@@ -33,10 +33,11 @@
import androidx.annotation.Nullable;
import com.android.launcher3.AppFilter;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.PromiseAppInfo;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -73,6 +74,13 @@
private AlphabeticIndexCompat mIndex;
/**
+ * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
+ * @see Callbacks#FLAG_QUIET_MODE_ENABLED
+ * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
+ */
+ private int mFlags;
+
+ /**
* Boring constructor.
*/
public AllAppsList(IconCache iconCache, AppFilter appFilter) {
@@ -91,6 +99,33 @@
}
/**
+ * Helper to checking {@link Callbacks#FLAG_HAS_SHORTCUT_PERMISSION}
+ */
+ public boolean hasShortcutHostPermission() {
+ return (mFlags & Callbacks.FLAG_HAS_SHORTCUT_PERMISSION) != 0;
+ }
+
+ /**
+ * Sets or clears the provided flag
+ */
+ public void setFlags(int flagMask, boolean enabled) {
+ if (enabled) {
+ mFlags |= flagMask;
+ } else {
+ mFlags &= ~flagMask;
+ }
+ mDataChanged = true;
+ }
+
+ /**
+ * Returns the model flags
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+
+ /**
* Add the supplied ApplicationInfo objects to the list, and enqueue it into the
* list to broadcast when notify() is called.
*
diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java
index 13ab033..629a0ee 100644
--- a/src/com/android/launcher3/model/AppLaunchTracker.java
+++ b/src/com/android/launcher3/model/AppLaunchTracker.java
@@ -40,6 +40,7 @@
public static final String CONTAINER_ALL_APPS = Integer.toString(ContainerType.ALLAPPS);
public static final String CONTAINER_PREDICTIONS = Integer.toString(ContainerType.PREDICTION);
public static final String CONTAINER_SEARCH = Integer.toString(ContainerType.SEARCHRESULT);
+ public static final String CONTAINER_OVERVIEW = Integer.toString(ContainerType.OVERVIEW);
public static final MainThreadInitializedObject<AppLaunchTracker> INSTANCE =
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 0d12183..8b0ef7b 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -17,18 +17,19 @@
package com.android.launcher3.model;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import android.util.Log;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.PagedView;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.LooperIdleLock;
@@ -95,7 +96,8 @@
public void bindAllApps() {
// shallow copy
AppInfo[] apps = mBgAllAppsList.copyData();
- executeCallbacksTask(c -> c.bindAllApplications(apps), mUiExecutor);
+ int flags = mBgAllAppsList.getFlags();
+ executeCallbacksTask(c -> c.bindAllApplications(apps, flags), mUiExecutor);
}
public abstract void bindWidgets();
@@ -196,6 +198,10 @@
// Load items on the current page.
bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
bindAppWidgets(currentAppWidgets, mainExecutor);
+
+ // Locate available spots for prediction using currentWorkspaceItems
+ IntArray gaps = getMissingHotseatRanks(currentWorkspaceItems, idp.numHotseatIcons);
+ bindPredictedItems(gaps, mainExecutor);
// In case of validFirstPage, only bind the first screen, and defer binding the
// remaining screens after first onDraw (and an optional the fade animation whichever
// happens later).
@@ -247,6 +253,11 @@
}
}
+ private void bindPredictedItems(IntArray ranks, final Executor executor) {
+ ArrayList<AppInfo> items = new ArrayList<>(mBgDataModel.cachedPredictedItems);
+ executeCallbacksTask(c -> c.bindPredictedItems(items, ranks), executor);
+ }
+
protected void executeCallbacksTask(CallbackTask task, Executor executor) {
executor.execute(() -> {
if (mMyBindingId != mBgDataModel.lastBindId) {
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 5a7b4d3..9013cba 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -17,13 +17,13 @@
import android.util.Log;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.widget.WidgetListRowEntry;
@@ -117,7 +117,8 @@
public void bindApplicationsIfNeeded() {
if (mAllAppsList.getAndResetChangeFlag()) {
AppInfo[] apps = mAllAppsList.copyData();
- scheduleCallbackTask(c -> c.bindAllApplications(apps));
+ int flags = mAllAppsList.getFlags();
+ scheduleCallbackTask(c -> c.bindAllApplications(apps, flags));
}
}
}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 206688a..9bef847 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -26,16 +26,16 @@
import android.util.Log;
import android.util.MutableInt;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.FolderInfo;
import com.android.launcher3.InstallShortcutReceiver;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.Workspace;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.PromiseAppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.ComponentKey;
@@ -93,9 +93,16 @@
public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
/**
- * True if the launcher has permission to access deep shortcuts.
+ * List of all cached predicted items visible on home screen
*/
- public boolean hasShortcutHostPermission;
+ public final ArrayList<AppInfo> cachedPredictedItems = new ArrayList<>();
+
+ /**
+ * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
+ * @see Callbacks#FLAG_QUIET_MODE_ENABLED
+ * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
+ */
+ public int flags;
/**
* Maps all launcher activities to counts of their shortcuts.
@@ -342,6 +349,13 @@
}
public interface Callbacks {
+ // If the launcher has permission to access deep shortcuts.
+ int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0;
+ // If quiet mode is enabled for any user
+ int FLAG_QUIET_MODE_ENABLED = 1 << 1;
+ // If launcher can change quiet mode
+ int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2;
+
/**
* Returns the page number to bind first, synchronously if possible or -1
*/
@@ -365,6 +379,11 @@
void executeOnNextDraw(ViewOnDrawExecutor executor);
void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
- void bindAllApplications(AppInfo[] apps);
+ void bindAllApplications(AppInfo[] apps, int flags);
+
+ /**
+ * Binds predicted appInfos at at available prediction slots.
+ */
+ void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks);
}
}
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index c1c8be3..8e6b064 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -18,11 +18,11 @@
import android.content.ComponentName;
import android.os.UserHandle;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import java.util.ArrayList;
import java.util.HashSet;
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index a0b7177..5112304 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -15,16 +15,18 @@
*/
package com.android.launcher3.model;
+import static android.os.Process.myUserHandle;
+
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller.SessionInfo;
import android.util.Log;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageUserKey;
@@ -35,8 +37,6 @@
import java.util.Map;
import java.util.Set;
-import static android.os.Process.myUserHandle;
-
/**
* Helper class to send broadcasts to package installers that have:
* - Items on the first screen
diff --git a/src/com/android/launcher3/model/GridBackupTable.java b/src/com/android/launcher3/model/GridBackupTable.java
index 4a1bc4d..acfc339 100644
--- a/src/com/android/launcher3/model/GridBackupTable.java
+++ b/src/com/android/launcher3/model/GridBackupTable.java
@@ -128,6 +128,32 @@
}
/**
+ * Creates a new table and populates with copy of Favorites.TABLE_NAME
+ */
+ public void createCustomBackupTable(String tableName) {
+ long profileId = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
+ Process.myUserHandle());
+ copyTable(mDb, Favorites.TABLE_NAME, tableName, profileId);
+ encodeDBProperties(0);
+ }
+
+ /**
+ *
+ * Restores the contents of a custom table to Favorites.TABLE_NAME
+ */
+
+ public void restoreFromCustomBackupTable(String tableName, boolean dropAfterUse) {
+ if (!tableExists(mDb, tableName)) {
+ return;
+ }
+ long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
+ Process.myUserHandle());
+ copyTable(mDb, tableName, Favorites.TABLE_NAME, userSerial);
+ if (dropAfterUse) {
+ dropTable(mDb, tableName);
+ }
+ }
+ /**
* Copy valid grid entries from one table to another.
*/
private static void copyTable(SQLiteDatabase db, String from, String to, long userSerial) {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index a084600..e8a52bd 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -1,5 +1,7 @@
package com.android.launcher3.model;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE;
import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.Utilities.parsePoint;
@@ -22,7 +24,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
@@ -32,6 +33,7 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
@@ -53,9 +55,6 @@
private static final String TAG = "GridSizeMigrationTask";
private static final boolean DEBUG = true;
- private static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
- private static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count";
-
// These are carefully selected weights for various item types (Math.random?), to allow for
// the least absurd migration experience.
private static final float WT_SHORTCUT = 1;
@@ -894,8 +893,7 @@
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
- || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
- idp.numHotseatIcons);
+ || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1);
}
/** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 79ae4c5..ebdfa8c 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -16,6 +16,8 @@
package com.android.launcher3.model;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
@@ -30,18 +32,18 @@
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
-import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.GridOccupancy;
@@ -50,12 +52,13 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* This class takes care of shrinking the workspace (by maximum of one row and one column), as a
@@ -63,9 +66,6 @@
*/
public class GridSizeMigrationTaskV2 {
- public static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
- public static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count";
-
private static final String TAG = "GridSizeMigrationTaskV2";
private static final boolean DEBUG = true;
@@ -110,8 +110,7 @@
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
- || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
- idp.numHotseatIcons);
+ || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1);
}
/** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
@@ -148,14 +147,6 @@
SharedPreferences prefs = Utilities.getPrefs(context);
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
-
- if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
- && idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
- idp.numHotseatIcons)) {
- // Skip if workspace and hotseat sizes have not changed.
- return true;
- }
-
HashSet<String> validPackages = getValidPackages(context);
int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
@@ -236,9 +227,8 @@
if (DEBUG) {
Log.d(TAG, "Migrating " + screenId);
}
- List<DbEntry> entries = mDestReader.loadWorkspaceEntries(screenId);
GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
- mDestReader, mContext, entries, screenId, mTrgX, mTrgY, mWorkspaceDiff);
+ mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff);
workspaceSolution.find();
if (mWorkspaceDiff.isEmpty()) {
break;
@@ -248,8 +238,7 @@
int screenId = mDestReader.mLastScreenId + 1;
while (!mWorkspaceDiff.isEmpty()) {
GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
- mDestReader, mContext, new ArrayList<>(), screenId, mTrgX, mTrgY,
- mWorkspaceDiff);
+ mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff);
workspaceSolution.find();
screenId++;
}
@@ -258,75 +247,88 @@
/** Return what's in the src but not in the dest */
private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) {
- Set<String> destSet = dest.parallelStream().map(DbEntry::getIntentStr).collect(
- Collectors.toSet());
+ Set<String> destIntentSet = new HashSet<>();
+ Set<Map<String, Integer>> destFolderIntentSet = new HashSet<>();
+ for (DbEntry entry : dest) {
+ if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ destFolderIntentSet.add(getFolderIntents(entry));
+ } else {
+ destIntentSet.add(entry.mIntent);
+ }
+ }
List<DbEntry> diff = new ArrayList<>();
for (DbEntry entry : src) {
- if (!destSet.contains(entry.mIntent)) {
- diff.add(entry);
+ if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ if (!destFolderIntentSet.contains(getFolderIntents(entry))) {
+ diff.add(entry);
+ }
+ } else {
+ if (!destIntentSet.contains(entry.mIntent)) {
+ diff.add(entry);
+ }
}
}
return diff;
}
- private static void insertEntryInDb(SQLiteDatabase db, Context context,
- ArrayList<DbEntry> entriesFromSrcDb, DbEntry entry, String srcTableName,
- String destTableName) {
- int id = -1;
- switch (entry.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
- for (DbEntry e : entriesFromSrcDb) {
- if (TextUtils.equals(e.mIntent, entry.mIntent)) {
- id = e.id;
- }
- }
-
- break;
- }
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
- for (DbEntry e : entriesFromSrcDb) {
- if (e.mFolderItems.size() == entry.mFolderItems.size()
- && e.mFolderItems.containsAll(entry.mFolderItems)) {
- id = e.id;
- }
- }
- break;
- }
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
- for (DbEntry e : entriesFromSrcDb) {
- if (TextUtils.equals(e.mProvider, entry.mProvider)) {
- id = e.id;
- break;
- }
- }
- break;
- }
- default:
- return;
+ private static Map<String, Integer> getFolderIntents(DbEntry entry) {
+ Map<String, Integer> folder = new HashMap<>();
+ for (String intent : entry.mFolderItems.keySet()) {
+ folder.put(intent, entry.mFolderItems.get(intent).size());
}
+ return folder;
+ }
- Cursor c = db.query(srcTableName, null, LauncherSettings.Favorites._ID + " = '" + id + "'",
+ private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
+ String srcTableName, String destTableName) {
+ int id = copyEntryAndUpdate(db, context, entry, srcTableName, destTableName);
+
+ if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ for (Set<Integer> itemIds : entry.mFolderItems.values()) {
+ for (int itemId : itemIds) {
+ copyEntryAndUpdate(db, context, itemId, id, srcTableName, destTableName);
+ }
+ }
+ }
+ }
+
+ private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
+ DbEntry entry, String srcTableName, String destTableName) {
+ return copyEntryAndUpdate(db, context, entry, -1, -1, srcTableName, destTableName);
+ }
+
+ private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
+ int id, int folderId, String srcTableName, String destTableName) {
+ return copyEntryAndUpdate(db, context, null, id, folderId, srcTableName, destTableName);
+ }
+
+ private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
+ DbEntry entry, int id, int folderId, String srcTableName, String destTableName) {
+ int newId = -1;
+ Cursor c = db.query(srcTableName, null,
+ LauncherSettings.Favorites._ID + " = '" + (entry != null ? entry.id : id) + "'",
null, null, null, null);
-
while (c.moveToNext()) {
ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(c, values);
- entry.updateContentValues(values);
- values.put(LauncherSettings.Favorites._ID,
- LauncherSettings.Settings.call(context.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
- LauncherSettings.Settings.EXTRA_VALUE));
+ if (entry != null) {
+ entry.updateContentValues(values);
+ } else {
+ values.put(LauncherSettings.Favorites.CONTAINER, folderId);
+ }
+ newId = LauncherSettings.Settings.call(context.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
+ LauncherSettings.Settings.EXTRA_VALUE);
+ values.put(LauncherSettings.Favorites._ID, newId);
db.insert(destTableName, null, values);
}
c.close();
+ return newId;
}
- private static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryId) {
+ private static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryIds) {
db.delete(tableName,
- Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryId), null);
+ Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryIds), null);
}
private static HashSet<String> getValidPackages(Context context) {
@@ -362,8 +364,7 @@
private int mNextStartY;
GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
- Context context, List<DbEntry> placedWorkspaceItems, int screenId, int trgX,
- int trgY, List<DbEntry> itemsToPlace) {
+ Context context, int screenId, int trgX, int trgY, List<DbEntry> itemsToPlace) {
mDb = db;
mSrcReader = srcReader;
mDestReader = destReader;
@@ -374,8 +375,11 @@
mTrgY = trgY;
mNextStartX = 0;
mNextStartY = mTrgY - 1;
- for (DbEntry entry : placedWorkspaceItems) {
- mOccupied.markCells(entry, true);
+ List<DbEntry> existedEntries = mDestReader.mWorkspaceEntriesByScreenId.get(screenId);
+ if (existedEntries != null) {
+ for (DbEntry entry : existedEntries) {
+ mOccupied.markCells(entry, true);
+ }
}
mItemsToPlace = itemsToPlace;
}
@@ -389,15 +393,20 @@
continue;
}
if (findPlacement(entry)) {
- insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry,
- mSrcReader.mTableName, mDestReader.mTableName);
+ insertEntryInDb(mDb, mContext, entry, mSrcReader.mTableName,
+ mDestReader.mTableName);
iterator.remove();
}
}
}
+ /**
+ * Search for the next possible placement of an icon. (mNextStartX, mNextStartY) serves as
+ * a memoization of last placement, we can start our search for next placement from there
+ * to speed up the search.
+ */
private boolean findPlacement(DbEntry entry) {
- for (int y = mNextStartY; y >= 0; y--) {
+ for (int y = mNextStartY; y > 0; y--) {
for (int x = mNextStartX; x < mTrgX; x++) {
boolean fits = mOccupied.isRegionVacant(x, y, entry.spanX, entry.spanY);
boolean minFits = mOccupied.isRegionVacant(x, y, entry.minSpanX,
@@ -416,6 +425,7 @@
return true;
}
}
+ mNextStartX = 0;
}
return false;
}
@@ -453,8 +463,8 @@
// to something other than -1.
entry.cellX = i;
entry.cellY = 0;
- insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry,
- mSrcReader.mTableName, mDestReader.mTableName);
+ insertEntryInDb(mDb, mContext, entry, mSrcReader.mTableName,
+ mDestReader.mTableName);
mOccupied.markCells(entry, true);
}
}
@@ -485,6 +495,8 @@
private final ArrayList<DbEntry> mHotseatEntries = new ArrayList<>();
private final ArrayList<DbEntry> mWorkspaceEntries = new ArrayList<>();
+ private final Map<Integer, ArrayList<DbEntry>> mWorkspaceEntriesByScreenId =
+ new ArrayMap<>();
DbReader(SQLiteDatabase db, String tableName, Context context,
HashSet<String> validPackages, int hotseatSize) {
@@ -574,25 +586,6 @@
return loadWorkspaceEntries(c);
}
- protected ArrayList<DbEntry> loadWorkspaceEntries(int screen) {
- Cursor c = queryWorkspace(
- new String[]{
- LauncherSettings.Favorites._ID, // 0
- LauncherSettings.Favorites.ITEM_TYPE, // 1
- LauncherSettings.Favorites.SCREEN, // 2
- LauncherSettings.Favorites.CELLX, // 3
- LauncherSettings.Favorites.CELLY, // 4
- LauncherSettings.Favorites.SPANX, // 5
- LauncherSettings.Favorites.SPANY, // 6
- LauncherSettings.Favorites.INTENT, // 7
- LauncherSettings.Favorites.APPWIDGET_PROVIDER, // 8
- LauncherSettings.Favorites.APPWIDGET_ID}, // 9
- LauncherSettings.Favorites.CONTAINER + " = "
- + LauncherSettings.Favorites.CONTAINER_DESKTOP
- + " AND " + LauncherSettings.Favorites.SCREEN + " = " + screen);
- return loadWorkspaceEntries(c);
- }
-
private ArrayList<DbEntry> loadWorkspaceEntries(Cursor c) {
final int indexId = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int indexItemType = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
@@ -670,6 +663,10 @@
continue;
}
mWorkspaceEntries.add(entry);
+ if (!mWorkspaceEntriesByScreenId.containsKey(entry.screenId)) {
+ mWorkspaceEntriesByScreenId.put(entry.screenId, new ArrayList<>());
+ }
+ mWorkspaceEntriesByScreenId.get(entry.screenId).add(entry);
}
removeEntryFromDb(mDb, mTableName, entriesToRemove);
c.close();
@@ -684,10 +681,14 @@
int total = 0;
while (c.moveToNext()) {
try {
+ int id = c.getInt(0);
String intent = c.getString(1);
verifyIntent(intent);
total++;
- entry.mFolderItems.add(intent);
+ if (!entry.mFolderItems.containsKey(intent)) {
+ entry.mFolderItems.put(intent, new HashSet<>());
+ }
+ entry.mFolderItems.get(intent).add(id);
} catch (Exception e) {
removeEntryFromDb(mDb, mTableName, IntArray.wrap(c.getInt(0)));
}
@@ -726,7 +727,7 @@
private String mIntent;
private String mProvider;
- private Set<String> mFolderItems = new HashSet<>();
+ private Map<String, Set<Integer>> mFolderItems = new HashMap<>();
/** Comparator according to the reading order */
@Override
@@ -760,9 +761,5 @@
values.put(LauncherSettings.Favorites.SPANX, spanX);
values.put(LauncherSettings.Favorites.SPANY, spanY);
}
-
- public String getIntentStr() {
- return mIntent;
- }
}
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 695d2a6..165d1ea 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -37,19 +37,19 @@
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
@@ -65,7 +65,7 @@
private static final String TAG = "LoaderCursor";
- public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
+ public final LongSparseArray<UserHandle> allUsers;
private final Uri mContentUri;
private final Context mContext;
@@ -100,9 +100,11 @@
public int itemType;
public int restoreFlag;
- public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app) {
+ public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app,
+ UserManagerState userManagerState) {
super(cursor);
+ allUsers = userManagerState.allUsers;
mContentUri = contentUri;
mContext = app.getContext();
mIconCache = app.getIconCache();
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index fc0997b..102ec31 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,11 +16,14 @@
package com.android.launcher3.model;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
@@ -35,6 +38,7 @@
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
import android.os.UserHandle;
@@ -45,20 +49,17 @@
import android.util.MutableInt;
import android.util.TimingLogger;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.FolderInfo;
+import androidx.annotation.WorkerThread;
+
import com.android.launcher3.InstallShortcutReceiver;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderGridOrganizer;
-import com.android.launcher3.folder.FolderNameInfo;
+import com.android.launcher3.folder.FolderNameInfos;
import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.icons.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic;
@@ -67,6 +68,12 @@
import com.android.launcher3.icons.ShortcutCachingLogic;
import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
@@ -117,6 +124,8 @@
private final InstallSessionHelper mSessionHelper;
private final IconCache mIconCache;
+ private final UserManagerState mUserManagerState = new UserManagerState();
+
private boolean mStopped;
public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
@@ -178,6 +187,7 @@
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts);
+ loadCachedPredictions();
logger.addSplit("loadWorkspace");
verifyNotStopped();
@@ -329,7 +339,8 @@
Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
final LoaderCursor c = new LoaderCursor(
- contentResolver.query(contentUri, null, null, null, null), contentUri, mApp);
+ contentResolver.query(contentUri, null, null, null, null), contentUri, mApp,
+ mUserManagerState);
Map<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
@@ -348,12 +359,13 @@
LauncherSettings.Favorites.OPTIONS);
final LongSparseArray<UserHandle> allUsers = c.allUsers;
- final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
+
+ mUserManagerState.init(mUserCache, mUserManager);
+
for (UserHandle user : mUserCache.getUserProfiles()) {
long serialNo = mUserCache.getSerialNumberForUser(user);
allUsers.put(serialNo, user);
- quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
boolean userUnlocked = mUserManager.isUserUnlocked(user);
@@ -400,8 +412,8 @@
continue;
}
- int disabledState = quietMode.get(c.serialNumber) ?
- WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER : 0;
+ int disabledState = mUserManagerState.isUserQuiet(c.serialNumber)
+ ? WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER : 0;
ComponentName cn = intent.getComponent();
targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
@@ -534,7 +546,7 @@
pinnedShortcut.getPackage(), info.user)) {
info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
}
- intent = info.intent;
+ intent = info.getIntent();
allDeepShortcuts.add(pinnedShortcut);
} else {
// Create a shortcut info in disabled mode for now.
@@ -848,6 +860,24 @@
}
}
+ @WorkerThread
+ private void loadCachedPredictions() {
+ synchronized (mBgDataModel) {
+ List<ComponentKey> componentKeys =
+ mApp.getPredictionModel().getPredictionComponentKeys();
+ List<LauncherActivityInfo> l;
+ mBgDataModel.cachedPredictedItems.clear();
+ for (ComponentKey key : componentKeys) {
+ l = mLauncherApps.getActivityList(key.componentName.getPackageName(), key.user);
+ if (l.size() == 0) continue;
+ AppInfo info = new AppInfo(l.get(0), key.user,
+ mUserManagerState.isUserQuiet(key.user));
+ mBgDataModel.cachedPredictedItems.add(info);
+ mIconCache.getTitleAndIcon(info, false);
+ }
+ }
+ }
+
private List<LauncherActivityInfo> loadAllApps() {
final List<UserHandle> profiles = mUserCache.getUserProfiles();
List<LauncherActivityInfo> allActivityList = new ArrayList<>();
@@ -861,7 +891,7 @@
if (apps == null || apps.isEmpty()) {
return allActivityList;
}
- boolean quietMode = mUserManager.isQuietModeEnabled(user);
+ boolean quietMode = mUserManagerState.isUserQuiet(user);
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
@@ -879,6 +909,22 @@
PackageInstallInfo.fromInstallingState(info));
}
}
+ for (AppInfo item : mBgDataModel.cachedPredictedItems) {
+ List<LauncherActivityInfo> l = mLauncherApps.getActivityList(
+ item.componentName.getPackageName(), item.user);
+ for (LauncherActivityInfo info : l) {
+ boolean quietMode = mUserManagerState.isUserQuiet(item.user);
+ mBgAllAppsList.add(new AppInfo(info, item.user, quietMode), info);
+ }
+ }
+
+ mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
+ mUserManagerState.isAnyProfileQuietModeEnabled());
+ mBgAllAppsList.setFlags(FLAG_HAS_SHORTCUT_PERMISSION,
+ hasShortcutsPermission(mApp.getContext()));
+ mBgAllAppsList.setFlags(FLAG_QUIET_MODE_CHANGE_PERMISSION,
+ mApp.getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
+ == PackageManager.PERMISSION_GRANTED);
mBgAllAppsList.getAndResetChangeFlag();
return allActivityList;
@@ -887,8 +933,8 @@
private List<ShortcutInfo> loadDeepShortcuts() {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
mBgDataModel.deepShortcutMap.clear();
- mBgDataModel.hasShortcutHostPermission = hasShortcutsPermission(mApp.getContext());
- if (mBgDataModel.hasShortcutHostPermission) {
+
+ if (mBgAllAppsList.hasShortcutHostPermission()) {
for (UserHandle user : mUserCache.getUserProfiles()) {
if (mUserManager.isUserUnlocked(user)) {
List<ShortcutInfo> shortcuts = new ShortcutRequest(mApp.getContext(), user)
@@ -907,13 +953,12 @@
synchronized (mBgDataModel) {
for (int i = 0; i < mBgDataModel.folders.size(); i++) {
- FolderNameInfo[] suggestionInfos =
- new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderNameInfos suggestionInfos = new FolderNameInfos();
FolderInfo info = mBgDataModel.folders.valueAt(i);
if (info.suggestedFolderNames == null) {
provider.getSuggestedFolderName(mApp.getContext(), info.contents,
suggestionInfos);
- info.suggestedFolderNames = new Intent().putExtra("suggest", suggestionInfos);
+ info.suggestedFolderNames = suggestionInfos;
}
}
}
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 1473124..4efeba5 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -16,14 +16,17 @@
package com.android.launcher3.model;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
+import java.util.List;
+import java.util.stream.IntStream;
/**
* Utils class for {@link com.android.launcher3.LauncherModel}.
@@ -109,4 +112,17 @@
}
});
}
+
+ /**
+ * Iterates though current workspace items and returns available hotseat ranks for prediction.
+ */
+ public static IntArray getMissingHotseatRanks(List<ItemInfo> items, int len) {
+ IntSet seen = new IntSet();
+ items.stream().filter(
+ info -> info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
+ .forEach(i -> seen.add(i.screenId));
+ IntArray result = new IntArray(len);
+ IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
+ return result;
+ }
}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 27fa580..2c99df7 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -27,20 +27,20 @@
import android.os.Looper;
import android.util.Log;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetHost;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ItemInfoMatcher;
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 2832150..203f1ca 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -19,13 +19,13 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.PromiseAppInfo;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.PromiseAppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.util.InstantAppResolver;
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 48c56e9..7cd467e 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,8 +15,9 @@
*/
package com.android.launcher3.model;
-import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
-import static com.android.launcher3.WorkspaceItemInfo.FLAG_RESTORED_ICON;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
import android.content.ComponentName;
import android.content.Context;
@@ -29,20 +30,20 @@
import android.util.Log;
import com.android.launcher3.InstallShortcutReceiver;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logging.FileLog;
+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.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -86,10 +87,6 @@
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.APP_NOT_DISABLED, "PackageUpdatedTask: " + mOp + ", " +
- Arrays.toString(mPackages));
- }
final Context context = app.getContext();
final IconCache iconCache = app.getIconCache();
@@ -154,14 +151,21 @@
if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
appsList.updateDisabledFlags(matcher, flagOp);
break;
- case OP_USER_AVAILABILITY_CHANGE:
- flagOp = context.getSystemService(UserManager.class).isQuietModeEnabled(mUser)
+ case OP_USER_AVAILABILITY_CHANGE: {
+ UserManagerState ums = new UserManagerState();
+ ums.init(UserCache.INSTANCE.get(context),
+ context.getSystemService(UserManager.class));
+ flagOp = ums.isUserQuiet(mUser)
? FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER)
: FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER);
// We want to update all packages for this user.
matcher = ItemInfoMatcher.ofUser(mUser);
appsList.updateDisabledFlags(matcher, flagOp);
+
+ // We are not synchronizing here, as int operations are atomic
+ appsList.setFlags(FLAG_QUIET_MODE_ENABLED, ums.isAnyProfileQuietModeEnabled());
break;
+ }
}
bindApplicationsIfNeeded();
@@ -233,7 +237,7 @@
} else if (!isTargetValid) {
removedShortcuts.put(si.id, true);
FileLog.e(TAG, "Restored shortcut no longer valid "
- + si.intent);
+ + si.getIntent());
continue;
} else {
si.status = WorkspaceItemInfo.DEFAULT;
diff --git a/src/com/android/launcher3/model/PagedViewOrientedState.java b/src/com/android/launcher3/model/PagedViewOrientedState.java
deleted file mode 100644
index e48b8c1..0000000
--- a/src/com/android/launcher3/model/PagedViewOrientedState.java
+++ /dev/null
@@ -1,106 +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.model;
-
-import android.view.Surface;
-
-import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
-import com.android.launcher3.touch.LandscapePagedViewHandler;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.SeascapePagedViewHandler;
-
-/**
- * Container to hold orientation/rotation related information for Launcher.
- * This is not meant to be an abstraction layer for applying different functionality between
- * the different orientation/rotations. For that see {@link PagedOrientationHandler}
- *
- * This class has initial default state assuming the device and foreground app have
- * no ({@link Surface.ROTATION_0} rotation.
- *
- * Currently this class resides in {@link com.android.launcher3.PagedView}, but there's a ticket
- * to disassociate it from Launcher since it's needed before Launcher is instantiated
- * See TODO(b/150300347)
- */
-public final class PagedViewOrientedState {
-
- private PagedOrientationHandler mOrientationHandler = new PortraitPagedViewHandler();
-
- private int mTouchRotation = Surface.ROTATION_0;
- private int mDisplayRotation = Surface.ROTATION_0;
- /**
- * If {@code true} we default to {@link PortraitPagedViewHandler} and don't support any fake
- * launcher orientations.
- */
- private boolean mDisableMultipleOrientations;
-
- /**
- * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
- * @param touchRotation The rotation the nav bar region that is touched is in
- * @param displayRotation Rotation of the display/device
- */
- public void update(int touchRotation, int displayRotation) {
- if (mDisableMultipleOrientations) {
- return;
- }
-
- mDisplayRotation = displayRotation;
- mTouchRotation = touchRotation;
- if (mTouchRotation == Surface.ROTATION_90) {
- mOrientationHandler = new LandscapePagedViewHandler();
- } else if (mTouchRotation == Surface.ROTATION_270) {
- mOrientationHandler = new SeascapePagedViewHandler();
- } else {
- mOrientationHandler = new PortraitPagedViewHandler();
- }
- }
-
- public boolean areMultipleLayoutOrientationsDisabled() {
- return mDisableMultipleOrientations;
- }
-
- /**
- * Setting this preference will render future calls to {@link #update(int, int)} as a no-op.
- */
- public void disableMultipleOrientations(boolean disable) {
- mDisableMultipleOrientations = disable;
- if (disable) {
- mOrientationHandler = new PortraitPagedViewHandler();
- }
- }
-
- public int getDisplayRotation() {
- return mDisplayRotation;
- }
-
- /**
- * Gets the difference between the rotation of the device/display and which region the
- * user is currently interacting with in factors of 90 degree clockwise rotations.
- * Ex. Display is in portrait -> 0, user touches landscape region -> 1, this
- * method would return 3 because it takes 3 clockwise 90 degree rotations from normal to
- * landscape (portrait -> seascape -> reverse portrait -> landscape)
- */
- public int getTouchDisplayDelta() {
- return RotationHelper.deltaRotation(mTouchRotation, mDisplayRotation);
- }
-
- public PagedOrientationHandler getOrientationHandler() {
- return mOrientationHandler;
- }
-}
diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java
new file mode 100644
index 0000000..1429843
--- /dev/null
+++ b/src/com/android/launcher3/model/PredictionModel.java
@@ -0,0 +1,124 @@
+/*
+ * 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.model;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.UserHandle;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Model Helper for app predictions
+ */
+public class PredictionModel implements ResourceBasedOverride {
+
+ private static final String CACHED_ITEMS_KEY = "predicted_item_keys";
+ private static final int MAX_CACHE_ITEMS = 5;
+
+ protected Context mContext;
+ private SharedPreferences mDevicePrefs;
+ private UserCache mUserCache;
+
+
+ /**
+ * Retrieve instance of this object that can be overridden in runtime based on the build
+ * variant of the application.
+ */
+ public static PredictionModel newInstance(Context context) {
+ PredictionModel model = Overrides.getObject(PredictionModel.class, context,
+ R.string.prediction_model_class);
+ model.init(context);
+ return model;
+ }
+
+ protected void init(Context context) {
+ mContext = context;
+ mDevicePrefs = Utilities.getDevicePrefs(mContext);
+ mUserCache = UserCache.INSTANCE.get(mContext);
+
+ }
+ /**
+ * Formats and stores a list of component key in device preferences.
+ */
+ @AnyThread
+ public void cachePredictionComponentKeys(List<ComponentKey> componentKeys) {
+ MODEL_EXECUTOR.execute(() -> {
+ StringBuilder builder = new StringBuilder();
+ int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS);
+ for (int i = 0; i < count; i++) {
+ builder.append(serializeComponentKeyToString(componentKeys.get(i)));
+ builder.append("\n");
+ }
+ mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply();
+ });
+ }
+
+ /**
+ * parses and returns ComponentKeys saved by
+ * {@link PredictionModel#cachePredictionComponentKeys(List)}
+ */
+ @WorkerThread
+ public List<ComponentKey> getPredictionComponentKeys() {
+ Preconditions.assertWorkerThread();
+ ArrayList<ComponentKey> items = new ArrayList<>();
+ String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, "");
+ for (String line : cachedBlob.split("\n")) {
+ ComponentKey key = getComponentKeyFromSerializedString(line);
+ if (key != null) {
+ items.add(key);
+ }
+
+ }
+ return items;
+ }
+
+ private String serializeComponentKeyToString(ComponentKey componentKey) {
+ long userSerialNumber = mUserCache.getSerialNumberForUser(componentKey.user);
+ return componentKey.componentName.flattenToString() + "#" + userSerialNumber;
+ }
+
+ private ComponentKey getComponentKeyFromSerializedString(String str) {
+ int sep = str.indexOf('#');
+ if (sep < 0 || (sep + 1) >= str.length()) {
+ return null;
+ }
+ ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep));
+ if (componentName == null) {
+ return null;
+ }
+ try {
+ long serialNumber = Long.parseLong(str.substring(sep + 1));
+ UserHandle userHandle = mUserCache.getUserForSerialNumber(serialNumber);
+ return userHandle != null ? new ComponentKey(componentName, userHandle) : null;
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 3f79ad0..1cbe5c2 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -19,10 +19,10 @@
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.ItemInfoMatcher;
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index a3adc82..7ec884f 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -15,17 +15,16 @@
*/
package com.android.launcher3.model;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
-import android.os.UserManager;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
@@ -43,18 +42,19 @@
public class UserLockStateChangedTask extends BaseModelUpdateTask {
private final UserHandle mUser;
+ private boolean mIsUserUnlocked;
- public UserLockStateChangedTask(UserHandle user) {
+ public UserLockStateChangedTask(UserHandle user, boolean isUserUnlocked) {
mUser = user;
+ mIsUserUnlocked = isUserUnlocked;
}
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
Context context = app.getContext();
- boolean isUserUnlocked = context.getSystemService(UserManager.class).isUserUnlocked(mUser);
HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>();
- if (isUserUnlocked) {
+ if (mIsUserUnlocked) {
QueryResult shortcuts = new ShortcutRequest(context, mUser)
.query(ShortcutRequest.PINNED);
if (shortcuts.wasSuccess()) {
@@ -65,7 +65,7 @@
// Shortcut manager can fail due to some race condition when the lock state
// changes too frequently. For the purpose of the update,
// consider it as still locked.
- isUserUnlocked = false;
+ mIsUserUnlocked = false;
}
}
@@ -77,7 +77,7 @@
if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& mUser.equals(itemInfo.user)) {
WorkspaceItemInfo si = (WorkspaceItemInfo) itemInfo;
- if (isUserUnlocked) {
+ if (mIsUserUnlocked) {
ShortcutKey key = ShortcutKey.fromItemInfo(si);
ShortcutInfo shortcut = pinnedShortcuts.get(key);
// We couldn't verify the shortcut during loader. If its no longer available
@@ -108,7 +108,7 @@
}
}
- if (isUserUnlocked) {
+ if (mIsUserUnlocked) {
dataModel.updateDeepShortcutCounts(
null, mUser,
new ShortcutRequest(context, mUser).query(ShortcutRequest.ALL));
diff --git a/src/com/android/launcher3/model/UserManagerState.java b/src/com/android/launcher3/model/UserManagerState.java
new file mode 100644
index 0000000..3a4206c
--- /dev/null
+++ b/src/com/android/launcher3/model/UserManagerState.java
@@ -0,0 +1,73 @@
+/*
+ * 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.model;
+
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.LongSparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.launcher3.pm.UserCache;
+
+/**
+ * Utility class to manager store and user manager state at any particular time
+ */
+public class UserManagerState {
+
+ public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
+
+ private final LongSparseArray<Boolean> mQuietUsersSerialNoMap = new LongSparseArray<>();
+ private final SparseBooleanArray mQuietUsersHashCodeMap = new SparseBooleanArray();
+
+ /**
+ * Initialises the state values for all users
+ */
+ public void init(UserCache userCache, UserManager userManager) {
+ for (UserHandle user : userCache.getUserProfiles()) {
+ long serialNo = userCache.getSerialNumberForUser(user);
+ boolean isUserQuiet = userManager.isQuietModeEnabled(user);
+ allUsers.put(serialNo, user);
+ mQuietUsersHashCodeMap.put(user.hashCode(), isUserQuiet);
+ mQuietUsersSerialNoMap.put(serialNo, isUserQuiet);
+ }
+ }
+
+ /**
+ * Returns true if quiet mode is enabled for the provided user
+ */
+ public boolean isUserQuiet(long serialNo) {
+ return mQuietUsersSerialNoMap.get(serialNo);
+ }
+
+ /**
+ * Returns true if quiet mode is enabled for the provided user
+ */
+ public boolean isUserQuiet(UserHandle user) {
+ return mQuietUsersHashCodeMap.get(user.hashCode());
+ }
+
+ /**
+ * Returns true if any user profile has quiet mode enabled.
+ */
+ public boolean isAnyProfileQuietModeEnabled() {
+ for (int i = mQuietUsersHashCodeMap.size() - 1; i >= 0; i--) {
+ if (mQuietUsersHashCodeMap.valueAt(i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
similarity index 92%
rename from src/com/android/launcher3/AppInfo.java
rename to src/com/android/launcher3/model/data/AppInfo.java
index f76ca50..b17b062 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.model.data;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
import android.content.ComponentName;
import android.content.Context;
@@ -28,6 +30,8 @@
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
@@ -72,7 +76,7 @@
public AppInfo(LauncherActivityInfo info, UserHandle user, boolean quietModeEnabled) {
this.componentName = info.getComponentName();
- this.container = ItemInfo.NO_ID;
+ this.container = CONTAINER_ALL_APPS;
this.user = user;
intent = makeLaunchIntent(info);
@@ -121,7 +125,8 @@
return new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setComponent(cn)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
public static void updateRuntimeFlagsForActivityTarget(
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
new file mode 100644
index 0000000..41ccbd7
--- /dev/null
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2008 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.data;
+
+import static android.text.TextUtils.isEmpty;
+
+import static androidx.core.util.Preconditions.checkNotNull;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.logger.LauncherAtom.Attribute.EMPTY_LABEL;
+import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL;
+import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
+
+import android.os.Process;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.FolderNameInfos;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.Attribute;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
+import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.userevent.LauncherLogProto;
+import com.android.launcher3.userevent.LauncherLogProto.Target;
+import com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState;
+import com.android.launcher3.userevent.LauncherLogProto.Target.ToFolderLabelState;
+import com.android.launcher3.util.ContentWriter;
+
+import java.util.ArrayList;
+import java.util.OptionalInt;
+import java.util.stream.IntStream;
+
+
+/**
+ * Represents a folder containing shortcuts or apps.
+ */
+public class FolderInfo extends ItemInfo {
+
+ public static final int NO_FLAGS = 0x00000000;
+
+ /**
+ * The folder is locked in sorted mode
+ */
+ public static final int FLAG_ITEMS_SORTED = 0x00000001;
+
+ /**
+ * It is a work folder
+ */
+ public static final int FLAG_WORK_FOLDER = 0x00000002;
+
+ /**
+ * The multi-page animation has run for this folder
+ */
+ public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
+
+ public static final int FLAG_MANUAL_FOLDER_NAME = 0x00000008;
+
+ /**
+ * Different states of folder label.
+ */
+ public enum LabelState {
+ // Folder's label is not yet assigned( i.e., title == null). Eligible for auto-labeling.
+ UNLABELED(Attribute.UNLABELED),
+
+ // Folder's label is empty(i.e., title == ""). Not eligible for auto-labeling.
+ EMPTY(EMPTY_LABEL),
+
+ // Folder's label is one of the non-empty suggested values.
+ SUGGESTED(SUGGESTED_LABEL),
+
+ // Folder's label is non-empty, manually entered by the user
+ // and different from any of suggested values.
+ MANUAL(MANUAL_LABEL);
+
+ private final LauncherAtom.Attribute mLogAttribute;
+
+ LabelState(Attribute logAttribute) {
+ this.mLogAttribute = logAttribute;
+ }
+ }
+
+ public static final String EXTRA_FOLDER_SUGGESTIONS = "suggest";
+
+ public int options;
+
+ public FolderNameInfos suggestedFolderNames;
+
+ /**
+ * The apps and shortcuts
+ */
+ public ArrayList<WorkspaceItemInfo> contents = new ArrayList<>();
+
+ private ArrayList<FolderListener> mListeners = new ArrayList<>();
+
+ public FolderInfo() {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+ user = Process.myUserHandle();
+ }
+
+ /**
+ * Add an app or shortcut
+ *
+ * @param item
+ */
+ public void add(WorkspaceItemInfo item, boolean animate) {
+ add(item, contents.size(), animate);
+ }
+
+ /**
+ * Add an app or shortcut for a specified rank.
+ */
+ public void add(WorkspaceItemInfo item, int rank, boolean animate) {
+ rank = Utilities.boundToRange(rank, 0, contents.size());
+ contents.add(rank, item);
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onAdd(item, rank);
+ }
+ itemsChanged(animate);
+ }
+
+ /**
+ * Remove an app or shortcut. Does not change the DB.
+ *
+ * @param item
+ */
+ public void remove(WorkspaceItemInfo item, boolean animate) {
+ contents.remove(item);
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onRemove(item);
+ }
+ itemsChanged(animate);
+ }
+
+ @Override
+ public void onAddToDatabase(ContentWriter writer) {
+ super.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites.TITLE, title)
+ .put(LauncherSettings.Favorites.OPTIONS, options);
+ }
+
+ public void addListener(FolderListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void removeListener(FolderListener listener) {
+ mListeners.remove(listener);
+ }
+
+ public void itemsChanged(boolean animate) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onItemsChanged(animate);
+ }
+ }
+
+ public interface FolderListener {
+ public void onAdd(WorkspaceItemInfo item, int rank);
+ public void onRemove(WorkspaceItemInfo item);
+ public void onItemsChanged(boolean animate);
+ }
+
+ public boolean hasOption(int optionFlag) {
+ return (options & optionFlag) != 0;
+ }
+
+ /**
+ * @param option flag to set or clear
+ * @param isEnabled whether to set or clear the flag
+ * @param writer if not null, save changes to the db.
+ */
+ public void setOption(int option, boolean isEnabled, ModelWriter writer) {
+ int oldOptions = options;
+ if (isEnabled) {
+ options |= option;
+ } else {
+ options &= ~option;
+ }
+ if (writer != null && oldOptions != options) {
+ writer.updateItemInDatabase(this);
+ }
+ }
+
+ @Override
+ protected String dumpProperties() {
+ return String.format("%s; labelState=%s", super.dumpProperties(), getLabelState());
+ }
+
+ @Override
+ public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) {
+ return getDefaultItemInfoBuilder()
+ .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size()))
+ .setRank(rank)
+ .setAttribute(getLabelState().mLogAttribute)
+ .setContainerInfo(getContainerInfo())
+ .build();
+ }
+
+ @Override
+ public void setTitle(@Nullable CharSequence title, ModelWriter modelWriter) {
+ // Updating label from null to empty is considered as false touch.
+ // Retaining null title(ie., UNLABELED state) allows auto-labeling when new items added.
+ if (isEmpty(title) && this.title == null) {
+ return;
+ }
+
+ // Updating title to same value does not change any states.
+ if (title != null && title.equals(this.title)) {
+ return;
+ }
+
+ this.title = title;
+ LabelState newLabelState =
+ title == null ? LabelState.UNLABELED
+ : title.length() == 0 ? LabelState.EMPTY :
+ getAcceptedSuggestionIndex().isPresent() ? LabelState.SUGGESTED
+ : LabelState.MANUAL;
+
+ if (newLabelState.equals(LabelState.MANUAL)) {
+ options |= FLAG_MANUAL_FOLDER_NAME;
+ } else {
+ options &= ~FLAG_MANUAL_FOLDER_NAME;
+ }
+ if (modelWriter != null) {
+ modelWriter.updateItemInDatabase(this);
+ }
+ }
+
+ /**
+ * Returns current state of the current folder label.
+ */
+ public LabelState getLabelState() {
+ return title == null ? LabelState.UNLABELED
+ : title.length() == 0 ? LabelState.EMPTY :
+ hasOption(FLAG_MANUAL_FOLDER_NAME) ? LabelState.MANUAL
+ : LabelState.SUGGESTED;
+ }
+
+ @Override
+ public ItemInfo makeShallowCopy() {
+ FolderInfo folderInfo = new FolderInfo();
+ folderInfo.copyFrom(this);
+ folderInfo.contents = this.contents;
+ return folderInfo;
+ }
+
+ /**
+ * Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging.
+ */
+ @Override
+ public LauncherAtom.ItemInfo buildProto() {
+ return buildProto(null);
+ }
+
+ /**
+ * Returns index of the accepted suggestion.
+ */
+ public OptionalInt getAcceptedSuggestionIndex() {
+ String newLabel = checkNotNull(title,
+ "Expected valid folder label, but found null").toString();
+ if (suggestedFolderNames == null || !suggestedFolderNames.hasSuggestions()) {
+ return OptionalInt.empty();
+ }
+ CharSequence[] labels = suggestedFolderNames.getLabels();
+ return IntStream.range(0, labels.length)
+ .filter(index -> !isEmpty(labels[index])
+ && newLabel.equalsIgnoreCase(
+ labels[index].toString()))
+ .sequential()
+ .findFirst();
+ }
+
+ /**
+ * Returns {@link FromState} based on current {@link #title}.
+ */
+ public LauncherAtom.FromState getFromLabelState() {
+ switch (getLabelState()){
+ case EMPTY:
+ return LauncherAtom.FromState.FROM_EMPTY;
+ case MANUAL:
+ return LauncherAtom.FromState.FROM_CUSTOM;
+ case SUGGESTED:
+ return LauncherAtom.FromState.FROM_SUGGESTED;
+ case UNLABELED:
+ default:
+ return LauncherAtom.FromState.FROM_STATE_UNSPECIFIED;
+ }
+ }
+
+ /**
+ * Returns {@link ToState} based on current {@link #title}.
+ */
+ public LauncherAtom.ToState getToLabelState() {
+ if (title == null) {
+ return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
+ }
+
+ if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ return title.length() > 0
+ ? LauncherAtom.ToState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED
+ : LauncherAtom.ToState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED;
+ }
+
+ // TODO: if suggestedFolderNames is null then it infrastructure issue, not
+ // ranking issue. We should log these appropriately.
+ if (suggestedFolderNames == null || !suggestedFolderNames.hasSuggestions()) {
+ return title.length() > 0
+ ? LauncherAtom.ToState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS
+ : LauncherAtom.ToState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS;
+ }
+
+ boolean hasValidPrimary = suggestedFolderNames != null && suggestedFolderNames.hasPrimary();
+ if (title.length() == 0) {
+ return hasValidPrimary ? LauncherAtom.ToState.TO_EMPTY_WITH_VALID_PRIMARY
+ : LauncherAtom.ToState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
+ }
+
+ OptionalInt accepted_suggestion_index = getAcceptedSuggestionIndex();
+ if (!accepted_suggestion_index.isPresent()) {
+ return hasValidPrimary ? LauncherAtom.ToState.TO_CUSTOM_WITH_VALID_PRIMARY
+ : LauncherAtom.ToState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
+ }
+
+ switch (accepted_suggestion_index.getAsInt()) {
+ case 0:
+ return LauncherAtom.ToState.TO_SUGGESTION0;
+ case 1:
+ return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION1_WITH_VALID_PRIMARY
+ : LauncherAtom.ToState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY;
+ case 2:
+ return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION2_WITH_VALID_PRIMARY
+ : LauncherAtom.ToState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY;
+ case 3:
+ return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION3_WITH_VALID_PRIMARY
+ : LauncherAtom.ToState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY;
+ default:
+ // fall through
+ }
+ return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
+ }
+
+ /**
+ * Returns {@link LauncherLogProto.LauncherEvent} to log current folder label info.
+ *
+ * @deprecated This method is used only for validation purpose and soon will be removed.
+ */
+ @Deprecated
+ public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent(FromState fromState,
+ ToState toState) {
+ return LauncherLogProto.LauncherEvent.newBuilder()
+ .setAction(LauncherLogProto.Action
+ .newBuilder()
+ .setType(LauncherLogProto.Action.Type.SOFT_KEYBOARD))
+ .addSrcTarget(Target
+ .newBuilder()
+ .setType(Target.Type.ITEM)
+ .setItemType(LauncherLogProto.ItemType.EDITTEXT)
+ .setFromFolderLabelState(convertFolderLabelState(fromState))
+ .setToFolderLabelState(convertFolderLabelState(toState)))
+ .addSrcTarget(Target.newBuilder()
+ .setType(Target.Type.CONTAINER)
+ .setContainerType(LauncherLogProto.ContainerType.FOLDER)
+ .setPageIndex(screenId)
+ .setGridX(cellX)
+ .setGridY(cellY)
+ .setCardinality(contents.size()))
+ .addSrcTarget(newParentContainerTarget())
+ .build();
+ }
+
+ /**
+ * @deprecated This method is used only for validation purpose and soon will be removed.
+ */
+ @Deprecated
+ private Target.Builder newParentContainerTarget() {
+ Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER);
+ switch (container) {
+ case CONTAINER_HOTSEAT:
+ return builder.setContainerType(LauncherLogProto.ContainerType.HOTSEAT);
+ case CONTAINER_DESKTOP:
+ return builder.setContainerType(LauncherLogProto.ContainerType.WORKSPACE);
+ default:
+ throw new AssertionError(String
+ .format("Expected container to be either %s or %s but found %s.",
+ CONTAINER_HOTSEAT,
+ CONTAINER_DESKTOP,
+ container));
+ }
+ }
+
+ /**
+ * @deprecated This method is used only for validation purpose and soon will be removed.
+ */
+ @Deprecated
+ private static FromFolderLabelState convertFolderLabelState(FromState fromState) {
+ switch (fromState) {
+ case FROM_EMPTY:
+ return FROM_EMPTY;
+ case FROM_SUGGESTED:
+ return FROM_SUGGESTED;
+ case FROM_CUSTOM:
+ return FROM_CUSTOM;
+ default:
+ return FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
+ }
+ }
+
+ /**
+ * @deprecated This method is used only for validation purpose and soon will be removed.
+ */
+ @Deprecated
+ private static ToFolderLabelState convertFolderLabelState(ToState toState) {
+ switch (toState) {
+ case UNCHANGED:
+ return ToFolderLabelState.UNCHANGED;
+ case TO_SUGGESTION0:
+ return ToFolderLabelState.TO_SUGGESTION0_WITH_VALID_PRIMARY;
+ case TO_SUGGESTION1_WITH_VALID_PRIMARY:
+ return ToFolderLabelState.TO_SUGGESTION1_WITH_VALID_PRIMARY;
+ case TO_SUGGESTION1_WITH_EMPTY_PRIMARY:
+ return ToFolderLabelState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY;
+ case TO_SUGGESTION2_WITH_VALID_PRIMARY:
+ return ToFolderLabelState.TO_SUGGESTION2_WITH_VALID_PRIMARY;
+ case TO_SUGGESTION2_WITH_EMPTY_PRIMARY:
+ return ToFolderLabelState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY;
+ case TO_SUGGESTION3_WITH_VALID_PRIMARY:
+ return ToFolderLabelState.TO_SUGGESTION3_WITH_VALID_PRIMARY;
+ case TO_SUGGESTION3_WITH_EMPTY_PRIMARY:
+ return ToFolderLabelState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY;
+ case TO_EMPTY_WITH_VALID_PRIMARY:
+ return ToFolderLabelState.TO_EMPTY_WITH_VALID_PRIMARY;
+ case TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY:
+ return ToFolderLabelState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
+ case TO_EMPTY_WITH_EMPTY_SUGGESTIONS:
+ return ToFolderLabelState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS;
+ case TO_EMPTY_WITH_SUGGESTIONS_DISABLED:
+ return ToFolderLabelState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED;
+ case TO_CUSTOM_WITH_VALID_PRIMARY:
+ return ToFolderLabelState.TO_CUSTOM_WITH_VALID_PRIMARY;
+ case TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY:
+ return ToFolderLabelState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
+ case TO_CUSTOM_WITH_EMPTY_SUGGESTIONS:
+ return ToFolderLabelState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS;
+ case TO_CUSTOM_WITH_SUGGESTIONS_DISABLED:
+ return ToFolderLabelState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED;
+ default:
+ return ToFolderLabelState.TO_FOLDER_LABEL_STATE_UNSPECIFIED;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
new file mode 100644
index 0000000..0d3ddad
--- /dev/null
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2008 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.data;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+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_SEARCH_RESULTS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SETTINGS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK;
+import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.AllAppsContainer;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.PredictionContainer;
+import com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
+import com.android.launcher3.logger.LauncherAtom.SettingsContainer;
+import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
+import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
+import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.util.ContentWriter;
+
+import java.util.Optional;
+
+/**
+ * Represents an item in the launcher.
+ */
+public class ItemInfo {
+
+ public static final boolean DEBUG = true;
+ public static final int NO_ID = -1;
+
+ /**
+ * The id in the settings database for this item
+ */
+ public int id = NO_ID;
+
+ /**
+ * One of {@link Favorites#ITEM_TYPE_APPLICATION},
+ * {@link Favorites#ITEM_TYPE_SHORTCUT},
+ * {@link Favorites#ITEM_TYPE_DEEP_SHORTCUT}
+ * {@link Favorites#ITEM_TYPE_FOLDER},
+ * {@link Favorites#ITEM_TYPE_APPWIDGET} or
+ * {@link Favorites#ITEM_TYPE_CUSTOM_APPWIDGET}.
+ */
+ public int itemType;
+
+ /**
+ * The id of the container that holds this item. For the desktop, this will be
+ * {@link Favorites#CONTAINER_DESKTOP}. For the all applications folder it
+ * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders
+ * it will be the id of the folder.
+ */
+ public int container = NO_ID;
+
+ /**
+ * Indicates the screen in which the shortcut appears if the container types is
+ * {@link Favorites#CONTAINER_DESKTOP}. (i.e., ignore if the container type is
+ * {@link Favorites#CONTAINER_HOTSEAT})
+ */
+ public int screenId = -1;
+
+ /**
+ * Indicates the X position of the associated cell.
+ */
+ public int cellX = -1;
+
+ /**
+ * Indicates the Y position of the associated cell.
+ */
+ public int cellY = -1;
+
+ /**
+ * Indicates the X cell span.
+ */
+ public int spanX = 1;
+
+ /**
+ * Indicates the Y cell span.
+ */
+ public int spanY = 1;
+
+ /**
+ * Indicates the minimum X cell span.
+ */
+ public int minSpanX = 1;
+
+ /**
+ * Indicates the minimum Y cell span.
+ */
+ public int minSpanY = 1;
+
+ /**
+ * Indicates the position in an ordered list.
+ */
+ public int rank = 0;
+
+ /**
+ * Title of the item
+ */
+ public CharSequence title;
+
+ /**
+ * Content description of the item.
+ */
+ public CharSequence contentDescription;
+
+ /**
+ * When the instance is created using {@link #copyFrom}, this field is used to keep track of
+ * original {@link ComponentName}.
+ */
+ private ComponentName mComponentName;
+
+ public UserHandle user;
+
+ public ItemInfo() {
+ user = Process.myUserHandle();
+ }
+
+ protected ItemInfo(ItemInfo info) {
+ copyFrom(info);
+ }
+
+ public void copyFrom(ItemInfo info) {
+ id = info.id;
+ cellX = info.cellX;
+ cellY = info.cellY;
+ spanX = info.spanX;
+ spanY = info.spanY;
+ rank = info.rank;
+ screenId = info.screenId;
+ itemType = info.itemType;
+ container = info.container;
+ user = info.user;
+ contentDescription = info.contentDescription;
+ mComponentName = info.getTargetComponent();
+ }
+
+ public Intent getIntent() {
+ return null;
+ }
+
+ @Nullable
+ public ComponentName getTargetComponent() {
+ return Optional.ofNullable(getIntent()).map(Intent::getComponent).orElse(mComponentName);
+ }
+
+ public void writeToValues(ContentWriter writer) {
+ writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
+ .put(LauncherSettings.Favorites.CONTAINER, container)
+ .put(LauncherSettings.Favorites.SCREEN, screenId)
+ .put(LauncherSettings.Favorites.CELLX, cellX)
+ .put(LauncherSettings.Favorites.CELLY, cellY)
+ .put(LauncherSettings.Favorites.SPANX, spanX)
+ .put(LauncherSettings.Favorites.SPANY, spanY)
+ .put(LauncherSettings.Favorites.RANK, rank);
+ }
+
+ public void readFromValues(ContentValues values) {
+ itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+ container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
+ screenId = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
+ cellX = values.getAsInteger(LauncherSettings.Favorites.CELLX);
+ cellY = values.getAsInteger(LauncherSettings.Favorites.CELLY);
+ spanX = values.getAsInteger(LauncherSettings.Favorites.SPANX);
+ spanY = values.getAsInteger(LauncherSettings.Favorites.SPANY);
+ rank = values.getAsInteger(LauncherSettings.Favorites.RANK);
+ }
+
+ /**
+ * Write the fields of this item to the DB
+ */
+ public void onAddToDatabase(ContentWriter writer) {
+ if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) {
+ // We should never persist an item on the extra empty screen.
+ throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
+ }
+
+ writeToValues(writer);
+ writer.put(LauncherSettings.Favorites.PROFILE_ID, user);
+ }
+
+ @Override
+ public final String toString() {
+ return getClass().getSimpleName() + "(" + dumpProperties() + ")";
+ }
+
+ protected String dumpProperties() {
+ return "id=" + id
+ + " type=" + LauncherSettings.Favorites.itemTypeToString(itemType)
+ + " container=" + LauncherSettings.Favorites.containerToString(container)
+ + " targetComponent=" + getTargetComponent()
+ + " screen=" + screenId
+ + " cell(" + cellX + "," + cellY + ")"
+ + " span(" + spanX + "," + spanY + ")"
+ + " minSpan(" + minSpanX + "," + minSpanY + ")"
+ + " rank=" + rank
+ + " user=" + user
+ + " title=" + title;
+ }
+
+ /**
+ * Whether this item is disabled.
+ */
+ public boolean isDisabled() {
+ return false;
+ }
+
+ public int getViewId() {
+ // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+ // This cast is safe as long as the id < 0x00FFFFFF
+ // Since we jail all the dynamically generated views, there should be no clashes
+ // with any other views.
+ return id;
+ }
+
+ /**
+ * Returns if an Item is a predicted item
+ */
+ public boolean isPredictedItem() {
+ return container == CONTAINER_HOTSEAT_PREDICTION || container == CONTAINER_PREDICTION;
+ }
+
+ /**
+ * Can be overridden by inherited classes to fill in {@link LauncherAtom.ItemInfo}
+ */
+ public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
+ }
+
+ /**
+ * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
+ */
+ public LauncherAtom.ItemInfo buildProto() {
+ return buildProto(null);
+ }
+
+ /**
+ * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
+ */
+ public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) {
+ LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder();
+ Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent());
+ switch (itemType) {
+ case ITEM_TYPE_APPLICATION:
+ itemBuilder
+ .setApplication(nullableComponent
+ .map(component -> LauncherAtom.Application.newBuilder()
+ .setComponentName(component.flattenToShortString())
+ .setPackageName(component.getPackageName()))
+ .orElse(LauncherAtom.Application.newBuilder()));
+ break;
+ case ITEM_TYPE_DEEP_SHORTCUT:
+ case ITEM_TYPE_SHORTCUT:
+ itemBuilder
+ .setShortcut(nullableComponent
+ .map(component -> LauncherAtom.Shortcut.newBuilder()
+ .setShortcutName(component.flattenToShortString()))
+ .orElse(LauncherAtom.Shortcut.newBuilder()));
+ break;
+ case ITEM_TYPE_APPWIDGET:
+ itemBuilder
+ .setWidget(nullableComponent
+ .map(component -> LauncherAtom.Widget.newBuilder()
+ .setComponentName(component.flattenToShortString())
+ .setPackageName(component.getPackageName()))
+ .orElse(LauncherAtom.Widget.newBuilder())
+ .setSpanX(spanX)
+ .setSpanY(spanY));
+ break;
+ case ITEM_TYPE_TASK:
+ itemBuilder
+ .setTask(LauncherAtom.Task.newBuilder()
+ .setComponentName(getTargetComponent().flattenToShortString())
+ .setIndex(screenId));
+ break;
+ default:
+ break;
+ }
+ if (fInfo != null) {
+ LauncherAtom.FolderContainer.Builder folderBuilder =
+ LauncherAtom.FolderContainer.newBuilder();
+ folderBuilder.setGridX(cellX).setGridY(cellY).setPageIndex(screenId);
+
+ switch (fInfo.container) {
+ case CONTAINER_HOTSEAT:
+ case CONTAINER_HOTSEAT_PREDICTION:
+ folderBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
+ .setIndex(fInfo.screenId));
+ break;
+ case CONTAINER_DESKTOP:
+ folderBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
+ .setPageIndex(fInfo.screenId)
+ .setGridX(fInfo.cellX).setGridY(fInfo.cellY));
+ break;
+ }
+ itemBuilder.setContainerInfo(ContainerInfo.newBuilder().setFolder(folderBuilder));
+ } else {
+ ContainerInfo containerInfo = getContainerInfo();
+ if (!containerInfo.getContainerCase().equals(CONTAINER_NOT_SET)) {
+ itemBuilder.setContainerInfo(containerInfo);
+ }
+ }
+ return itemBuilder.build();
+ }
+
+ LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() {
+ LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
+ itemBuilder.setIsWork(user != Process.myUserHandle());
+ return itemBuilder;
+ }
+
+ protected ContainerInfo getContainerInfo() {
+ switch (container) {
+ case CONTAINER_HOTSEAT:
+ return ContainerInfo.newBuilder()
+ .setHotseat(LauncherAtom.HotseatContainer.newBuilder().setIndex(screenId))
+ .build();
+ case CONTAINER_HOTSEAT_PREDICTION:
+ return ContainerInfo.newBuilder().setPredictedHotseatContainer(
+ LauncherAtom.PredictedHotseatContainer.newBuilder().setIndex(screenId))
+ .build();
+ case CONTAINER_DESKTOP:
+ return ContainerInfo.newBuilder()
+ .setWorkspace(
+ LauncherAtom.WorkspaceContainer.newBuilder()
+ .setGridX(cellX)
+ .setGridY(cellY)
+ .setPageIndex(screenId))
+ .build();
+ case CONTAINER_ALL_APPS:
+ return ContainerInfo.newBuilder()
+ .setAllAppsContainer(
+ AllAppsContainer.getDefaultInstance())
+ .build();
+ case CONTAINER_WIDGETS_TRAY:
+ return ContainerInfo.newBuilder()
+ .setWidgetsContainer(
+ LauncherAtom.WidgetsContainer.getDefaultInstance())
+ .build();
+ case CONTAINER_PREDICTION:
+ return ContainerInfo.newBuilder()
+ .setPredictionContainer(PredictionContainer.getDefaultInstance())
+ .build();
+ case CONTAINER_SEARCH_RESULTS:
+ return ContainerInfo.newBuilder()
+ .setSearchResultContainer(SearchResultContainer.getDefaultInstance())
+ .build();
+ case CONTAINER_SHORTCUTS:
+ return ContainerInfo.newBuilder()
+ .setShortcutsContainer(ShortcutsContainer.getDefaultInstance())
+ .build();
+ case CONTAINER_SETTINGS:
+ return ContainerInfo.newBuilder()
+ .setSettingsContainer(SettingsContainer.getDefaultInstance())
+ .build();
+ case CONTAINER_TASKSWITCHER:
+ return ContainerInfo.newBuilder()
+ .setTaskSwitcherContainer(TaskSwitcherContainer.getDefaultInstance())
+ .build();
+
+ }
+ return ContainerInfo.getDefaultInstance();
+ }
+
+ /**
+ * Returns shallow copy of the object.
+ */
+ public ItemInfo makeShallowCopy() {
+ ItemInfo itemInfo = new ItemInfo();
+ itemInfo.copyFrom(this);
+ return itemInfo;
+ }
+
+ /**
+ * Sets the title of the item and writes to DB model if needed.
+ */
+ public void setTitle(CharSequence title, ModelWriter modelWriter) {
+ this.title = title;
+ }
+}
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
similarity index 93%
rename from src/com/android/launcher3/ItemInfoWithIcon.java
rename to src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 1941455..d95f94f 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.model.data;
import com.android.launcher3.icons.BitmapInfo;
@@ -60,9 +60,9 @@
*/
public static final int FLAG_DISABLED_LOCKED_USER = 1 << 5;
- public static final int FLAG_DISABLED_MASK = FLAG_DISABLED_SAFEMODE |
- FLAG_DISABLED_NOT_AVAILABLE | FLAG_DISABLED_SUSPENDED |
- FLAG_DISABLED_QUIET_USER | FLAG_DISABLED_BY_PUBLISHER | FLAG_DISABLED_LOCKED_USER;
+ public static final int FLAG_DISABLED_MASK = FLAG_DISABLED_SAFEMODE
+ | FLAG_DISABLED_NOT_AVAILABLE | FLAG_DISABLED_SUSPENDED
+ | FLAG_DISABLED_QUIET_USER | FLAG_DISABLED_BY_PUBLISHER | FLAG_DISABLED_LOCKED_USER;
/**
* The item points to a system app.
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
similarity index 91%
rename from src/com/android/launcher3/LauncherAppWidgetInfo.java
rename to src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index 3a478dd..b0d19a6 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -14,15 +14,19 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.model.data;
import android.appwidget.AppWidgetHostView;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Process;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.util.ContentWriter;
/**
@@ -140,6 +144,12 @@
return appWidgetId <= CUSTOM_WIDGET_ID;
}
+ @Nullable
+ @Override
+ public ComponentName getTargetComponent() {
+ return providerName;
+ }
+
@Override
public void onAddToDatabase(ContentWriter writer) {
super.onAddToDatabase(writer);
@@ -154,7 +164,7 @@
* When we bind the widget, we should notify the widget that the size has changed if we have not
* done so already (only really for default workspace widgets).
*/
- void onBindAppWidget(Launcher launcher, AppWidgetHostView hostView) {
+ public void onBindAppWidget(Launcher launcher, AppWidgetHostView hostView) {
if (!mHasNotifiedInitialWidgetSizeChanged) {
AppWidgetResizeFrame.updateWidgetSizeRanges(hostView, launcher, spanX, spanY);
mHasNotifiedInitialWidgetSizeChanged = true;
@@ -169,8 +179,8 @@
}
public final boolean isWidgetIdAllocated() {
- return (restoreStatus & FLAG_ID_NOT_VALID) == 0 ||
- (restoreStatus & FLAG_ID_ALLOCATED) == FLAG_ID_ALLOCATED;
+ return (restoreStatus & FLAG_ID_NOT_VALID) == 0
+ || (restoreStatus & FLAG_ID_ALLOCATED) == FLAG_ID_ALLOCATED;
}
public final boolean hasRestoreFlag(int flag) {
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/data/PackageItemInfo.java
similarity index 95%
rename from src/com/android/launcher3/model/PackageItemInfo.java
rename to src/com/android/launcher3/model/data/PackageItemInfo.java
index 2fc064c..b70d0d4 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/data/PackageItemInfo.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.launcher3.model;
+package com.android.launcher3.model.data;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherSettings;
import java.util.Objects;
diff --git a/src/com/android/launcher3/PromiseAppInfo.java b/src/com/android/launcher3/model/data/PromiseAppInfo.java
similarity index 97%
rename from src/com/android/launcher3/PromiseAppInfo.java
rename to src/com/android/launcher3/model/data/PromiseAppInfo.java
index e55e4bd..b6231ed 100644
--- a/src/com/android/launcher3/PromiseAppInfo.java
+++ b/src/com/android/launcher3/model/data/PromiseAppInfo.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.model.data;
import android.content.Context;
import android.content.Intent;
diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
similarity index 94%
rename from src/com/android/launcher3/WorkspaceItemInfo.java
rename to src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index be907e5..a7bf1f3 100644
--- a/src/com/android/launcher3/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.model.data;
import android.app.Person;
import android.content.ComponentName;
@@ -25,7 +25,9 @@
import androidx.annotation.NonNull;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.Utilities;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.uioverrides.ApiWrapper;
@@ -120,7 +122,7 @@
public WorkspaceItemInfo(AppInfo info) {
super(info);
title = Utilities.trim(info.title);
- intent = new Intent(info.intent);
+ intent = new Intent(info.getIntent());
}
/**
@@ -199,8 +201,8 @@
/** Returns the WorkspaceItemInfo id associated with the deep shortcut. */
public String getDeepShortcutId() {
- return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT ?
- getIntent().getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) : null;
+ return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ ? getIntent().getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) : null;
}
@NonNull
@@ -211,8 +213,8 @@
@Override
public ComponentName getTargetComponent() {
ComponentName cn = super.getTargetComponent();
- if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT
- || hasStatusFlag(FLAG_SUPPORTS_WEB_UI|FLAG_AUTOINSTALL_ICON|FLAG_RESTORED_ICON))) {
+ if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT || hasStatusFlag(
+ FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON | FLAG_RESTORED_ICON))) {
// Legacy shortcuts and promise icons with web UI may not have a componentName but just
// a packageName. In that case create a dummy componentName instead of adding additional
// check everywhere.
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
index e5525b2..835f72d 100644
--- a/src/com/android/launcher3/notification/NotificationInfo.java
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -16,6 +16,8 @@
package com.android.launcher3.notification;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_LAUNCH_TAP;
+
import android.app.ActivityOptions;
import android.app.Notification;
import android.app.PendingIntent;
@@ -32,6 +34,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.PackageUserKey;
/**
@@ -51,6 +54,7 @@
public final boolean autoCancel;
public final boolean dismissable;
+ private final ItemInfo mItemInfo;
private Drawable mIconDrawable;
private int mIconColor;
private boolean mIsIconLarge;
@@ -58,7 +62,8 @@
/**
* Extracts the data that we need from the StatusBarNotification.
*/
- public NotificationInfo(Context context, StatusBarNotification statusBarNotification) {
+ public NotificationInfo(Context context, StatusBarNotification statusBarNotification,
+ ItemInfo itemInfo) {
packageUserKey = PackageUserKey.fromNotification(statusBarNotification);
notificationKey = statusBarNotification.getKey();
Notification notification = statusBarNotification.getNotification();
@@ -88,6 +93,7 @@
intent = notification.contentIntent;
autoCancel = (notification.flags & Notification.FLAG_AUTO_CANCEL) != 0;
dismissable = (notification.flags & Notification.FLAG_ONGOING_EVENT) == 0;
+ this.mItemInfo = itemInfo;
}
@Override
@@ -101,6 +107,8 @@
try {
intent.send(null, 0, null, null, null, null, activityOptions);
launcher.getUserEventDispatcher().logNotificationLaunch(view, intent);
+ launcher.getStatsLogManager().logger().withItemInfo(mItemInfo)
+ .log(LAUNCHER_NOTIFICATION_LAUNCH_TAP);
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index b193ffd..b03aa9c 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -34,10 +34,10 @@
import android.widget.FrameLayout;
import android.widget.TextView;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 976d7ba..901d27f 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -56,8 +56,6 @@
// Set<String> of session ids of promise icons that have been added to the home screen
// as FLAG_PROMISE_NEW_INSTALLS.
protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
- public static final String KEY_INSTALL_SESSION_CREATED_TIMESTAMP =
- "key_install_session_created_timestamp";
private static final boolean DEBUG = false;
@@ -166,14 +164,13 @@
}
/**
- * Attempt to restore workspace layout if the session is triggered due to device restore and it
- * has a newer timestamp.
+ * Attempt to restore workspace layout if the session is triggered due to device restore.
*/
public boolean restoreDbIfApplicable(@NonNull final SessionInfo info) {
if (!Utilities.ATLEAST_OREO || !FeatureFlags.ENABLE_DATABASE_RESTORE.get()) {
return false;
}
- if (isRestore(info) && hasNewerTimestamp(mAppContext, info)) {
+ if (isRestore(info)) {
LauncherSettings.Settings.call(mAppContext.getContentResolver(),
LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE);
return true;
@@ -186,13 +183,6 @@
return info.getInstallReason() == PackageManager.INSTALL_REASON_DEVICE_RESTORE;
}
- private static boolean hasNewerTimestamp(
- @NonNull final Context context, @NonNull final SessionInfo info) {
- return PackageManagerHelper.getSessionCreatedTimeInMillis(info)
- > Utilities.getDevicePrefs(context).getLong(
- KEY_INSTALL_SESSION_CREATED_TIMESTAMP, 0);
- }
-
public boolean promiseIconAddedForId(int sessionId) {
return mPromiseIconIds.contains(sessionId);
}
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
index 74a5a31..179061f 100644
--- a/src/com/android/launcher3/pm/PinRequestHelper.java
+++ b/src/com/android/launcher3/pm/PinRequestHelper.java
@@ -30,8 +30,8 @@
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.icons.ShortcutCachingLogic;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
public class PinRequestHelper {
diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
index ac0e065..40d7031 100644
--- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
@@ -40,9 +40,9 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index f723256..5aab41a 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.content.Intent;
-import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -107,22 +106,6 @@
}
/**
- * Returns true if any user profile has quiet mode enabled.
- */
- public boolean isAnyProfileQuietModeEnabled() {
- List<UserHandle> userProfiles = getUserProfiles();
- for (UserHandle userProfile : userProfiles) {
- if (Process.myUserHandle().equals(userProfile)) {
- continue;
- }
- if (mUserManager.isQuietModeEnabled(userProfile)) {
- return true;
- }
- }
- return false;
- }
-
- /**
* @see UserManager#getSerialNumberForUser(UserHandle)
*/
public long getSerialNumberForUser(UserHandle user) {
@@ -160,16 +143,4 @@
List<UserHandle> users = mUserManager.getUserProfiles();
return users == null ? Collections.emptyList() : users;
}
-
- /**
- * Returns true is there is at least one user profile enabled
- */
- public boolean hasWorkProfile() {
- synchronized (this) {
- if (mUsers != null) {
- return mUsers.size() > 1;
- }
- }
- return getUserProfiles().size() > 1;
- }
}
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 18bc55a..d5b32fc 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -293,8 +293,8 @@
// gravity to CENTER_HORIZONTAL, but continue below to update y.
} else {
boolean canBeLeftAligned = x + width + insets.left
- < dragLayer.getRight() - insets.right;
- boolean canBeRightAligned = x > dragLayer.getLeft() + insets.left;
+ < dragLayer.getWidth() - insets.right;
+ boolean canBeRightAligned = x > insets.left;
boolean alignmentStillValid = mIsLeftAligned && canBeLeftAligned
|| !mIsLeftAligned && canBeRightAligned;
if (!alignmentStillValid) {
@@ -367,8 +367,10 @@
super.onLayout(changed, l, t, r, b);
// enforce contained is within screen
- ViewGroup dragLayer = getPopupContainer();
- if (getTranslationX() + l < 0 || getTranslationX() + r > dragLayer.getWidth()) {
+ BaseDragLayer dragLayer = getPopupContainer();
+ Rect insets = dragLayer.getInsets();
+ if (getTranslationX() + l < insets.left
+ || getTranslationX() + r > dragLayer.getWidth() - insets.right) {
// If we are still off screen, center horizontally too.
mGravity |= Gravity.CENTER_HORIZONTAL;
}
@@ -387,6 +389,11 @@
return Pair.create(this, "");
}
+ @Override
+ protected View getAccessibilityInitialFocusView() {
+ return getChildCount() > 0 ? getChildAt(0) : this;
+ }
+
private void animateOpen() {
setVisibility(View.VISIBLE);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 406e1b2..614cf14 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -16,6 +16,7 @@
package com.android.launcher3.popup;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
@@ -49,10 +50,9 @@
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
import com.android.launcher3.dot.DotInfo;
@@ -60,6 +60,9 @@
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
@@ -397,6 +400,9 @@
} else if (view instanceof ImageView) {
// Only the system shortcut icon shows on a gray background header.
info.setIconAndContentDescriptionFor((ImageView) view);
+ if (Utilities.ATLEAST_OREO) {
+ view.setTooltipText(view.getContentDescription());
+ }
}
view.setTag(info);
view.setOnClickListener(info);
@@ -671,8 +677,10 @@
iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
+ WorkspaceItemInfo itemInfo = sv.getFinalInfo();
+ itemInfo.container = CONTAINER_SHORTCUTS;
DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView,
- mContainer, sv.getFinalInfo(),
+ mContainer, itemInfo,
new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
new DragOptions());
dv.animateShift(-iconShift.x, -iconShift.y);
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 1092c7b..5a5f668 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -23,9 +23,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.ComponentKey;
@@ -33,6 +33,7 @@
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.widget.WidgetListRowEntry;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -238,6 +239,11 @@
}).collect(Collectors.toList());
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "PopupDataProvider:");
+ writer.println(prefix + "\tmPackageUserToDotInfos:" + mPackageUserToDotInfos);
+ }
+
public interface PopupDataChangeListener {
PopupDataChangeListener INSTANCE = new PopupDataChangeListener() { };
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index fdcf04f..5ed6f2e 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -16,6 +16,8 @@
package com.android.launcher3.popup;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
+
import android.content.ComponentName;
import android.content.pm.ShortcutInfo;
import android.os.Handler;
@@ -25,10 +27,10 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.notification.NotificationListener;
@@ -142,7 +144,7 @@
infos = Collections.emptyList();
} else {
infos = notificationListener.getNotificationsForKeys(notificationKeys).stream()
- .map(sbn -> new NotificationInfo(launcher, sbn))
+ .map(sbn -> new NotificationInfo(launcher, sbn, originalInfo))
.collect(Collectors.toList());
}
uiHandler.post(() -> container.applyNotificationInfos(infos));
@@ -160,6 +162,7 @@
final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, launcher);
cache.getUnbadgedShortcutIcon(si, shortcut);
si.rank = i;
+ si.container = CONTAINER_SHORTCUTS;
final DeepShortcutView view = shortcutViews.get(i);
uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 8751202..61829c0 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -16,6 +16,7 @@
package com.android.launcher3.popup;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.annotation.TargetApi;
@@ -33,9 +34,9 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@TargetApi(Build.VERSION_CODES.Q)
@@ -76,6 +77,8 @@
@Override
public void onClick(View view) {
AbstractFloatingView.closeAllOpenViews(mTarget);
+ mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
+ .log(LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP);
final String actionIdentity = mAction.getTitle() + ", "
+ mItemInfo.getTargetComponent().getPackageName();
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 21c5ac5..fd292a3 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,5 +1,7 @@
package com.android.launcher3.popup;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP;
import android.app.ActivityOptions;
import android.content.Context;
@@ -14,11 +16,11 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.InstantAppResolver;
@@ -27,6 +29,7 @@
import com.android.launcher3.widget.WidgetsBottomSheet;
import java.util.List;
+
/**
* Represents a system shortcut for a given app. The shortcut should have a label and icon, and an
* onClickListener that depends on the item that the shortcut services.
@@ -103,7 +106,6 @@
};
public static class Widgets extends SystemShortcut<Launcher> {
-
public Widgets(Launcher target, ItemInfo itemInfo) {
super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo);
}
@@ -117,6 +119,8 @@
widgetsBottomSheet.populateAndShow(mItemInfo);
mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
ControlType.WIDGETS_BUTTON, view);
+ mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
+ .log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP);
}
}
@@ -137,6 +141,8 @@
mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
ControlType.APPINFO_TARGET, view);
+ mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
+ .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP);
}
}
@@ -145,8 +151,9 @@
&& ((WorkspaceItemInfo) itemInfo).hasStatusFlag(
WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI);
boolean isInstantApp = false;
- if (itemInfo instanceof com.android.launcher3.AppInfo) {
- com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo;
+ if (itemInfo instanceof com.android.launcher3.model.data.AppInfo) {
+ com.android.launcher3.model.data.AppInfo
+ appInfo = (com.android.launcher3.model.data.AppInfo) itemInfo;
isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
}
boolean enabled = supportsWebUI || isInstantApp;
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index 732fb0b..a5462a6 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -37,7 +37,6 @@
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.DefaultLayoutParser;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -46,6 +45,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 0fe3673..53183bf 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -16,7 +16,6 @@
package com.android.launcher3.provider;
-import static com.android.launcher3.pm.InstallSessionHelper.KEY_INSTALL_SESSION_CREATED_TIMESTAMP;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import android.app.backup.BackupManager;
@@ -35,13 +34,13 @@
import com.android.launcher3.AppWidgetsRestoredReceiver;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.GridBackupTable;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LogConfig;
@@ -87,13 +86,10 @@
*/
public static boolean restoreIfPossible(@NonNull Context context,
@NonNull DatabaseHelper helper, @NonNull BackupManager backupManager) {
- Utilities.getDevicePrefs(context).edit().putLong(
- KEY_INSTALL_SESSION_CREATED_TIMESTAMP, System.currentTimeMillis()).apply();
final SQLiteDatabase db = helper.getWritableDatabase();
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
RestoreDbTask task = new RestoreDbTask();
task.restoreWorkspace(context, db, helper, backupManager);
- task.restoreAppWidgetIdsIfExists(context);
t.commit();
return true;
} catch (Exception e) {
@@ -107,7 +103,6 @@
*/
private void backupWorkspace(Context context, SQLiteDatabase db) throws Exception {
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
- // TODO(pinyaoting): Support backing up workspace with multiple grid options.
new GridBackupTable(context, db, idp.numHotseatIcons, idp.numColumns, idp.numRows)
.doBackup(getDefaultProfileId(db), GridBackupTable.OPTION_REQUIRES_SANITIZATION);
}
@@ -115,13 +110,17 @@
private void restoreWorkspace(@NonNull Context context, @NonNull SQLiteDatabase db,
@NonNull DatabaseHelper helper, @NonNull BackupManager backupManager)
throws Exception {
- // TODO(pinyaoting): Support restoring workspace with multiple grid options.
final InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
GridBackupTable backupTable = new GridBackupTable(context, db, idp.numHotseatIcons,
idp.numColumns, idp.numRows);
if (backupTable.restoreFromRawBackupIfAvailable(getDefaultProfileId(db))) {
- sanitizeDB(helper, db, backupManager);
+ int itemsDeleted = sanitizeDB(helper, db, backupManager);
LauncherAppState.getInstance(context).getModel().forceReload();
+ restoreAppWidgetIdsIfExists(context);
+ if (itemsDeleted == 0) {
+ // all the items are restored, we no longer need the backup table
+ dropTable(db, Favorites.BACKUP_TABLE_NAME);
+ }
}
}
@@ -132,8 +131,10 @@
* the restored apps get installed.
* 3. If the user serial for any restored profile is different than that of the previous
* device, update the entries to the new profile id.
+ *
+ * @return number of items deleted.
*/
- private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db, BackupManager backupManager)
+ private int sanitizeDB(DatabaseHelper helper, SQLiteDatabase db, BackupManager backupManager)
throws Exception {
// Primary user ids
long myProfileId = helper.getDefaultUserSerial();
@@ -210,6 +211,7 @@
if (myProfileId != oldProfileId) {
changeDefaultColumn(db, myProfileId);
}
+ return itemsDeleted;
}
/**
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
index 54b7fb9..e9058c3 100644
--- a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -30,12 +30,12 @@
import android.widget.BaseAdapter;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AppInfoComparator;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.ComponentKey;
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 1cc01f4..21ad275 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -28,18 +28,18 @@
import android.view.inputmethod.InputMethodManager;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.PromiseAppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
@@ -89,17 +89,16 @@
if (mDragLayer != null) {
return;
}
- InvariantDeviceProfile mainIdp = LauncherAppState.getIDP(this);
InvariantDeviceProfile currentDisplayIdp =
new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay());
- // Pick the device profile with the smaller icon size so that the cached icons are
- // shown properly
- if (mainIdp.iconBitmapSize <= currentDisplayIdp.iconBitmapSize) {
- mDeviceProfile = mainIdp.getDeviceProfile(this).copy(this);
- } else {
- mDeviceProfile = currentDisplayIdp.getDeviceProfile(this);
- }
+ // Disable transpose layout and use multi-window mode so that the icons are scaled properly
+ mDeviceProfile = currentDisplayIdp.getDeviceProfile(this)
+ .toBuilder(this)
+ .setMultiWindowMode(true)
+ .setTransposeLayoutWithOrientation(false)
+ .build();
+ mDeviceProfile.autoResizeAllAppsCells();
setContentView(R.layout.secondary_launcher);
mDragLayer = findViewById(R.id.drag_layer);
@@ -197,6 +196,9 @@
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
@Override
+ public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
+
+ @Override
public void bindScreens(IntArray orderedScreenIds) { }
@Override
@@ -294,8 +296,8 @@
}
@Override
- public void bindAllApplications(AppInfo[] apps) {
- mAppsView.getAppsStore().setApps(apps);
+ public void bindAllApplications(AppInfo[] apps, int flags) {
+ mAppsView.getAppsStore().setApps(apps, flags);
}
public PopupDataProvider getPopupDataProvider() {
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index 936d377..40630d3 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -30,9 +30,9 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.TouchController;
@@ -117,10 +117,12 @@
if (child == mAppsView) {
int padding = 2 * (grid.desiredWorkspaceLeftRightMarginPx
+ grid.cellLayoutPaddingLeftRightPx);
- int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding;
+ int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding;
int appsWidth = Math.min(width, maxWidth);
- int appsHeight = Math.round(appsWidth * (float) height / (float) width);
+
+ int maxHeight = grid.allAppsCellHeightPx * idp.numAllAppsColumns + padding;
+ int appsHeight = Math.min(height, maxHeight);
mAppsView.measure(
makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY));
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index 8dc2e61..b12d04f 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -216,17 +216,45 @@
if (launchSandboxIntent.resolveActivity(context.getPackageManager()) == null) {
return;
}
- PreferenceCategory sandboxCategory = newCategory("Sandbox");
- Preference launchSandboxPreference = new Preference(context);
- launchSandboxPreference.setKey("launchSandbox");
- launchSandboxPreference.setTitle("Launch Gesture Navigation Sandbox");
- launchSandboxPreference.setSummary(
- "This provides tutorials and a place to practice navigation gestures.");
- launchSandboxPreference.setOnPreferenceClickListener(preference -> {
- startActivity(launchSandboxIntent);
+ PreferenceCategory sandboxCategory = newCategory("Gesture Navigation Sandbox");
+ sandboxCategory.setSummary("Learn and practice navigation gestures");
+ Preference launchBackTutorialPreference = new Preference(context);
+ launchBackTutorialPreference.setKey("launchBackTutorial");
+ launchBackTutorialPreference.setTitle("Launch Back Tutorial");
+ launchBackTutorialPreference.setSummary("Learn how to use the Back gesture");
+ launchBackTutorialPreference.setOnPreferenceClickListener(preference -> {
+ startActivity(launchSandboxIntent.putExtra(
+ "tutorial_type", "RIGHT_EDGE_BACK_NAVIGATION"));
return true;
});
- sandboxCategory.addPreference(launchSandboxPreference);
+ sandboxCategory.addPreference(launchBackTutorialPreference);
+ Preference launchHomeTutorialPreference = new Preference(context);
+ launchHomeTutorialPreference.setKey("launchHomeTutorial");
+ launchHomeTutorialPreference.setTitle("Launch Home Tutorial");
+ launchHomeTutorialPreference.setSummary("Learn how to use the Home gesture");
+ launchHomeTutorialPreference.setOnPreferenceClickListener(preference -> {
+ startActivity(launchSandboxIntent.putExtra("tutorial_type", "HOME_NAVIGATION"));
+ return true;
+ });
+ sandboxCategory.addPreference(launchHomeTutorialPreference);
+ Preference launchOverviewTutorialPreference = new Preference(context);
+ launchOverviewTutorialPreference.setKey("launchOverviewTutorial");
+ launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
+ launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
+ launchOverviewTutorialPreference.setOnPreferenceClickListener(preference -> {
+ startActivity(launchSandboxIntent.putExtra("tutorial_type", "OVERVIEW_NAVIGATION"));
+ return true;
+ });
+ sandboxCategory.addPreference(launchOverviewTutorialPreference);
+ Preference launchAssistantTutorialPreference = new Preference(context);
+ launchAssistantTutorialPreference.setKey("launchAssistantTutorial");
+ launchAssistantTutorialPreference.setTitle("Launch Assistant Tutorial");
+ launchAssistantTutorialPreference.setSummary("Learn how to use the Assistant gesture");
+ launchAssistantTutorialPreference.setOnPreferenceClickListener(preference -> {
+ startActivity(launchSandboxIntent.putExtra("tutorial_type", "ASSISTANT"));
+ return true;
+ });
+ sandboxCategory.addPreference(launchAssistantTutorialPreference);
}
private String toName(String action) {
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 12085c8..d3213a1 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -23,10 +23,7 @@
import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
-import android.content.ComponentName;
-import android.content.Context;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -48,7 +45,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.GridOptionsProvider;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.SecureSettingsObserver;
@@ -71,8 +67,6 @@
private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
- public static final String GRID_OPTIONS_PREFERENCE_KEY = "pref_grid_options";
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -95,26 +89,7 @@
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (GRID_OPTIONS_PREFERENCE_KEY.equals(key)) {
-
- final ComponentName cn = new ComponentName(getApplicationContext(),
- GridOptionsProvider.class);
- Context c = getApplicationContext();
- int oldValue = c.getPackageManager().getComponentEnabledSetting(cn);
- int newValue;
- if (Utilities.getPrefs(c).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) {
- newValue = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
- } else {
- newValue = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
- }
-
- if (oldValue != newValue) {
- c.getPackageManager().setComponentEnabledSetting(cn, newValue,
- PackageManager.DONT_KILL_APP);
- }
- }
- }
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { }
private boolean startFragment(String fragment, Bundle args, String key) {
if (Utilities.ATLEAST_P && getSupportFragmentManager().isStateSaved()) {
@@ -233,10 +208,6 @@
// Show if plugins are enabled or flag UI is enabled.
return FeatureFlags.showFlagTogglerUi(getContext()) ||
PluginManagerWrapper.hasPlugins(getContext());
- case GRID_OPTIONS_PREFERENCE_KEY:
- return Utilities.isDevelopersOptionsEnabled(getContext()) &&
- Utilities.IS_DEBUG_DEVICE &&
- Utilities.existsStyleWallpapers(getContext());
}
return true;
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 9cc7d8f..e9b92e2 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -28,7 +28,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
/**
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index fa1a85f..3ca9490 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -6,7 +6,7 @@
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ComponentKey;
/**
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
new file mode 100644
index 0000000..daec1d8
--- /dev/null
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -0,0 +1,55 @@
+/*
+ * 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.statemanager;
+
+import android.content.Context;
+
+/**
+ * Interface representing a state of a StatefulActivity
+ */
+public interface BaseState<T extends BaseState> {
+
+ // Flag to indicate that Launcher is non-interactive in this state
+ int FLAG_NON_INTERACTIVE = 1 << 0;
+ int FLAG_DISABLE_RESTORE = 1 << 1;
+
+ static int getFlag(int index) {
+ // reserve few spots to base flags
+ return 1 << (index + 2);
+ }
+
+ /**
+ * @return How long the animation to this state should take (or from this state to NORMAL).
+ */
+ int getTransitionDuration(Context context);
+
+ /**
+ * Returns the state to go back to from this state
+ */
+ T getHistoryForState(T previousState);
+
+ /**
+ * @return true if the state can be persisted across activity restarts.
+ */
+ default boolean shouldDisableRestore() {
+ return hasFlag(FLAG_DISABLE_RESTORE);
+ }
+
+ /**
+ * Returns if the state has the provided flag
+ */
+ boolean hasFlag(int flagMask);
+}
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
similarity index 67%
rename from src/com/android/launcher3/LauncherStateManager.java
rename to src/com/android/launcher3/statemanager/StateManager.java
index e071777..60b87d9 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.statemanager;
-import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
import android.animation.Animator;
@@ -25,93 +24,62 @@
import android.animation.AnimatorSet;
import android.os.Handler;
import android.os.Looper;
+import android.util.Log;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.testing.TestProtocol;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
- * TODO: figure out what kind of tests we can write for this
- *
- * Things to test when changing the following class.
- * - Home from workspace
- * - from center screen
- * - from other screens
- * - Home from all apps
- * - from center screen
- * - from other screens
- * - Back from all apps
- * - from center screen
- * - from other screens
- * - Launch app from workspace and quit
- * - with back
- * - with home
- * - Launch app from all apps and quit
- * - with back
- * - with home
- * - Go to a screen that's not the default, then all
- * apps, and launch and app, and go back
- * - with back
- * -with home
- * - On workspace, long press power and go back
- * - with back
- * - with home
- * - On all apps, long press power and go back
- * - with back
- * - with home
- * - On workspace, power off
- * - On all apps, power off
- * - Launch an app and turn off the screen while in that app
- * - Go back with home key
- * - Go back with back key TODO: make this not go to workspace
- * - From all apps
- * - From workspace
- * - Enter and exit car mode (becase it causes an extra configuration changed)
- * - From all apps
- * - From the center workspace
- * - From another workspace
+ * Class to manage transitions between different states for a StatefulActivity based on different
+ * states
*/
-public class LauncherStateManager {
+public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>> {
public static final String TAG = "StateManager";
private final AnimationState mConfig = new AnimationState();
private final Handler mUiHandler;
- private final Launcher mLauncher;
- private final ArrayList<StateListener> mListeners = new ArrayList<>();
+ private final StatefulActivity<STATE_TYPE> mActivity;
+ private final ArrayList<StateListener<STATE_TYPE>> mListeners = new ArrayList<>();
+ private final STATE_TYPE mBaseState;
// Animators which are run on properties also controlled by state animations.
- private Animator[] mStateElementAnimators;
+ private final AtomicAnimationFactory mAtomicAnimationFactory;
- private StateHandler[] mStateHandlers;
- private LauncherState mState = NORMAL;
+ private StateHandler<STATE_TYPE>[] mStateHandlers;
+ private STATE_TYPE mState;
- private LauncherState mLastStableState = NORMAL;
- private LauncherState mCurrentStableState = NORMAL;
+ private STATE_TYPE mLastStableState;
+ private STATE_TYPE mCurrentStableState;
- private LauncherState mRestState;
+ private STATE_TYPE mRestState;
- public LauncherStateManager(Launcher l) {
+ public StateManager(StatefulActivity<STATE_TYPE> l, STATE_TYPE baseState) {
mUiHandler = new Handler(Looper.getMainLooper());
- mLauncher = l;
+ mActivity = l;
+ mBaseState = baseState;
+ mState = mLastStableState = mCurrentStableState = baseState;
+ mAtomicAnimationFactory = l.createAtomicAnimationFactory();
}
- public LauncherState getState() {
+ public STATE_TYPE getState() {
return mState;
}
- public LauncherState getCurrentStableState() {
+ public STATE_TYPE getCurrentStableState() {
return mCurrentStableState;
}
public void dump(String prefix, PrintWriter writer) {
- writer.println(prefix + "LauncherState:");
+ writer.println(prefix + "StateManager:");
writer.println(prefix + "\tmLastStableState:" + mLastStableState);
writer.println(prefix + "\tmCurrentStableState:" + mCurrentStableState);
writer.println(prefix + "\tmState:" + mState);
@@ -121,7 +89,7 @@
public StateHandler[] getStateHandlers() {
if (mStateHandlers == null) {
- mStateHandlers = mLauncher.createStateHandlers();
+ mStateHandlers = mActivity.createStateHandlers();
}
return mStateHandlers;
}
@@ -138,29 +106,29 @@
* Returns true if the state changes should be animated.
*/
public boolean shouldAnimateStateChange() {
- return !mLauncher.isForceInvisible() && mLauncher.isStarted();
+ return !mActivity.isForceInvisible() && mActivity.isStarted();
}
/**
* @return {@code true} if the state matches the current state and there is no active
* transition to different state.
*/
- public boolean isInStableState(LauncherState state) {
+ public boolean isInStableState(STATE_TYPE state) {
return mState == state && mCurrentStableState == state
&& (mConfig.targetState == null || mConfig.targetState == state);
}
/**
- * @see #goToState(LauncherState, boolean, Runnable)
+ * @see #goToState(STATE_TYPE, boolean, Runnable)
*/
- public void goToState(LauncherState state) {
+ public void goToState(STATE_TYPE state) {
goToState(state, shouldAnimateStateChange());
}
/**
- * @see #goToState(LauncherState, boolean, Runnable)
+ * @see #goToState(STATE_TYPE, boolean, Runnable)
*/
- public void goToState(LauncherState state, boolean animated) {
+ public void goToState(STATE_TYPE state, boolean animated) {
goToState(state, animated, 0, null);
}
@@ -171,21 +139,21 @@
* true otherwise
* @paras onCompleteRunnable any action to perform at the end of the transition, of null.
*/
- public void goToState(LauncherState state, boolean animated, Runnable onCompleteRunnable) {
+ public void goToState(STATE_TYPE state, boolean animated, Runnable onCompleteRunnable) {
goToState(state, animated, 0, onCompleteRunnable);
}
/**
* Changes the Launcher state to the provided state after the given delay.
*/
- public void goToState(LauncherState state, long delay, Runnable onCompleteRunnable) {
+ public void goToState(STATE_TYPE state, long delay, Runnable onCompleteRunnable) {
goToState(state, true, delay, onCompleteRunnable);
}
/**
* Changes the Launcher state to the provided state after the given delay.
*/
- public void goToState(LauncherState state, long delay) {
+ public void goToState(STATE_TYPE state, long delay) {
goToState(state, true, delay, null);
}
@@ -196,7 +164,7 @@
public void reapplyState(boolean cancelCurrentAnimation) {
boolean wasInAnimation = mConfig.currentAnimation != null;
if (cancelCurrentAnimation) {
- cancelAllStateElementAnimation();
+ mAtomicAnimationFactory.cancelAllStateElementAnimation();
cancelAnimation();
}
if (mConfig.currentAnimation == null) {
@@ -209,10 +177,10 @@
}
}
- private void goToState(LauncherState state, boolean animated, long delay,
+ private void goToState(STATE_TYPE state, boolean animated, long delay,
final Runnable onCompleteRunnable) {
- animated &= Utilities.areAnimationsEnabled(mLauncher);
- if (mLauncher.isInState(state)) {
+ animated &= Utilities.areAnimationsEnabled(mActivity);
+ if (mActivity.isInState(state)) {
if (mConfig.currentAnimation == null) {
// Run any queued runnable
if (onCompleteRunnable != null) {
@@ -230,11 +198,11 @@
}
// Cancel the current animation. This will reset mState to mCurrentStableState, so store it.
- LauncherState fromState = mState;
+ STATE_TYPE fromState = mState;
mConfig.reset();
if (!animated) {
- cancelAllStateElementAnimation();
+ mAtomicAnimationFactory.cancelAllStateElementAnimation();
onStateTransitionStart(state);
for (StateHandler handler : getStateHandlers()) {
handler.setState(state);
@@ -263,15 +231,15 @@
}
}
- private void goToStateAnimated(LauncherState state, LauncherState fromState,
+ private void goToStateAnimated(STATE_TYPE state, STATE_TYPE fromState,
Runnable onCompleteRunnable) {
- // Since state NORMAL can be reached from multiple states, just assume that the
+ // Since state mBaseState can be reached from multiple states, just assume that the
// transition plays in reverse and use the same duration as previous state.
- mConfig.duration = state == NORMAL
- ? fromState.getTransitionDuration(mLauncher)
- : state.getTransitionDuration(mLauncher);
+ mConfig.duration = state == mBaseState
+ ? fromState.getTransitionDuration(mActivity)
+ : state.getTransitionDuration(mActivity);
prepareForAtomicAnimation(fromState, state, mConfig);
- AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).getAnim();
+ AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
if (onCompleteRunnable != null) {
animation.addListener(AnimationSuccessListener.forRunnable(onCompleteRunnable));
}
@@ -283,46 +251,46 @@
* - Setting interpolators for various animations included in the state transition.
* - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
*/
- public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
+ public void prepareForAtomicAnimation(STATE_TYPE fromState, STATE_TYPE toState,
StateAnimationConfig config) {
- toState.prepareForAtomicAnimation(mLauncher, fromState, config);
+ mAtomicAnimationFactory.prepareForAtomicAnimation(fromState, toState, config);
}
/**
* Creates an animation representing atomic transitions between the provided states
*/
public AnimatorSet createAtomicAnimation(
- LauncherState fromState, LauncherState toState, StateAnimationConfig config) {
+ STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) {
PendingAnimation builder = new PendingAnimation(config.duration);
prepareForAtomicAnimation(fromState, toState, config);
- for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
+ for (StateHandler handler : mActivity.getStateManager().getStateHandlers()) {
handler.setStateWithAnimation(toState, config, builder);
}
- return builder.getAnim();
+ return builder.buildAnim();
}
/**
* Creates a {@link AnimatorPlaybackController} that can be used for a controlled
* state transition.
* @param state the final state for the transition.
- * @param duration intended duration for normal playback. Use higher duration for better
+ * @param duration intended duration for state playback. Use higher duration for better
* accuracy.
*/
public AnimatorPlaybackController createAnimationToNewWorkspace(
- LauncherState state, long duration) {
+ STATE_TYPE state, long duration) {
return createAnimationToNewWorkspace(state, duration, ANIM_ALL_COMPONENTS);
}
public AnimatorPlaybackController createAnimationToNewWorkspace(
- LauncherState state, long duration, @AnimationFlags int animComponents) {
+ STATE_TYPE state, long duration, @AnimationFlags int animComponents) {
StateAnimationConfig config = new StateAnimationConfig();
config.duration = duration;
config.animFlags = animComponents;
return createAnimationToNewWorkspace(state, config);
}
- public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
+ public AnimatorPlaybackController createAnimationToNewWorkspace(STATE_TYPE state,
StateAnimationConfig config) {
config.userControlled = true;
mConfig.reset();
@@ -332,12 +300,18 @@
return mConfig.playbackController;
}
- private PendingAnimation createAnimationToNewWorkspaceInternal(final LauncherState state) {
- PendingAnimation builder = new PendingAnimation(mConfig.duration);
- for (StateHandler handler : getStateHandlers()) {
- handler.setStateWithAnimation(state, mConfig, builder);
+ private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "createAnimationToNewWorkspaceInternal: "
+ + state);
}
- builder.getAnim().addListener(new AnimationSuccessListener() {
+ PendingAnimation builder = new PendingAnimation(mConfig.duration);
+ if (mConfig.getAnimComponents() != 0) {
+ for (StateHandler handler : getStateHandlers()) {
+ handler.setStateWithAnimation(state, mConfig, builder);
+ }
+ }
+ builder.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
@@ -347,53 +321,43 @@
@Override
public void onAnimationSuccess(Animator animator) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "onAnimationSuccess: " + state);
+ }
onStateTransitionEnd(state);
}
});
- mConfig.setAnimation(builder.getAnim(), state);
+ mConfig.setAnimation(builder.buildAnim(), state);
return builder;
}
- private void onStateTransitionStart(LauncherState state) {
- if (mState != state) {
- mState.onStateDisabled(mLauncher);
- }
+ private void onStateTransitionStart(STATE_TYPE state) {
mState = state;
- mState.onStateEnabled(mLauncher);
- mLauncher.onStateSetStart(mState);
-
- if (state.disablePageClipping) {
- // Only disable clipping if needed, otherwise leave it as previous value.
- mLauncher.getWorkspace().setClipChildren(false);
- }
+ mActivity.onStateSetStart(mState);
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).onStateTransitionStart(state);
}
}
- private void onStateTransitionEnd(LauncherState state) {
+ private void onStateTransitionEnd(STATE_TYPE state) {
// Only change the stable states after the transitions have finished
if (state != mCurrentStableState) {
mLastStableState = state.getHistoryForState(mCurrentStableState);
mCurrentStableState = state;
}
- state.onStateTransitionEnd(mLauncher);
- mLauncher.onStateSetEnd(state);
-
- if (state == NORMAL) {
+ mActivity.onStateSetEnd(state);
+ if (state == mBaseState) {
setRestState(null);
}
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).onStateTransitionComplete(state);
}
-
- AccessibilityManagerCompat.sendStateEventToTest(mLauncher, state.ordinal);
}
- public LauncherState getLastState() {
+ public STATE_TYPE getLastState() {
return mLastStableState;
}
@@ -402,18 +366,18 @@
// The user is doing something. Lets not mess it up
return;
}
- if (mState.disableRestore) {
+ if (mState.shouldDisableRestore()) {
goToState(getRestState());
// Reset history
- mLastStableState = NORMAL;
+ mLastStableState = mBaseState;
}
}
- public LauncherState getRestState() {
- return mRestState == null ? NORMAL : mRestState;
+ public STATE_TYPE getRestState() {
+ return mRestState == null ? mBaseState : mRestState;
}
- public void setRestState(LauncherState restState) {
+ public void setRestState(STATE_TYPE restState) {
mRestState = restState;
}
@@ -461,42 +425,23 @@
mConfig.setAnimation(anim, null);
}
- private void cancelAllStateElementAnimation() {
- if (mStateElementAnimators == null) {
- return;
- }
-
- for (Animator animator : mStateElementAnimators) {
- if (animator != null) {
- animator.cancel();
- }
- }
- }
-
/**
* Cancels a currently running gesture animation
*/
public void cancelStateElementAnimation(int index) {
- if (mStateElementAnimators == null) {
- return;
- }
- if (mStateElementAnimators[index] != null) {
- mStateElementAnimators[index].cancel();
+ if (mAtomicAnimationFactory.mStateElementAnimators[index] != null) {
+ mAtomicAnimationFactory.mStateElementAnimators[index].cancel();
}
}
public Animator createStateElementAnimation(int index, float... values) {
cancelStateElementAnimation(index);
- LauncherAppTransitionManager latm = mLauncher.getAppTransitionManager();
- if (mStateElementAnimators == null) {
- mStateElementAnimators = new Animator[latm.getStateElementAnimationsCount()];
- }
- Animator anim = latm.createStateElementAnimation(index, values);
- mStateElementAnimators[index] = anim;
+ Animator anim = mAtomicAnimationFactory.createStateElementAnimation(index, values);
+ mAtomicAnimationFactory.mStateElementAnimators[index] = anim;
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mStateElementAnimators[index] = null;
+ mAtomicAnimationFactory.mStateElementAnimators[index] = null;
}
});
return anim;
@@ -527,13 +472,14 @@
}
}
- private static class AnimationState extends StateAnimationConfig implements AnimatorListener {
+ private static class AnimationState<STATE_TYPE> extends StateAnimationConfig
+ implements AnimatorListener {
private static final StateAnimationConfig DEFAULT = new StateAnimationConfig();
public AnimatorPlaybackController playbackController;
public AnimatorSet currentAnimation;
- public LauncherState targetState;
+ public STATE_TYPE targetState;
// Id to keep track of config changes, to tie an animation with the corresponding request
public int changeId = 0;
@@ -568,7 +514,7 @@
}
}
- public void setAnimation(AnimatorSet animation, LauncherState targetState) {
+ public void setAnimation(AnimatorSet animation, STATE_TYPE targetState) {
currentAnimation = animation;
this.targetState = targetState;
currentAnimation.addListener(this);
@@ -584,23 +530,68 @@
public void onAnimationRepeat(Animator animator) { }
}
- public interface StateHandler {
+ public interface StateHandler<STATE_TYPE> {
/**
* Updates the UI to {@param state} without any animations
*/
- void setState(LauncherState state);
+ void setState(STATE_TYPE state);
/**
* Sets the UI to {@param state} by animating any changes.
*/
void setStateWithAnimation(
- LauncherState toState, StateAnimationConfig config, PendingAnimation animation);
+ STATE_TYPE toState, StateAnimationConfig config, PendingAnimation animation);
}
- public interface StateListener {
+ public interface StateListener<STATE_TYPE> {
- void onStateTransitionStart(LauncherState toState);
- void onStateTransitionComplete(LauncherState finalState);
+ default void onStateTransitionStart(STATE_TYPE toState) { }
+
+ default void onStateTransitionComplete(STATE_TYPE finalState) { }
+ }
+
+ /**
+ * Factory class to configure and create atomic animations.
+ */
+ public static class AtomicAnimationFactory<STATE_TYPE> {
+
+ protected static final int NEXT_INDEX = 0;
+
+ private final Animator[] mStateElementAnimators;
+
+ /**
+ *
+ * @param sharedElementAnimCount number of animations which run on state properties
+ */
+ public AtomicAnimationFactory(int sharedElementAnimCount) {
+ mStateElementAnimators = new Animator[sharedElementAnimCount];
+ }
+
+ void cancelAllStateElementAnimation() {
+ for (Animator animator : mStateElementAnimators) {
+ if (animator != null) {
+ animator.cancel();
+ }
+ }
+ }
+
+ /**
+ * Creates animations for elements which can be also be part of state transitions. The
+ * actual definition of the animation is up to the app to define.
+ *
+ */
+ public Animator createStateElementAnimation(int index, float... values) {
+ throw new RuntimeException("Unknown gesture animation " + index);
+ }
+
+ /**
+ * Prepares for a non-user controlled animation from fromState to this state. Preparations
+ * include:
+ * - Setting interpolators for various animations included in the state transition.
+ * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
+ */
+ public void prepareForAtomicAnimation(
+ STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) { }
}
}
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
new file mode 100644
index 0000000..dbe5f42
--- /dev/null
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -0,0 +1,172 @@
+/*
+ * 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.statemanager;
+
+import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
+
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.annotation.CallSuper;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.LauncherRootView;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.views.BaseDragLayer;
+
+/**
+ * Abstract activity with state management
+ * @param <STATE_TYPE> Type of state object
+ */
+public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>>
+ extends BaseDraggingActivity {
+
+ public final Handler mHandler = new Handler();
+ private final Runnable mHandleDeferredResume = this::handleDeferredResume;
+ private boolean mDeferredResumePending;
+
+ private LauncherRootView mRootView;
+
+ /**
+ * Create handlers to control the property changes for this activity
+ */
+ protected abstract StateHandler<STATE_TYPE>[] createStateHandlers();
+
+ /**
+ * Returns true if the activity is in the provided state
+ */
+ public boolean isInState(STATE_TYPE state) {
+ return getStateManager().getState() == state;
+ }
+
+ /**
+ * Returns the state manager for this activity
+ */
+ public abstract StateManager<STATE_TYPE> getStateManager();
+
+ protected void inflateRootView(int layoutId) {
+ mRootView = (LauncherRootView) LayoutInflater.from(this).inflate(layoutId, null);
+ mRootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+
+ @Override
+ public final LauncherRootView getRootView() {
+ return mRootView;
+ }
+
+ @Override
+ public <T extends View> T findViewById(int id) {
+ return mRootView.findViewById(id);
+ }
+
+ /**
+ * Called when transition to the state starts
+ */
+ @CallSuper
+ public void onStateSetStart(STATE_TYPE state) {
+ if (mDeferredResumePending) {
+ handleDeferredResume();
+ }
+ }
+
+ /**
+ * Called when transition to state ends
+ */
+ public void onStateSetEnd(STATE_TYPE state) { }
+
+ /**
+ * Creates a factory for atomic state animations
+ */
+ public AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
+ return new AtomicAnimationFactory(0);
+ }
+
+ @Override
+ public void reapplyUi() {
+ reapplyUi(true /* cancelCurrentAnimation */);
+ }
+
+ /**
+ * Re-applies if any state transition is not running, optionally cancelling
+ * the transition if requested.
+ */
+ public void reapplyUi(boolean cancelCurrentAnimation) {
+ getRootView().dispatchInsets();
+ getStateManager().reapplyState(cancelCurrentAnimation);
+ }
+
+ @Override
+ protected void onStop() {
+ BaseDragLayer dragLayer = getDragLayer();
+ final boolean wasActive = isUserActive();
+ final STATE_TYPE origState = getStateManager().getState();
+ final int origDragLayerChildCount = dragLayer.getChildCount();
+ super.onStop();
+
+ getStateManager().moveToRestState();
+
+ // Workaround for b/78520668, explicitly trim memory once UI is hidden
+ onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
+
+ if (wasActive) {
+ // The expected condition is that this activity is stopped because the device goes to
+ // sleep and the UI may have noticeable changes.
+ dragLayer.post(() -> {
+ if ((!getStateManager().isInStableState(origState)
+ // The drag layer may be animating (e.g. dismissing QSB).
+ || dragLayer.getAlpha() < 1
+ // Maybe an ArrowPopup is closed.
+ || dragLayer.getChildCount() != origDragLayerChildCount)) {
+ onUiChangedWhileSleeping();
+ }
+ });
+ }
+ }
+
+ /**
+ * Called if the Activity UI changed while the activity was not visible
+ */
+ protected void onUiChangedWhileSleeping() { }
+
+ private void handleDeferredResume() {
+ if (hasBeenResumed() && !getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)) {
+ onDeferredResumed();
+ addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED);
+
+ mDeferredResumePending = false;
+ } else {
+ mDeferredResumePending = true;
+ }
+ }
+
+ /**
+ * Called want the activity has stayed resumed for 1 frame.
+ */
+ protected void onDeferredResumed() { }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ mHandler.removeCallbacks(mHandleDeferredResume);
+ Utilities.postAsyncCallback(mHandler, mHandleDeferredResume);
+ }
+}
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index 43f30f1..b8a184f 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -15,10 +15,10 @@
*/
package com.android.launcher3.states;
+import android.content.Context;
+
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.Workspace;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
/**
@@ -26,7 +26,7 @@
*/
public class HintState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_DISABLE_RESTORE
+ private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE
| FLAG_HAS_SYS_UI_SCRIM;
public HintState(int id) {
@@ -34,13 +34,23 @@
}
@Override
- public int getTransitionDuration(Launcher launcher) {
+ public int getTransitionDuration(Context context) {
return 80;
}
@Override
+ protected float getDepthUnchecked(Context context) {
+ return 0.15f;
+ }
+
+ @Override
+ public float getOverviewScrimAlpha(Launcher launcher) {
+ return 0.4f;
+ }
+
+ @Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(0.9f, 0, 0);
+ return new ScaleAndTranslation(0.92f, 0, 0);
}
@Override
@@ -48,16 +58,4 @@
// Treat the QSB as part of the hotseat so they move together.
return getHotseatScaleAndTranslation(launcher);
}
-
- @Override
- public void onStateTransitionEnd(Launcher launcher) {
- LauncherStateManager stateManager = launcher.getStateManager();
- Workspace workspace = launcher.getWorkspace();
- boolean willMoveScreens = workspace.getNextPage() != Workspace.DEFAULT_PAGE;
- stateManager.goToState(NORMAL, true, willMoveScreens ? null
- : launcher.getScrimView()::startDragHandleEducationAnim);
- if (willMoveScreens) {
- workspace.post(workspace::moveToDefaultScreen);
- }
- }
}
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 8bb6a08..5a60f53 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -15,52 +15,43 @@
*/
package com.android.launcher3.states;
-import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
-import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
import android.app.Activity;
import android.content.ContentResolver;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
+import android.database.ContentObserver;
+import android.os.Handler;
import android.provider.Settings;
-import android.view.MotionEvent;
-import android.view.Surface;
+import android.util.Log;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.UiThreadHelper;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Utility class to manage launcher rotation
*/
public class RotationHelper implements OnSharedPreferenceChangeListener {
+ private static final String TAG = "RotationHelper";
+
public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
- public static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
private final ContentResolver mContentResolver;
+ private boolean mSystemAutoRotateEnabled;
- /**
- * Listener to receive changes when {@link #FIXED_ROTATION_TRANSFORM_SETTING_NAME} flag changes.
- */
- public interface ForcedRotationChangedListener {
- void onForcedRotationChanged(boolean isForcedRotation);
- }
+ private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAutoRotateSetting();
+ }
+ };
public static boolean getAllowRotationDefaultValue() {
// If the device's pixel density was scaled (usually via settings for A11y), use the
@@ -77,12 +68,9 @@
private final Activity mActivity;
private final SharedPreferences mSharedPrefs;
- private final SharedPreferences mFeatureFlagsPrefs;
private boolean mIgnoreAutoRotateSettings;
- private boolean mAutoRotateEnabled;
- private boolean mForcedRotation;
- private List<ForcedRotationChangedListener> mForcedRotationChangedListeners = new ArrayList<>();
+ private boolean mHomeRotationEnabled;
/**
* Rotation request made by
@@ -113,67 +101,35 @@
if (!mIgnoreAutoRotateSettings) {
mSharedPrefs = Utilities.getPrefs(mActivity);
mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
- mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+ mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
getAllowRotationDefaultValue());
} else {
mSharedPrefs = null;
}
mContentResolver = activity.getContentResolver();
- mFeatureFlagsPrefs = Utilities.getFeatureFlagsPrefs(mActivity);
- mFeatureFlagsPrefs.registerOnSharedPreferenceChangeListener(this);
- updateForcedRotation(true);
}
- /**
- * @param setValueFromPrefs If true, then {@link #mForcedRotation} will get set to the value
- * from the home developer settings. Otherwise it will not.
- * This is primarily to allow tests to set their own conditions.
- */
- private void updateForcedRotation(boolean setValueFromPrefs) {
- boolean isForcedRotation = mFeatureFlagsPrefs
- .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
- && !getAllowRotationDefaultValue();
- if (mForcedRotation == isForcedRotation) {
- return;
+ private void updateAutoRotateSetting() {
+ int autoRotateEnabled = 0;
+ try {
+ autoRotateEnabled = Settings.System.getInt(mContentResolver,
+ Settings.System.ACCELEROMETER_ROTATION);
+ } catch (Settings.SettingNotFoundException e) {
+ Log.e(TAG, "autorotate setting not found", e);
}
- if (setValueFromPrefs) {
- mForcedRotation = isForcedRotation;
- }
- UI_HELPER_EXECUTOR.execute(() -> {
- if (mActivity.checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) {
- Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
- mForcedRotation ? 1 : 0);
- }
- });
- for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
- listener.onForcedRotationChanged(mForcedRotation);
- }
- }
- /**
- * will not be called when first registering the listener.
- */
- public void addForcedRotationCallback(ForcedRotationChangedListener listener) {
- mForcedRotationChangedListeners.add(listener);
- }
-
- public void removeForcedRotationCallback(ForcedRotationChangedListener listener) {
- mForcedRotationChangedListeners.remove(listener);
+ mSystemAutoRotateEnabled = autoRotateEnabled == 1;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
- if (FLAG_ENABLE_FIXED_ROTATION_TRANSFORM.equals(s)) {
- updateForcedRotation(true);
- return;
- }
-
- boolean wasRotationEnabled = mAutoRotateEnabled;
- mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+ boolean wasRotationEnabled = mHomeRotationEnabled;
+ mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
getAllowRotationDefaultValue());
- if (mAutoRotateEnabled != wasRotationEnabled) {
+ if (mHomeRotationEnabled != wasRotationEnabled) {
notifyChange();
+ updateAutoRotateSetting();
}
}
@@ -202,10 +158,6 @@
public void forceAllowRotationForTesting(boolean allowRotation) {
mIgnoreAutoRotateSettings =
allowRotation || mActivity.getResources().getBoolean(R.bool.allow_rotation);
- // TODO(b/150214193) Tests currently expect launcher to be able to be rotated
- // Modify tests for this new behavior
- mForcedRotation = !allowRotation;
- updateForcedRotation(false);
notifyChange();
}
@@ -213,6 +165,11 @@
if (!mInitialized) {
mInitialized = true;
notifyChange();
+
+ mContentResolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
+ false, mSystemAutoRotateObserver);
+ updateAutoRotateSetting();
}
}
@@ -222,8 +179,7 @@
if (mSharedPrefs != null) {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
- mForcedRotationChangedListeners.clear();
- mFeatureFlagsPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
}
}
@@ -233,10 +189,7 @@
}
final int activityFlags;
- if (mForcedRotation) {
- // TODO(b/150214193) Properly address this
- activityFlags = SCREEN_ORIENTATION_PORTRAIT;
- } else if (mStateHandlerRequest != REQUEST_NONE) {
+ if (mStateHandlerRequest != REQUEST_NONE) {
activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
} else if (mCurrentTransitionRequest != REQUEST_NONE) {
@@ -245,7 +198,7 @@
} else if (mCurrentStateRequest == REQUEST_LOCK) {
activityFlags = SCREEN_ORIENTATION_LOCKED;
} else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
- || mAutoRotateEnabled) {
+ || mHomeRotationEnabled) {
activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
} else {
// If auto rotation is off, allow rotation on the activity, in case the user is using
@@ -258,39 +211,6 @@
}
}
- public static int getDegreesFromRotation(int rotation) {
- int degrees;
- switch (rotation) {
- case Surface.ROTATION_90:
- degrees = 90;
- break;
- case Surface.ROTATION_180:
- degrees = 180;
- break;
- case Surface.ROTATION_270:
- degrees = 270;
- break;
- case Surface.ROTATION_0:
- default:
- degrees = 0;
- break;
- }
- return degrees;
- }
-
- public static int getRotationFromDegrees(float degrees) {
- int threshold = 70;
- if (degrees >= (360 - threshold) || degrees < (threshold)) {
- return Surface.ROTATION_0;
- } else if (degrees < (90 + threshold)) {
- return Surface.ROTATION_270;
- } else if (degrees < 180 + threshold) {
- return Surface.ROTATION_180;
- } else {
- return Surface.ROTATION_90;
- }
- }
-
/**
* @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
* E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
@@ -302,99 +222,12 @@
return delta;
}
- /**
- * For landscape, since the navbar is already in a vertical position, we don't have to do any
- * rotations as the change in Y coordinate is what is read. We only flip the sign of the
- * y coordinate to make it match existing behavior of swipe to the top to go previous
- */
- public static void transformEventForNavBar(MotionEvent ev, boolean inverse) {
- // TODO(b/151269990): Use a temp matrix
- Matrix m = new Matrix();
- m.setScale(1, -1);
- if (inverse) {
- Matrix inv = new Matrix();
- m.invert(inv);
- ev.transform(inv);
- } else {
- ev.transform(m);
- }
- }
-
- /**
- * Creates a matrix to transform the given motion event specified by degrees.
- * If {@param inverse} is {@code true}, the inverse of that matrix will be applied
- */
- public static void transformEvent(float degrees, MotionEvent ev, boolean inverse) {
- Matrix transform = new Matrix();
- // TODO(b/151269990): Use a temp matrix
- transform.setRotate(degrees);
- if (inverse) {
- Matrix inv = new Matrix();
- transform.invert(inv);
- ev.transform(inv);
- } else {
- ev.transform(transform);
- }
- // TODO: Add scaling back in based on degrees
-// if (getWidth() > 0 && getHeight() > 0) {
-// float scale = ((float) getWidth()) / getHeight();
-// transform.postScale(scale, 1 / scale);
-// }
- }
-
- /**
- * TODO(b/149658423): Have {@link com.android.quickstep.OrientationTouchTransformer
- * also use this}
- */
- public static Matrix getRotationMatrix(int screenWidth, int screenHeight, int displayRotation) {
- Matrix m = new Matrix();
- // TODO(b/151269990): Use a temp matrix
- switch (displayRotation) {
- case Surface.ROTATION_0:
- return m;
- case Surface.ROTATION_90:
- m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation));
- m.postTranslate(0, screenWidth);
- break;
- case Surface.ROTATION_270:
- m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation));
- m.postTranslate(screenHeight, 0);
- break;
- }
- return m;
- }
-
- public static void mapRectFromNormalOrientation(RectF src, int screenWidth, int screenHeight,
- int displayRotation) {
- Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
- m.mapRect(src);
- }
-
- public static void mapInverseRectFromNormalOrientation(RectF src, int screenWidth,
- int screenHeight, int displayRotation) {
- Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
- Matrix inverse = new Matrix();
- m.invert(inverse);
- inverse.mapRect(src);
- }
-
- public static void getTargetRectForRotation(Rect srcOut, int screenWidth, int screenHeight,
- int displayRotation) {
- RectF wrapped = new RectF(srcOut);
- Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
- m.mapRect(wrapped);
- wrapped.round(srcOut);
- }
-
- public static boolean isRotationLandscape(int rotation) {
- return rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90;
- }
-
@Override
public String toString() {
return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d,"
- + " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mAutoRotateEnabled=%b]",
+ + " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mHomeRotationEnabled=%b,"
+ + " mSystemAutoRotateEnabled=%b]",
mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
- mIgnoreAutoRotateSettings, mAutoRotateEnabled);
+ mIgnoreAutoRotateSettings, mHomeRotationEnabled, mSystemAutoRotateEnabled);
}
}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 2ba624c..2a4f887 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -15,13 +15,10 @@
*/
package com.android.launcher3.states;
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
-
import android.content.Context;
import android.graphics.Rect;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
@@ -32,16 +29,17 @@
*/
public class SpringLoadedState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_MULTI_PAGE |
- FLAG_DISABLE_ACCESSIBILITY | FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED |
- FLAG_DISABLE_PAGE_CLIPPING | FLAG_PAGE_BACKGROUNDS | FLAG_HIDE_BACK_BUTTON;
+ private static final int STATE_FLAGS = FLAG_MULTI_PAGE
+ | FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE
+ | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_WORKSPACE_HAS_BACKGROUNDS
+ | FLAG_HIDE_BACK_BUTTON;
public SpringLoadedState(int id) {
- super(id, ContainerType.OVERVIEW, STATE_FLAGS);
+ super(id, ContainerType.WORKSPACE, STATE_FLAGS);
}
@Override
- public int getTransitionDuration(Launcher launcher) {
+ public int getTransitionDuration(Context context) {
return 150;
}
@@ -78,7 +76,7 @@
}
@Override
- public float getDepth(Context context) {
+ protected float getDepthUnchecked(Context context) {
return 0.5f;
}
@@ -88,28 +86,7 @@
}
@Override
- public void onStateEnabled(Launcher launcher) {
- Workspace ws = launcher.getWorkspace();
- ws.showPageIndicatorAtCurrentScroll();
- ws.getPageIndicator().setShouldAutoHide(false);
-
- // Prevent any Un/InstallShortcutReceivers from updating the db while we are
- // in spring loaded mode
- InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP);
- launcher.getRotationHelper().setCurrentStateRequest(REQUEST_LOCK);
- }
-
- @Override
public float getWorkspaceScrimAlpha(Launcher launcher) {
return 0.3f;
}
-
- @Override
- public void onStateDisabled(final Launcher launcher) {
- launcher.getWorkspace().getPageIndicator().setShouldAutoHide(true);
-
- // Re-enable any Un/InstallShortcutReceiver and now process any queued items
- InstallShortcutReceiver.disableAndFlushInstallQueue(
- InstallShortcutReceiver.FLAG_DRAG_AND_DROP, launcher);
- }
}
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index 82cde64..f90ad3c 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -37,6 +37,7 @@
PLAY_ATOMIC_OVERVIEW_SCALE,
PLAY_ATOMIC_OVERVIEW_PEEK,
SKIP_OVERVIEW,
+ SKIP_DEPTH_CONTROLLER
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimationFlags {}
@@ -44,6 +45,7 @@
public static final int PLAY_ATOMIC_OVERVIEW_SCALE = 1 << 1;
public static final int PLAY_ATOMIC_OVERVIEW_PEEK = 1 << 2;
public static final int SKIP_OVERVIEW = 1 << 3;
+ public static final int SKIP_DEPTH_CONTROLLER = 1 << 4;
public long duration;
public boolean userControlled;
@@ -67,6 +69,8 @@
ANIM_ALL_APPS_FADE,
ANIM_OVERVIEW_SCRIM_FADE,
ANIM_ALL_APPS_HEADER_FADE,
+ ANIM_OVERVIEW_MODAL,
+ ANIM_DEPTH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimType {}
@@ -83,8 +87,10 @@
public static final int ANIM_ALL_APPS_FADE = 10;
public static final int ANIM_OVERVIEW_SCRIM_FADE = 11;
public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
+ public static final int ANIM_OVERVIEW_MODAL = 13;
+ public static final int ANIM_DEPTH = 14;
- private static final int ANIM_TYPES_COUNT = 13;
+ private static final int ANIM_TYPES_COUNT = 15;
private final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index e786f07..38b3712 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -15,27 +15,17 @@
*/
package com.android.launcher3.testing;
-import static android.graphics.Bitmap.Config.ARGB_8888;
-
import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Color;
import android.graphics.Insets;
import android.os.Build;
import android.os.Bundle;
-import android.os.Debug;
-import android.system.Os;
-import android.util.Log;
-import android.view.View;
import android.view.WindowInsets;
-import androidx.annotation.Keep;
-
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
@@ -44,10 +34,7 @@
import com.android.launcher3.R;
import com.android.launcher3.util.ResourceBasedOverride;
-import java.util.LinkedList;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -65,7 +52,6 @@
protected Context mContext;
protected DeviceProfile mDeviceProfile;
protected LauncherAppState mLauncherAppState;
- private static LinkedList mLeaks;
public void init(Context context) {
mContext = context;
@@ -77,15 +63,6 @@
public Bundle call(String method) {
final Bundle response = new Bundle();
switch (method) {
- case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
- return getLauncherUIProperty(Bundle::putInt, l -> {
- final float progress = LauncherState.OVERVIEW.getVerticalProgress(l)
- - LauncherState.ALL_APPS.getVerticalProgress(l);
- final float distance = l.getAllAppsController().getShiftRange() * progress;
- return (int) distance;
- });
- }
-
case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
return getLauncherUIProperty(Bundle::putInt, l -> {
final float progress = LauncherState.NORMAL.getVerticalProgress(l)
@@ -99,14 +76,6 @@
return getUIProperty(Bundle::putBoolean, t -> isLauncherInitialized(), () -> true);
}
- case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
- TestProtocol.sDebugTracing = true;
- break;
-
- case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
- TestProtocol.sDebugTracing = false;
- break;
-
case TestProtocol.REQUEST_FREEZE_APP_LIST:
return getLauncherUIProperty(Bundle::putBoolean, l -> {
l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
@@ -118,11 +87,6 @@
return true;
});
- case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
- return getLauncherUIProperty(Bundle::putInt,
- l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
- }
-
case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
return getLauncherUIProperty(Bundle::putInt,
l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
@@ -137,59 +101,19 @@
}, this::getCurrentActivity);
}
- case TestProtocol.REQUEST_PID: {
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
- break;
- }
-
- case TestProtocol.REQUEST_TOTAL_PSS_KB: {
- runGcAndFinalizersSync();
- Debug.MemoryInfo mem = new Debug.MemoryInfo();
- Debug.getMemoryInfo(mem);
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss());
- break;
- }
-
- case TestProtocol.REQUEST_JAVA_LEAK: {
- if (mLeaks == null) mLeaks = new LinkedList();
-
- // Allocate and dirty the memory.
- final int leakSize = 1024 * 1024;
- final byte[] bytes = new byte[leakSize];
- for (int i = 0; i < leakSize; i += 239) {
- bytes[i] = (byte) (i % 256);
- }
- mLeaks.add(bytes);
- break;
- }
-
- case TestProtocol.REQUEST_NATIVE_LEAK: {
- if (mLeaks == null) mLeaks = new LinkedList();
-
- // Allocate and dirty a bitmap.
- final Bitmap bitmap = Bitmap.createBitmap(512, 512, ARGB_8888);
- bitmap.eraseColor(Color.RED);
- mLeaks.add(bitmap);
- break;
- }
-
- case TestProtocol.REQUEST_VIEW_LEAK: {
- if (mLeaks == null) mLeaks = new LinkedList();
- mLeaks.add(new View(mContext));
- break;
- }
-
case TestProtocol.REQUEST_ICON_HEIGHT: {
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
mDeviceProfile.allAppsCellHeightPx);
- break;
+ return response;
}
case TestProtocol.REQUEST_MOCK_SENSOR_ROTATION:
TestProtocol.sDisableSensorRotation = true;
- break;
+ return response;
+
+ default:
+ return null;
}
- return response;
}
protected boolean isLauncherInitialized() {
@@ -201,22 +125,6 @@
return Launcher.ACTIVITY_TRACKER.getCreatedActivity();
}
- private static void runGcAndFinalizersSync() {
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
-
- final CountDownLatch fence = new CountDownLatch(1);
- createFinalizationObserver(fence);
- try {
- do {
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
- } while (!fence.await(100, TimeUnit.MILLISECONDS));
- } catch (InterruptedException ex) {
- throw new RuntimeException(ex);
- }
- }
-
/**
* Returns the result by getting a Launcher property on UI thread
*/
@@ -257,21 +165,4 @@
*/
void set(Bundle b, String key, T value);
}
-
- // Create the observer in the scope of a method to minimize the chance that
- // it remains live in a DEX/machine register at the point of the fence guard.
- // This must be kept to avoid R8 inlining it.
- @Keep
- private static void createFinalizationObserver(CountDownLatch fence) {
- new Object() {
- @Override
- protected void finalize() throws Throwable {
- try {
- fence.countDown();
- } finally {
- super.finalize();
- }
- }
- };
- }
}
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index d522d81..51e0819 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -21,9 +21,17 @@
import com.android.launcher3.Utilities;
+import java.util.function.BiConsumer;
+
public final class TestLogging {
+ private static BiConsumer<String, String> sEventConsumer;
+
private static void recordEventSlow(String sequence, String event) {
Log.d(TestProtocol.TAPL_EVENTS_TAG, sequence + " / " + event);
+ final BiConsumer<String, String> eventConsumer = sEventConsumer;
+ if (eventConsumer != null) {
+ eventConsumer.accept(sequence, event);
+ }
}
public static void recordEvent(String sequence, String event) {
@@ -43,4 +51,8 @@
recordEventSlow(sequence, message + ": " + event);
}
}
+
+ static void setEventConsumer(BiConsumer<String, String> consumer) {
+ sEventConsumer = consumer;
+ }
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 3181752..3ca08fd 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -28,10 +28,11 @@
public static final int SPRING_LOADED_STATE_ORDINAL = 1;
public static final int OVERVIEW_STATE_ORDINAL = 2;
public static final int OVERVIEW_PEEK_STATE_ORDINAL = 3;
- public static final int QUICK_SWITCH_STATE_ORDINAL = 4;
- public static final int ALL_APPS_STATE_ORDINAL = 5;
- public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
- public static final int HINT_STATE_ORDINAL = 7;
+ public static final int OVERVIEW_MODAL_TASK_STATE_ORDINAL = 4;
+ public static final int QUICK_SWITCH_STATE_ORDINAL = 5;
+ public static final int ALL_APPS_STATE_ORDINAL = 6;
+ public static final int BACKGROUND_APP_STATE_ORDINAL = 7;
+ public static final int HINT_STATE_ORDINAL = 8;
public static final String TAPL_EVENTS_TAG = "TaplEvents";
public static final String SEQUENCE_MAIN = "Main";
public static final String SEQUENCE_TIS = "TIS";
@@ -47,6 +48,8 @@
return "Overview";
case OVERVIEW_PEEK_STATE_ORDINAL:
return "OverviewPeek";
+ case OVERVIEW_MODAL_TASK_STATE_ORDINAL:
+ return "OverviewModal";
case QUICK_SWITCH_STATE_ORDINAL:
return "QuickSwitch";
case ALL_APPS_STATE_ORDINAL:
@@ -56,7 +59,7 @@
case HINT_STATE_ORDINAL:
return "Hint";
default:
- return null;
+ return "Unknown";
}
}
@@ -85,6 +88,10 @@
public static final String REQUEST_NATIVE_LEAK = "native-leak";
public static final String REQUEST_VIEW_LEAK = "view-leak";
public static final String REQUEST_RECENT_TASKS_LIST = "recent-tasks-list";
+ public static final String REQUEST_START_EVENT_LOGGING = "start-event-logging";
+ public static final String REQUEST_GET_TEST_EVENTS = "get-test-events";
+ public static final String REQUEST_STOP_EVENT_LOGGING = "stop-event-logging";
+ public static final String REQUEST_CLEAR_DATA = "clear-data";
public static boolean sDebugTracing = false;
public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
@@ -96,9 +103,7 @@
public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
-
- public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
- public static final String APP_NOT_DISABLED = "b/139891609";
- public static final String NO_SCROLL_END_WIDGETS = "b/152354290";
- public static final String NO_START_FROM_RECENTS = "b/152658211";
+ public static final String PAUSE_NOT_DETECTED = "b/139891609";
+ public static final String OVERIEW_NOT_ALLAPPS = "b/156095088";
+ public static final String NO_SWIPE_TO_HOME = "b/158017601";
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index cbc5257..3c78b08 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -21,6 +21,8 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.PLAY_NON_ATOMIC;
@@ -31,6 +33,7 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.os.SystemClock;
+import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
@@ -41,11 +44,14 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.TouchController;
@@ -162,6 +168,9 @@
@Override
public final boolean onControllerTouchEvent(MotionEvent ev) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "onControllerTouchEvent");
+ }
return mDetector.onTouchEvent(ev);
}
@@ -194,6 +203,10 @@
mFromState = newFromState;
mToState = newToState;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "reinitCurrentAnimation: "
+ + newToState.ordinal + " " + getClass().getSimpleName());
+ }
mStartProgress = 0;
mPassedOverviewAtomicThreshold = false;
@@ -233,6 +246,7 @@
public void onDragStart(boolean start, float startDisplacement) {
mStartState = mLauncher.getStateManager().getState();
mIsLogContainerSet = false;
+
if (mCurrentAnimation == null) {
mFromState = mStartState;
mToState = null;
@@ -250,6 +264,10 @@
}
mCanBlockFling = mFromState == NORMAL;
mFlingBlockCheck.unblockFling();
+ // Must be called after all the animation controllers have been paused
+ if (mToState == ALL_APPS || mToState == NORMAL) {
+ mLauncher.getAllAppsController().onDragStart(mToState == ALL_APPS);
+ }
}
@Override
@@ -284,11 +302,11 @@
public boolean onDrag(float displacement, MotionEvent ev) {
if (!mIsLogContainerSet) {
if (mStartState == ALL_APPS) {
- mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
+ mStartContainerType = ContainerType.ALLAPPS;
} else if (mStartState == NORMAL) {
mStartContainerType = getLogContainerTypeForNormalState(ev);
} else if (mStartState == OVERVIEW) {
- mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
+ mStartContainerType = ContainerType.TASKSWITCHER;
}
mIsLogContainerSet = true;
}
@@ -296,6 +314,9 @@
}
protected void updateProgress(float fraction) {
+ if (mCurrentAnimation == null) {
+ return;
+ }
mCurrentAnimation.setPlayFraction(fraction);
if (mAtomicComponentsController != null) {
// Make sure we don't divide by 0, and have at least a small runway.
@@ -542,10 +563,22 @@
// Transition complete. log the action
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
getDirectionForLog(), mDetector.getDownX(), mDetector.getDownY(),
- mStartContainerType,
- mStartState.containerType,
+ mStartContainerType /* e.g., hotseat */,
+ mStartState.containerType /* e.g., workspace */,
targetState.containerType,
mLauncher.getWorkspace().getCurrentPage());
+ mLauncher.getStatsLogManager().logger()
+ .withSrcState(StatsLogManager.containerTypeToAtomState(mStartState.containerType))
+ .withDstState(StatsLogManager.containerTypeToAtomState(targetState.containerType))
+ .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setWorkspace(
+ LauncherAtom.WorkspaceContainer.newBuilder()
+ .setPageIndex(mLauncher.getWorkspace().getCurrentPage()))
+ .build())
+ .log(StatsLogManager.getLauncherAtomEvent(mStartState.containerType,
+ targetState.containerType, mToState.ordinal > mFromState.ordinal
+ ? LAUNCHER_UNKNOWN_SWIPEUP
+ : LAUNCHER_UNKNOWN_SWIPEDOWN));
}
protected void clearState() {
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 1276ece..01b33d8 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -26,6 +26,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.testing.TestProtocol;
+
import java.util.LinkedList;
import java.util.Queue;
@@ -173,6 +175,9 @@
if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {
setState(ScrollState.DRAGGING);
}
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "before report dragging");
+ }
if (mState == ScrollState.DRAGGING) {
reportDragging(ev);
}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index f7ce160..8486666 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -15,14 +15,15 @@
*/
package com.android.launcher3.touch;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
import android.app.AlertDialog;
import android.content.Intent;
@@ -38,20 +39,23 @@
import androidx.annotation.Nullable;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.PromiseAppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.widget.PendingAppWidgetHostView;
@@ -109,6 +113,8 @@
if (!folder.isOpen() && !folder.isDestroyed()) {
// Open the requested folder
folder.animateOpen();
+ StatsLogManager.newInstance(v.getContext()).logger().withItemInfo(folder.mInfo)
+ .log(LAUNCHER_FOLDER_OPEN);
}
}
@@ -224,8 +230,9 @@
// Check for abandoned promise
if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
- String packageName = shortcut.intent.getComponent() != null ?
- shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
+ String packageName = shortcut.getIntent().getComponent() != null
+ ? shortcut.getIntent().getComponent().getPackageName()
+ : shortcut.getIntent().getPackage();
if (!TextUtils.isEmpty(packageName)) {
onClickPendingAppItem(v, launcher, packageName,
shortcut.hasStatusFlag(WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE));
@@ -239,6 +246,8 @@
private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher,
@Nullable String sourceContainer) {
+ TestLogging.recordEvent(
+ TestProtocol.SEQUENCE_MAIN, "start: startAppShortcutOrInfoActivity");
Intent intent;
if (item instanceof PromiseAppInfo) {
PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 8537bdf..7baeab8 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -28,11 +28,11 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DropTarget;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 6715bc1..48c7734 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -16,6 +16,7 @@
package com.android.launcher3.touch;
+import static android.widget.ListPopupWindow.WRAP_CONTENT;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
@@ -30,9 +31,8 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.PagedView;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.OverScroller;
@@ -65,16 +65,21 @@
}
@Override
- public void getCurveProperties(PagedView view, Rect mInsets, CurveProperties out) {
+ public void getCurveProperties(PagedView view, Rect insets, CurveProperties out) {
out.scroll = view.getScrollY();
out.halfPageSize = view.getNormalChildHeight() / 2;
out.halfScreenSize = view.getMeasuredHeight() / 2;
- out.screenCenter = mInsets.top + view.getPaddingTop() + out.scroll + out.halfPageSize;
+ out.screenCenter = insets.top + view.getPaddingTop() + out.scroll + out.halfPageSize;
}
@Override
- public boolean isGoingUp(float displacement) {
- return displacement > 0;
+ public boolean isGoingUp(float displacement, boolean isRtl) {
+ return isRtl ? displacement < 0 : displacement > 0;
+ }
+
+ @Override
+ public boolean isLayoutNaturalToLauncher() {
+ return false;
}
@Override
@@ -115,32 +120,21 @@
}
@Override
- public int getPrimarySize(Rect rect) {
- return rect.height();
- }
-
- @Override
public float getPrimarySize(RectF rect) {
return rect.height();
}
@Override
+ public int getClearAllScrollOffset(View view, boolean isRtl) {
+ return (isRtl ? view.getPaddingBottom() : - view.getPaddingTop()) / 2;
+ }
+
+ @Override
public int getSecondaryDimension(View view) {
return view.getWidth();
}
@Override
- public ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view) {
- float offscreenTranslationY = dp.heightPx - view.getPaddingTop();
- return new ScaleAndTranslation(1f, 0f, offscreenTranslationY);
- }
-
- @Override
- public float getTranslationValue(ScaleAndTranslation scaleAndTranslation) {
- return scaleAndTranslation.translationY;
- }
-
- @Override
public FloatProperty<View> getPrimaryViewTranslate() {
return VIEW_TRANSLATE_Y;
}
@@ -157,11 +151,6 @@
}
@Override
- public float getViewCenterPosition(View view) {
- return view.getTop() + view.getTranslationY();
- }
-
- @Override
public int getPrimaryScroll(View view) {
return view.getScrollY();
}
@@ -187,16 +176,8 @@
}
@Override
- public void offsetTaskRect(RectF rect, float value, int displayRotation) {
- if (displayRotation == Surface.ROTATION_0) {
- rect.offset(0, value);
- } else if (displayRotation == Surface.ROTATION_90) {
- rect.offset(value, 0);
- } else if (displayRotation == Surface.ROTATION_180) {
- rect.offset(0, -value);
- } else {
- rect.offset(-value, 0);
- }
+ public int getRotation() {
+ return Surface.ROTATION_90;
}
@Override
@@ -205,6 +186,11 @@
}
@Override
+ public float getChildStartWithTranslation(View view) {
+ return view.getTop() + view.getTranslationY();
+ }
+
+ @Override
public int getCenterForPage(View view, Rect insets) {
return (view.getPaddingLeft() + view.getMeasuredWidth() + insets.left
- insets.right - view.getPaddingRight()) / 2;
@@ -226,13 +212,40 @@
}
@Override
- public int getShortEdgeLength(DeviceProfile dp) {
- return dp.heightPx;
+ public int getTaskDismissDirectionFactor() {
+ return 1;
}
@Override
- public int getTaskDismissDirectionFactor() {
- return 1;
+ public int getTaskDragDisplacementFactor(boolean isRtl) {
+ return isRtl ? 1 : -1;
+ }
+
+ @Override
+ public float getTaskMenuX(float x, View thumbnailView) {
+ return thumbnailView.getMeasuredWidth() + x;
+ }
+
+ @Override
+ public float getTaskMenuY(float y, View thumbnailView) {
+ return y;
+ }
+
+ @Override
+ public int getTaskMenuWidth(View view) {
+ return view.getMeasuredHeight();
+ }
+
+ @Override
+ public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
+ return LinearLayout.HORIZONTAL;
+ }
+
+ @Override
+ public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp) {
+ lp.width = 0;
+ lp.height = WRAP_CONTENT;
+ lp.weight = 1;
}
@Override
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 24fa815..65b1a7a 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -18,6 +18,7 @@
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -26,9 +27,9 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.PagedView;
import com.android.launcher3.util.OverScroller;
@@ -39,6 +40,10 @@
*/
public interface PagedOrientationHandler {
+ PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
+ PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
+ PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
+
interface Int2DAction<T> {
void call(T target, int x, int y);
}
@@ -48,34 +53,34 @@
Int2DAction<View> VIEW_SCROLL_BY = View::scrollBy;
Int2DAction<View> VIEW_SCROLL_TO = View::scrollTo;
Float2DAction<Canvas> CANVAS_TRANSLATE = Canvas::translate;
+ Float2DAction<Matrix> MATRIX_POST_TRANSLATE = Matrix::postTranslate;
+
<T> void set(T target, Int2DAction<T> action, int param);
<T> void set(T target, Float2DAction<T> action, float param);
float getPrimaryDirection(MotionEvent event, int pointerIndex);
float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
int getMeasuredSize(View view);
- int getPrimarySize(Rect rect);
float getPrimarySize(RectF rect);
+ int getClearAllScrollOffset(View view, boolean isRtl);
int getSecondaryDimension(View view);
- LauncherState.ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view);
- float getTranslationValue(LauncherState.ScaleAndTranslation scaleAndTranslation);
FloatProperty<View> getPrimaryViewTranslate();
FloatProperty<View> getSecondaryViewTranslate();
void setPrimaryAndResetSecondaryTranslate(View view, float translation);
- float getViewCenterPosition(View view);
int getPrimaryScroll(View view);
float getPrimaryScale(View view);
int getChildStart(View view);
+ float getChildStartWithTranslation(View view);
int getCenterForPage(View view, Rect insets);
int getScrollOffsetStart(View view, Rect insets);
int getScrollOffsetEnd(View view, Rect insets);
SingleAxisSwipeDetector.Direction getOppositeSwipeDirection();
- int getShortEdgeLength(DeviceProfile dp);
int getTaskDismissDirectionFactor();
+ int getTaskDragDisplacementFactor(boolean isRtl);
ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
void setMaxScroll(AccessibilityEvent event, int maxScroll);
boolean getRecentsRtlSetting(Resources resources);
float getDegreesRotated();
- void offsetTaskRect(RectF rect, float value, int delta);
+ int getRotation();
int getPrimaryValue(int x, int y);
int getSecondaryValue(int x, int y);
void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll);
@@ -83,8 +88,14 @@
void delegateScrollTo(PagedView pagedView, int primaryScroll);
void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y);
void scrollerStartScroll(OverScroller scroller, int newPosition);
- void getCurveProperties(PagedView view, Rect mInsets, CurveProperties out);
- boolean isGoingUp(float displacement);
+ void getCurveProperties(PagedView view, Rect insets, CurveProperties out);
+ boolean isGoingUp(float displacement, boolean isRtl);
+ boolean isLayoutNaturalToLauncher();
+ float getTaskMenuX(float x, View thumbnailView);
+ float getTaskMenuY(float y, View thumbnailView);
+ int getTaskMenuWidth(View view);
+ int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout);
+ void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
/**
* Maps the velocity from the coordinate plane of the foreground app to that
@@ -92,7 +103,6 @@
*/
void adjustFloatingIconStartVelocity(PointF velocity);
-
class CurveProperties {
public int scroll;
public int halfPageSize;
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 6d903b3..79e5c87 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -30,9 +30,8 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.PagedView;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.OverScroller;
@@ -65,19 +64,25 @@
}
@Override
- public void getCurveProperties(PagedView view, Rect mInsets, CurveProperties out) {
+ public void getCurveProperties(PagedView view, Rect insets, CurveProperties out) {
out.scroll = view.getScrollX();
out.halfPageSize = view.getNormalChildWidth() / 2;
out.halfScreenSize = view.getMeasuredWidth() / 2;
- out.screenCenter = mInsets.left + view.getPaddingLeft() + out.scroll + out.halfPageSize;
+ out.screenCenter = insets.left + view.getPaddingLeft() + out.scroll + out.halfPageSize;
}
@Override
- public boolean isGoingUp(float displacement) {
+ public boolean isGoingUp(float displacement, boolean isRtl) {
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
return displacement < 0;
}
@Override
+ public boolean isLayoutNaturalToLauncher() {
+ return true;
+ }
+
+ @Override
public void adjustFloatingIconStartVelocity(PointF velocity) {
//no-op
}
@@ -113,32 +118,21 @@
}
@Override
- public int getPrimarySize(Rect rect) {
- return rect.width();
- }
-
- @Override
public float getPrimarySize(RectF rect) {
return rect.width();
}
@Override
+ public int getClearAllScrollOffset(View view, boolean isRtl) {
+ return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
+ }
+
+ @Override
public int getSecondaryDimension(View view) {
return view.getHeight();
}
@Override
- public ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view) {
- float offscreenTranslationX = dp.widthPx - view.getPaddingStart();
- return new ScaleAndTranslation(1f, offscreenTranslationX, 0f);
- }
-
- @Override
- public float getTranslationValue(ScaleAndTranslation scaleAndTranslation) {
- return scaleAndTranslation.translationX;
- }
-
- @Override
public FloatProperty<View> getPrimaryViewTranslate() {
return VIEW_TRANSLATE_X;
}
@@ -155,11 +149,6 @@
}
@Override
- public float getViewCenterPosition(View view) {
- return view.getLeft() + view.getTranslationX();
- }
-
- @Override
public int getPrimaryScroll(View view) {
return view.getScrollX();
}
@@ -185,16 +174,8 @@
}
@Override
- public void offsetTaskRect(RectF rect, float value, int displayRotation) {
- if (displayRotation == Surface.ROTATION_0) {
- rect.offset(value, 0);
- } else if (displayRotation == Surface.ROTATION_90) {
- rect.offset(0, -value);
- } else if (displayRotation == Surface.ROTATION_180) {
- rect.offset(-value, 0);
- } else {
- rect.offset(0, value);
- }
+ public int getRotation() {
+ return Surface.ROTATION_0;
}
@Override
@@ -203,6 +184,11 @@
}
@Override
+ public float getChildStartWithTranslation(View view) {
+ return view.getLeft() + view.getTranslationX();
+ }
+
+ @Override
public int getCenterForPage(View view, Rect insets) {
return (view.getPaddingTop() + view.getMeasuredHeight() + insets.top
- insets.bottom - view.getPaddingBottom()) / 2;
@@ -224,13 +210,39 @@
}
@Override
- public int getShortEdgeLength(DeviceProfile dp) {
- return dp.widthPx;
+ public int getTaskDismissDirectionFactor() {
+ return -1;
}
@Override
- public int getTaskDismissDirectionFactor() {
- return -1;
+ public int getTaskDragDisplacementFactor(boolean isRtl) {
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ return 1;
+ }
+
+ @Override
+ public float getTaskMenuX(float x, View thumbnailView) {
+ return x;
+ }
+
+ @Override
+ public float getTaskMenuY(float y, View thumbnailView) {
+ return y;
+ }
+
+ @Override
+ public int getTaskMenuWidth(View view) {
+ return view.getMeasuredWidth();
+ }
+
+ @Override
+ public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
+ return taskMenuLayout.getOrientation();
+ }
+
+ @Override
+ public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp) {
+ // no-op, defaults are fine
}
@Override
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index eebd87f..d5ae2dc 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -18,8 +18,8 @@
import android.content.res.Resources;
import android.graphics.PointF;
-import android.graphics.RectF;
import android.view.Surface;
+import android.view.View;
import com.android.launcher3.Utilities;
@@ -31,21 +31,13 @@
}
@Override
- public boolean getRecentsRtlSetting(Resources resources) {
- return Utilities.isRtl(resources);
+ public int getTaskDragDisplacementFactor(boolean isRtl) {
+ return isRtl ? -1 : 1;
}
@Override
- public void offsetTaskRect(RectF rect, float value, int displayRotation) {
- if (displayRotation == Surface.ROTATION_0) {
- rect.offset(0, value);
- } else if (displayRotation == Surface.ROTATION_90) {
- rect.offset(value, 0);
- } else if (displayRotation == Surface.ROTATION_180) {
- rect.offset(0, -value);
- } else {
- rect.offset(-value, 0);
- }
+ public boolean getRecentsRtlSetting(Resources resources) {
+ return Utilities.isRtl(resources);
}
@Override
@@ -54,8 +46,13 @@
}
@Override
- public boolean isGoingUp(float displacement) {
- return displacement < 0;
+ public int getRotation() {
+ return Surface.ROTATION_270;
+ }
+
+ @Override
+ public boolean isGoingUp(float displacement, boolean isRtl) {
+ return isRtl ? displacement > 0 : displacement < 0;
}
@Override
@@ -64,4 +61,20 @@
float oldY = velocity.y;
velocity.set(oldY, -oldX);
}
+
+ @Override
+ public float getTaskMenuX(float x, View thumbnailView) {
+ return x;
+ }
+
+ @Override
+ public float getTaskMenuY(float y, View thumbnailView) {
+ return y + thumbnailView.getMeasuredHeight();
+ }
+
+ @Override
+ public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
+ view.setTranslationX(0);
+ view.setTranslationY(translation);
+ }
}
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
index d725486..875eefb 100644
--- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -17,6 +17,7 @@
import android.content.Context;
import android.graphics.PointF;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -24,6 +25,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
/**
* One dimensional scroll/drag/swipe gesture detector (either HORIZONTAL or VERTICAL).
@@ -103,6 +105,11 @@
super(config, isRtl);
mListener = l;
mDir = dir;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "SingleAxisSwipeDetector.ctor "
+ + l.getClass().getSimpleName()
+ + " @ " + android.util.Log.getStackTraceString(new Throwable()));
+ }
}
public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
@@ -154,6 +161,10 @@
@Override
protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "SingleAxisSwipeDetector "
+ + mListener.getClass().getSimpleName());
+ }
mListener.onDrag(mDir.extractDirection(displacement),
mDir.extractOrthogonalDirection(displacement), event);
}
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index da631bd..4fa658e 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -22,6 +22,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_LONGPRESS;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -41,8 +42,6 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.OptionsPopupView;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
/**
* Helper class to handle touch on empty space in workspace and show options popup on long press
@@ -167,17 +166,15 @@
@Override
public void onLongPress(MotionEvent event) {
- TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Workspace.longPress");
if (mLongPressState == STATE_REQUESTED) {
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Workspace.longPress");
if (canHandleLongPress()) {
mLongPressState = STATE_PENDING_PARENT_INFORM;
mWorkspace.getParent().requestDisallowInterceptTouchEvent(true);
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
- mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
- Action.Direction.NONE, ContainerType.WORKSPACE,
- mWorkspace.getCurrentPage());
+ mLauncher.getStatsLogManager().logger().log(LAUNCHER_WORKSPACE_LONGPRESS);
OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
} else {
cancelLongPress();
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index 499f655..59266b4 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -15,27 +15,23 @@
*/
package com.android.launcher3.util;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.Log;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
-import com.android.launcher3.testing.TestProtocol;
import java.lang.ref.WeakReference;
/**
* Helper class to statically track activity creation
+ * @param <T> The activity type to track
*/
-public final class ActivityTracker<T extends BaseActivity> implements Runnable {
+public final class ActivityTracker<T extends BaseActivity> {
private WeakReference<T> mCurrentActivity = new WeakReference<>(null);
- private WeakReference<SchedulerCallback<T>> mPendingCallback = new WeakReference<>(null);
private static final String EXTRA_SCHEDULER_CALLBACK = "launcher.scheduler_callback";
@@ -51,76 +47,32 @@
}
/**
- * Schedules the callback to be notified when the activity is created.
- * @return true if the activity is already created, false otherwise
+ * Call {@link SchedulerCallback#init(BaseActivity, boolean)} when the activity is ready.
+ * If the activity is already created, this is called immediately, otherwise we add the
+ * callback as an extra on the intent, and will call init() when we get handleIntent().
+ * @param callback The callback to call init() on when the activity is ready.
+ * @param intent The intent that will be used to initialize the activity, if the activity
+ * doesn't already exist. We add the callback as an extra on this intent.
*/
- public boolean schedule(SchedulerCallback<? extends T> callback) {
- synchronized (this) {
- mPendingCallback = new WeakReference<>((SchedulerCallback<T>) callback);
- }
- if (!notifyInitIfPending()) {
- // If the activity doesn't already exist, then post and wait for the activity to start
- MAIN_EXECUTOR.execute(this);
- return false;
- }
- return true;
- }
-
- @Override
- public void run() {
- notifyInitIfPending();
- }
-
- /**
- * Notifies the pending callback if the activity is now created.
- * @return true if the activity is now created.
- */
- private boolean notifyInitIfPending() {
+ public void runCallbackWhenActivityExists(SchedulerCallback<T> callback, Intent intent) {
T activity = mCurrentActivity.get();
if (activity != null) {
- notifyInitIfPending(activity, activity.isStarted());
- return true;
+ callback.init(activity, activity.isStarted());
+ } else {
+ callback.addToIntent(intent);
}
- return false;
- }
-
- public boolean notifyInitIfPending(T activity, boolean alreadyOnHome) {
- SchedulerCallback<T> pendingCallback = mPendingCallback.get();
- if (pendingCallback != null) {
- if (!pendingCallback.init(activity, alreadyOnHome)) {
- clearReference(pendingCallback);
- }
- return true;
- }
- return false;
- }
-
- public boolean clearReference(SchedulerCallback<? extends T> handler) {
- synchronized (this) {
- if (mPendingCallback.get() == handler) {
- mPendingCallback.clear();
- return true;
- }
- return false;
- }
- }
-
- public boolean hasPending() {
- return mPendingCallback.get() != null;
}
public boolean handleCreate(T activity) {
mCurrentActivity = new WeakReference<>(activity);
- return handleIntent(activity, activity.getIntent(), false, false);
+ return handleIntent(activity, activity.getIntent(), false);
}
public boolean handleNewIntent(T activity, Intent intent) {
- return handleIntent(activity, intent, activity.isStarted(), true);
+ return handleIntent(activity, intent, activity.isStarted());
}
- private boolean handleIntent(
- T activity, Intent intent, boolean alreadyOnHome, boolean explicitIntent) {
- boolean result = false;
+ private boolean handleIntent(T activity, Intent intent, boolean alreadyOnHome) {
if (intent != null && intent.getExtras() != null) {
IBinder stateBinder = intent.getExtras().getBinder(EXTRA_SCHEDULER_CALLBACK);
if (stateBinder instanceof ObjectWrapper) {
@@ -129,19 +81,26 @@
if (!handler.init(activity, alreadyOnHome)) {
intent.getExtras().remove(EXTRA_SCHEDULER_CALLBACK);
}
- result = true;
+ return true;
}
}
- if (!result && !explicitIntent) {
- result = notifyInitIfPending(activity, alreadyOnHome);
- }
- return result;
+ return false;
}
public interface SchedulerCallback<T extends BaseActivity> {
+ /**
+ * Called when the activity is ready.
+ * @param alreadyOnHome Whether the activity is already started.
+ * @return Whether to continue receiving callbacks (i.e. if the activity is recreated).
+ */
boolean init(T activity, boolean alreadyOnHome);
+ /**
+ * Adds this callback as an extra on the intent, so we can retrieve it in handleIntent() and
+ * call {@link #init}. The intent should be used to start the activity after calling this
+ * method in order for us to get handleIntent().
+ */
default Intent addToIntent(Intent intent) {
Bundle extras = new Bundle();
extras.putBinder(EXTRA_SCHEDULER_CALLBACK, ObjectWrapper.wrap(this));
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index f18e411..35788a5 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.util;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.Context;
@@ -26,7 +28,6 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
-import android.view.WindowManager;
import androidx.annotation.VisibleForTesting;
@@ -46,20 +47,27 @@
public static final int CHANGE_ROTATION = 1 << 1;
public static final int CHANGE_FRAME_DELAY = 1 << 2;
- private final Context mContext;
+ public static final int CHANGE_ALL = CHANGE_SIZE | CHANGE_ROTATION | CHANGE_FRAME_DELAY;
+
+ private final Context mDisplayContext;
private final int mId;
private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
private final Handler mChangeHandler;
private Info mInfo;
private DefaultDisplay(Context context) {
- mContext = context;
- mInfo = new Info(context);
+ DisplayManager dm = context.getSystemService(DisplayManager.class);
+ // Use application context to create display context so that it can have its own Resources.
+ mDisplayContext = context.getApplicationContext().createDisplayContext(
+ dm.getDisplay(DEFAULT_DISPLAY));
+ // Note that the Display object must be obtained from DisplayManager which is associated to
+ // the display context, so the Display is isolated from Activity and Application to provide
+ // the actual state of device that excludes the additional adjustment and override.
+ mInfo = new Info(mDisplayContext);
mId = mInfo.id;
mChangeHandler = new Handler(this::onChange);
- context.getSystemService(DisplayManager.class)
- .registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+ dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
}
@Override
@@ -75,7 +83,7 @@
}
Info oldInfo = mInfo;
- Info info = new Info(mContext);
+ Info info = new Info(mDisplayContext);
int change = 0;
if (info.hasDifferentSize(oldInfo)) {
@@ -142,10 +150,11 @@
}
private Info(Context context) {
- this(context.getSystemService(WindowManager.class).getDefaultDisplay());
+ this(context, context.getSystemService(DisplayManager.class)
+ .getDisplay(DEFAULT_DISPLAY));
}
- public Info(Display display) {
+ public Info(Context context, Display display) {
id = display.getDisplayId();
rotation = display.getRotation();
@@ -158,8 +167,7 @@
display.getRealSize(realSize);
display.getCurrentSizeRange(smallestSize, largestSize);
- metrics = new DisplayMetrics();
- display.getMetrics(metrics);
+ metrics = context.getResources().getDisplayMetrics();
}
private boolean hasDifferentSize(Info info) {
diff --git a/src/com/android/launcher3/util/GridOccupancy.java b/src/com/android/launcher3/util/GridOccupancy.java
index 6a10b0a..9c752a7 100644
--- a/src/com/android/launcher3/util/GridOccupancy.java
+++ b/src/com/android/launcher3/util/GridOccupancy.java
@@ -2,7 +2,7 @@
import android.graphics.Rect;
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
/**
* Utility object to manage the occupancy in a grid.
diff --git a/src/com/android/launcher3/util/InstantAppResolver.java b/src/com/android/launcher3/util/InstantAppResolver.java
index 031a40d..6f706d2 100644
--- a/src/com/android/launcher3/util/InstantAppResolver.java
+++ b/src/com/android/launcher3/util/InstantAppResolver.java
@@ -21,8 +21,8 @@
import android.content.pm.PackageManager;
import android.util.Log;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.R;
+import com.android.launcher3.model.data.AppInfo;
/**
* A wrapper class to access instant app related APIs.
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 59a5ed6..4d5405d 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -19,11 +19,11 @@
import android.content.ComponentName;
import android.os.UserHandle;
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import java.util.HashSet;
diff --git a/src/com/android/launcher3/util/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
index b54074e..528a6e9 100644
--- a/src/com/android/launcher3/util/LogConfig.java
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -12,6 +12,8 @@
public class LogConfig {
// These are list of strings that can be used to replace TAGNAME.
+ public static final String STATSLOG = "StatsLog";
+
/**
* After this tag is turned on, whenever there is n user event, debug information is
* printed out to logcat.
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index 07f835d..a8642b0 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -16,7 +16,7 @@
package com.android.launcher3.util;
-import android.util.Property;
+import android.util.FloatProperty;
import android.view.View;
import java.util.Arrays;
@@ -26,8 +26,8 @@
*/
public class MultiValueAlpha {
- public static final Property<AlphaProperty, Float> VALUE =
- new Property<AlphaProperty, Float>(Float.TYPE, "value") {
+ public static final FloatProperty<AlphaProperty> VALUE =
+ new FloatProperty<AlphaProperty>("value") {
@Override
public Float get(AlphaProperty alphaProperty) {
@@ -35,7 +35,7 @@
}
@Override
- public void set(AlphaProperty object, Float value) {
+ public void setValue(AlphaProperty object, float value) {
object.setValue(value);
}
};
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
new file mode 100644
index 0000000..d4e074c
--- /dev/null
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -0,0 +1,122 @@
+/*
+ * 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.util;
+
+import android.content.SharedPreferences;
+import android.util.ArrayMap;
+
+import androidx.annotation.StringDef;
+
+import com.android.launcher3.Launcher;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Stores and retrieves onboarding-related data via SharedPreferences.
+ */
+public class OnboardingPrefs<T extends Launcher> {
+
+ public static final String HOME_BOUNCE_SEEN = "launcher.apps_view_shown";
+ public static final String SHELF_BOUNCE_SEEN = "launcher.shelf_bounce_seen";
+ public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
+ public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
+ public static final String ALL_APPS_COUNT = "launcher.all_apps_count";
+ public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
+ public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
+
+ /**
+ * Events that either have happened or have not (booleans).
+ */
+ @StringDef(value = {
+ HOME_BOUNCE_SEEN,
+ SHELF_BOUNCE_SEEN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventBoolKey {}
+
+ /**
+ * Events that occur multiple times, which we count up to a max defined in {@link #MAX_COUNTS}.
+ */
+ @StringDef(value = {
+ HOME_BOUNCE_COUNT,
+ SHELF_BOUNCE_COUNT,
+ ALL_APPS_COUNT,
+ HOTSEAT_DISCOVERY_TIP_COUNT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventCountKey {}
+
+ private static final Map<String, Integer> MAX_COUNTS;
+ static {
+ Map<String, Integer> maxCounts = new ArrayMap<>(3);
+ maxCounts.put(HOME_BOUNCE_COUNT, 3);
+ maxCounts.put(SHELF_BOUNCE_COUNT, 3);
+ maxCounts.put(ALL_APPS_COUNT, 5);
+ maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
+ MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
+ }
+
+ protected final T mLauncher;
+ protected final SharedPreferences mSharedPrefs;
+
+ public OnboardingPrefs(T launcher, SharedPreferences sharedPrefs) {
+ mLauncher = launcher;
+ mSharedPrefs = sharedPrefs;
+ }
+
+ /** @return The number of times we have seen the given event. */
+ public int getCount(@EventCountKey String key) {
+ return mSharedPrefs.getInt(key, 0);
+ }
+
+ /** @return Whether we have seen this event enough times, as defined by {@link #MAX_COUNTS}. */
+ public boolean hasReachedMaxCount(@EventCountKey String eventKey) {
+ return hasReachedMaxCount(getCount(eventKey), eventKey);
+ }
+
+ private boolean hasReachedMaxCount(int count, @EventCountKey String eventKey) {
+ return count >= MAX_COUNTS.get(eventKey);
+ }
+
+ /** @return Whether we have seen the given event. */
+ public boolean getBoolean(@EventBoolKey String key) {
+ return mSharedPrefs.getBoolean(key, false);
+ }
+
+ /**
+ * Marks on-boarding preference boolean at true
+ */
+ public void markChecked(String flag) {
+ mSharedPrefs.edit().putBoolean(flag, true).apply();
+ }
+
+ /**
+ * Add 1 to the given event count, if we haven't already reached the max count.
+ * @return Whether we have now reached the max count.
+ */
+ public boolean incrementEventCount(@EventCountKey String eventKey) {
+ int count = getCount(eventKey);
+ if (hasReachedMaxCount(count, eventKey)) {
+ return true;
+ }
+ count++;
+ mSharedPrefs.edit().putInt(eventKey, count).apply();
+ return hasReachedMaxCount(count, eventKey);
+ }
+}
diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java
index 34efb12..87e6986 100644
--- a/src/com/android/launcher3/util/OverScroller.java
+++ b/src/com/android/launcher3/util/OverScroller.java
@@ -561,10 +561,11 @@
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mDuration = duration;
+ if (mSpring != null) {
+ mSpring.cancel();
+ }
+
if (mState == SPRING) {
- if (mSpring != null) {
- mSpring.cancel();
- }
mSpring = new SpringAnimation(this, SPRING_PROPERTY);
ResourceProvider rp = DynamicResource.provider(mContext);
@@ -576,9 +577,9 @@
mSpring.setStartVelocity(velocity);
mSpring.animateToFinalPosition(mFinal);
mSpring.addEndListener((animation, canceled, value, velocity1) -> {
+ mSpring = null;
finish();
mState = SPLINE;
- mSpring = null;
});
}
// Unused
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 6c18747..86f3431 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,7 +16,6 @@
package com.android.launcher3.util;
-import static android.content.pm.PackageInstaller.SessionInfo;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import android.app.AppOpsManager;
@@ -45,16 +44,14 @@
import android.util.Pair;
import android.widget.Toast;
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.PromiseAppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import java.net.URISyntaxException;
import java.util.List;
@@ -348,15 +345,4 @@
}
return false;
}
-
- /**
- * Returns the created time in millis of given session info. Returns 0 if not available.
- */
- public static long getSessionCreatedTimeInMillis(@NonNull final SessionInfo info) {
- try {
- return (long) SessionInfo.class.getDeclaredMethod("getCreatedMillis").invoke(info);
- } catch (Exception e) {
- return 0;
- }
- }
}
diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java
index f243ca6..3a3b5a2 100644
--- a/src/com/android/launcher3/util/PackageUserKey.java
+++ b/src/com/android/launcher3/util/PackageUserKey.java
@@ -3,9 +3,10 @@
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
import java.util.Arrays;
@@ -60,4 +61,10 @@
PackageUserKey otherKey = (PackageUserKey) obj;
return mPackageName.equals(otherKey.mPackageName) && mUser.equals(otherKey.mUser);
}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return mPackageName + "#" + mUser;
+ }
}
diff --git a/src/com/android/launcher3/util/PendingRequestArgs.java b/src/com/android/launcher3/util/PendingRequestArgs.java
index b8bcfed..9b8c6a6 100644
--- a/src/com/android/launcher3/util/PendingRequestArgs.java
+++ b/src/com/android/launcher3/util/PendingRequestArgs.java
@@ -20,7 +20,7 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.widget.WidgetAddFlowHandler;
/**
diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java
index 48aa02b..4b22429 100644
--- a/src/com/android/launcher3/util/SecureSettingsObserver.java
+++ b/src/com/android/launcher3/util/SecureSettingsObserver.java
@@ -29,6 +29,11 @@
/** Hidden field Settings.Secure.NOTIFICATION_BADGING */
public static final String NOTIFICATION_BADGING = "notification_badging";
+ /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
+ public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
+ /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
+ public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
+ "swipe_bottom_to_notification_enabled";
private final ContentResolver mResolver;
private final String mKeySetting;
@@ -79,4 +84,21 @@
return new SecureSettingsObserver(
context.getContentResolver(), listener, NOTIFICATION_BADGING, 1);
}
+
+ public static SecureSettingsObserver newOneHandedSettingsObserver(Context context,
+ OnChangeListener listener) {
+ return new SecureSettingsObserver(
+ context.getContentResolver(), listener, ONE_HANDED_ENABLED, 1);
+ }
+
+ /**
+ * Constructs settings observer for {@link #ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED}
+ * preference.
+ */
+ public static SecureSettingsObserver newSwipeToNotificationSettingsObserver(Context context,
+ OnChangeListener listener) {
+ return new SecureSettingsObserver(
+ context.getContentResolver(), listener,
+ ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
+ }
}
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
index 49c97da..91cf835 100644
--- a/src/com/android/launcher3/util/ShortcutUtil.java
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -15,11 +15,11 @@
*/
package com.android.launcher3.util;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
public class ShortcutUtil {
@@ -34,7 +34,7 @@
* Returns true when we should show depp shortcuts in shortcut menu for the item.
*/
public static boolean supportsDeepShortcuts(ItemInfo info) {
- return isActive(info) && isApp(info);
+ return isActive(info) && isApp(info) && !WidgetsModel.GO_DISABLE_WIDGETS;
}
/**
@@ -64,7 +64,7 @@
private static boolean isActive(ItemInfo info) {
boolean isLoading = info instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) info).hasPromiseIconUi();
- return !isLoading && !info.isDisabled() && !WidgetsModel.GO_DISABLE_WIDGETS;
+ return !isLoading && !info.isDisabled();
}
private static boolean isApp(ItemInfo info) {
diff --git a/src/com/android/launcher3/util/SystemUiController.java b/src/com/android/launcher3/util/SystemUiController.java
index 86995b7..275c024 100644
--- a/src/com/android/launcher3/util/SystemUiController.java
+++ b/src/com/android/launcher3/util/SystemUiController.java
@@ -16,7 +16,6 @@
package com.android.launcher3.util;
-import android.text.TextUtils;
import android.view.View;
import android.view.Window;
@@ -31,10 +30,9 @@
// Various UI states in increasing order of priority
public static final int UI_STATE_BASE_WINDOW = 0;
- public static final int UI_STATE_ALL_APPS = 1;
+ public static final int UI_STATE_SCRIM_VIEW = 1;
public static final int UI_STATE_WIDGET_BOTTOM_SHEET = 2;
- public static final int UI_STATE_ROOT_VIEW = 3;
- public static final int UI_STATE_OVERVIEW = 4;
+ public static final int UI_STATE_OVERVIEW = 3;
public static final int FLAG_LIGHT_NAV = 1 << 0;
public static final int FLAG_DARK_NAV = 1 << 1;
@@ -42,7 +40,7 @@
public static final int FLAG_DARK_STATUS = 1 << 3;
private final Window mWindow;
- private final int[] mStates = new int[5];
+ private final int[] mStates = new int[4];
public SystemUiController(Window window) {
mWindow = window;
@@ -63,25 +61,38 @@
// Apply the state flags in priority order
int newFlags = oldFlags;
for (int stateFlag : mStates) {
- if (Utilities.ATLEAST_OREO) {
- if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
- newFlags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- } else if ((stateFlag & FLAG_DARK_NAV) != 0) {
- newFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
- }
- }
-
- if ((stateFlag & FLAG_LIGHT_STATUS) != 0) {
- newFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
- } else if ((stateFlag & FLAG_DARK_STATUS) != 0) {
- newFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
- }
+ newFlags = getSysUiVisibilityFlags(stateFlag, newFlags);
}
if (newFlags != oldFlags) {
mWindow.getDecorView().setSystemUiVisibility(newFlags);
}
}
+ /**
+ * Returns the sysui visibility for the base layer
+ */
+ public int getBaseSysuiVisibility() {
+ return getSysUiVisibilityFlags(
+ mStates[UI_STATE_BASE_WINDOW], mWindow.getDecorView().getSystemUiVisibility());
+ }
+
+ private int getSysUiVisibilityFlags(int stateFlag, int currentVisibility) {
+ if (Utilities.ATLEAST_OREO) {
+ if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
+ currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+ } else if ((stateFlag & FLAG_DARK_NAV) != 0) {
+ currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+ }
+ }
+
+ if ((stateFlag & FLAG_LIGHT_STATUS) != 0) {
+ currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ } else if ((stateFlag & FLAG_DARK_STATUS) != 0) {
+ currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ }
+ return currentVisibility;
+ }
+
@Override
public String toString() {
return "mStates=" + Arrays.toString(mStates);
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index da59afe..b74686f 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -113,10 +113,17 @@
* Returns the alpha corresponding to the theme attribute {@param attr}, in the range [0, 255].
*/
public static int getAlpha(Context context, int attr) {
+ return (int) (255 * getFloat(context, attr, 0) + 0.5f);
+ }
+
+ /**
+ * Returns the alpha corresponding to the theme attribute {@param attr}
+ */
+ public static float getFloat(Context context, int attr, float defValue) {
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
- float alpha = ta.getFloat(0, 0);
+ float value = ta.getFloat(0, defValue);
ta.recycle();
- return (int) (255 * alpha + 0.5f);
+ return value;
}
/**
diff --git a/src/com/android/launcher3/util/WindowBounds.java b/src/com/android/launcher3/util/WindowBounds.java
new file mode 100644
index 0000000..3c2fb62
--- /dev/null
+++ b/src/com/android/launcher3/util/WindowBounds.java
@@ -0,0 +1,47 @@
+/*
+ * 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.util;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Utility class to hold information about window position and layout
+ */
+public class WindowBounds {
+
+ public final Rect bounds;
+ public final Rect insets;
+ public final Point availableSize;
+
+ public WindowBounds(Rect bounds, Rect insets) {
+ this.bounds = bounds;
+ this.insets = insets;
+ availableSize = new Point(bounds.width() - insets.left - insets.right,
+ bounds.height() - insets.top - insets.bottom);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof WindowBounds)) {
+ return false;
+ }
+ WindowBounds other = (WindowBounds) obj;
+ return other.bounds.equals(bounds) && other.insets.equals(insets);
+ }
+}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index c9cdeff..ae459e1 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -21,8 +21,8 @@
import android.view.View.AccessibilityDelegate;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.model.data.ItemInfo;
/**
* An interface to be used along with a context for various activities in Launcher. This allows a
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index 60470dc..b4a6b14 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -32,7 +32,7 @@
import androidx.core.content.ContextCompat;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragLayer;
@@ -48,13 +48,13 @@
private static final long SHOW_DURATION_MS = 300;
private static final long HIDE_DURATION_MS = 100;
- protected final Launcher mLauncher;
+ protected final BaseDraggingActivity mActivity;
private final Handler mHandler = new Handler();
private Runnable mOnClosed;
public ArrowTipView(Context context) {
super(context, null, 0);
- mLauncher = Launcher.getLauncher(context);
+ mActivity = BaseDraggingActivity.fromContext(context);
init(context);
}
@@ -75,11 +75,11 @@
.setStartDelay(0)
.setDuration(HIDE_DURATION_MS)
.setInterpolator(Interpolators.ACCEL)
- .withEndAction(() -> mLauncher.getDragLayer().removeView(this))
+ .withEndAction(() -> mActivity.getDragLayer().removeView(this))
.start();
} else {
animate().cancel();
- mLauncher.getDragLayer().removeView(this);
+ mActivity.getDragLayer().removeView(this);
}
if (mOnClosed != null) mOnClosed.run();
mIsOpen = false;
@@ -125,13 +125,37 @@
* Show Tip with specified string and Y location
*/
public ArrowTipView show(String text, int top) {
+ return show(text, Gravity.CENTER_HORIZONTAL, 0, top);
+ }
+
+ /**
+ * Show the ArrowTipView (tooltip) center, start, or end aligned.
+ *
+ * @param text The text to be shown in the tooltip.
+ * @param gravity The gravity aligns the tooltip center, start, or end.
+ * @param arrowMarginStart The margin from start to place arrow (ignored if center)
+ * @param top The Y coordinate of the bottom of tooltip.
+ * @return The tooltip.
+ */
+ public ArrowTipView show(String text, int gravity, int arrowMarginStart, int top) {
((TextView) findViewById(R.id.text)).setText(text);
- mLauncher.getDragLayer().addView(this);
+ ViewGroup parent = mActivity.getDragLayer();
+ parent.addView(this);
DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
- params.gravity = Gravity.CENTER_HORIZONTAL;
- params.leftMargin = mLauncher.getDeviceProfile().workspacePadding.left;
- params.rightMargin = mLauncher.getDeviceProfile().workspacePadding.right;
+ params.gravity = gravity;
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) findViewById(
+ R.id.arrow).getLayoutParams();
+ lp.gravity = gravity;
+ if (gravity == Gravity.END) {
+ lp.setMarginEnd(parent.getMeasuredWidth() - arrowMarginStart);
+ } else if (gravity == Gravity.START) {
+ lp.setMarginStart(arrowMarginStart);
+ }
+ requestLayout();
+
+ params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left;
+ params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right;
post(() -> setY(top - getHeight()));
setAlpha(0);
animate()
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 2fc3eaf..b010b4b 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -90,13 +90,23 @@
}
};
- // Touch is being dispatched through the normal view dispatch system
- private static final int TOUCH_DISPATCHING_VIEW = 1 << 0;
+ // Touch coming from normal view system is being dispatched.
+ private static final int TOUCH_DISPATCHING_FROM_VIEW = 1 << 0;
// Touch is being dispatched through the normal view dispatch system, and started at the
- // system gesture region
- private static final int TOUCH_DISPATCHING_GESTURE = 1 << 1;
- // Touch is being dispatched through a proxy from InputMonitor
- private static final int TOUCH_DISPATCHING_PROXY = 1 << 2;
+ // system gesture region. In this case we prevent internal gesture handling and only allow
+ // normal view event handling.
+ private static final int TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION = 1 << 1;
+ // Touch coming from InputMonitor proxy is being dispatched 'only to gestures'. Note that both
+ // this and view-system can be active at the same time where view-system would go to the views,
+ // and this would go to the gestures.
+ // Note that this is not set when events are coming from proxy, but going through full dispatch
+ // process (both views and gestures) to allow view-system to easily take over in case it
+ // comes later.
+ private static final int TOUCH_DISPATCHING_FROM_PROXY = 1 << 2;
+ // ACTION_DOWN has been dispatched to child views and ACTION_UP or ACTION_CANCEL is pending.
+ // Note that the event source can either be view-dispatching or proxy-dispatching based on if
+ // TOUCH_DISPATCHING_VIEW is present or not.
+ private static final int TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS = 1 << 3;
protected final float[] mTmpXY = new float[2];
protected final float[] mTmpRectPoints = new float[4];
@@ -181,11 +191,17 @@
}
private TouchController findControllerToHandleTouch(MotionEvent ev) {
- if (isEventInLauncher(ev)) {
- AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
- if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
- return topView;
- }
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "findControllerToHandleTouch ev=" + ev
+ + ", isEventInLauncher=" + isEventInLauncher(ev)
+ + ", topOpenView=" + AbstractFloatingView.getTopOpenView(mActivity));
+ }
+
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+ if (topView != null
+ && (isEventInLauncher(ev) || topView.canInterceptEventsInSystemGestureRegion())
+ && topView.onControllerInterceptTouchEvent(ev)) {
+ return topView;
}
for (TouchController controller : mControllers) {
@@ -198,7 +214,8 @@
protected boolean findActiveController(MotionEvent ev) {
mActiveController = null;
- if ((mTouchDispatchState & (TOUCH_DISPATCHING_GESTURE | TOUCH_DISPATCHING_PROXY)) == 0) {
+ if ((mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION
+ | TOUCH_DISPATCHING_FROM_PROXY)) == 0) {
// Only look for controllers if we are not dispatching from gesture area and proxy is
// not active
mActiveController = findControllerToHandleTouch(ev);
@@ -275,24 +292,30 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "BaseDragLayer: " + ev);
- }
switch (ev.getAction()) {
case ACTION_DOWN: {
- mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
+ if ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0) {
+ // Cancel the previous touch
+ int action = ev.getAction();
+ ev.setAction(ACTION_CANCEL);
+ super.dispatchTouchEvent(ev);
+ ev.setAction(action);
+ }
+ mTouchDispatchState |= TOUCH_DISPATCHING_FROM_VIEW
+ | TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
if (isEventInLauncher(ev)) {
- mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
+ mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION;
} else {
- mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
+ mTouchDispatchState |= TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION;
}
break;
}
case ACTION_CANCEL:
case ACTION_UP:
- mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
- mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
+ mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION;
+ mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW;
+ mTouchDispatchState &= ~TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
break;
}
super.dispatchTouchEvent(ev);
@@ -302,43 +325,53 @@
}
/**
- * Called before we are about to receive proxy events.
- *
- * @return false if we can't handle proxy at this time
- */
- public boolean prepareProxyEventStarting() {
- mProxyTouchController = null;
- if ((mTouchDispatchState & TOUCH_DISPATCHING_VIEW) != 0 && mActiveController != null) {
- // We are already dispatching using view system and have an active controller, we can't
- // handle another controller.
-
- // This flag was already cleared in proxy ACTION_UP or ACTION_CANCEL. Added here just
- // to be safe
- mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
- return false;
- }
-
- mTouchDispatchState |= TOUCH_DISPATCHING_PROXY;
- return true;
- }
-
- /**
* Proxies the touch events to the gesture handlers
*/
- public boolean proxyTouchEvent(MotionEvent ev) {
- boolean handled;
- if (mProxyTouchController != null) {
- handled = mProxyTouchController.onControllerTouchEvent(ev);
+ public boolean proxyTouchEvent(MotionEvent ev, boolean allowViewDispatch) {
+ int actionMasked = ev.getActionMasked();
+ boolean isViewDispatching = (mTouchDispatchState & TOUCH_DISPATCHING_FROM_VIEW) != 0;
+
+ // Only do view dispatch if another view-dispatching is not running, or we already started
+ // proxy-dispatching before. Note that view-dispatching can always take over the proxy
+ // dispatching at anytime, but not vice-versa.
+ allowViewDispatch = allowViewDispatch && !isViewDispatching
+ && (actionMasked == ACTION_DOWN
+ || ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0));
+
+ if (allowViewDispatch) {
+ mTouchDispatchState |= TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
+ super.dispatchTouchEvent(ev);
+
+ if (actionMasked == ACTION_UP || actionMasked == ACTION_CANCEL) {
+ mTouchDispatchState &= ~TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
+ mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY;
+ }
+ return true;
} else {
- mProxyTouchController = findControllerToHandleTouch(ev);
- handled = mProxyTouchController != null;
+ boolean handled;
+ if (mProxyTouchController != null) {
+ handled = mProxyTouchController.onControllerTouchEvent(ev);
+ } else {
+ if (actionMasked == ACTION_DOWN) {
+ if (isViewDispatching && mActiveController != null) {
+ // A controller is already active, we can't initiate our own controller
+ mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY;
+ } else {
+ // We will control the handler via proxy
+ mTouchDispatchState |= TOUCH_DISPATCHING_FROM_PROXY;
+ }
+ }
+ if ((mTouchDispatchState & TOUCH_DISPATCHING_FROM_PROXY) != 0) {
+ mProxyTouchController = findControllerToHandleTouch(ev);
+ }
+ handled = mProxyTouchController != null;
+ }
+ if (actionMasked == ACTION_UP || actionMasked == ACTION_CANCEL) {
+ mProxyTouchController = null;
+ mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY;
+ }
+ return handled;
}
- int action = ev.getAction();
- if (action == ACTION_UP || action == ACTION_CANCEL) {
- mProxyTouchController = null;
- mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
- }
- return handled;
}
/**
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 478141a..1a8e11b 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -101,7 +101,6 @@
private @Nullable Drawable mForeground;
private @Nullable Drawable mBackground;
- private boolean mIsVerticalBarLayout = false;
private boolean mIsAdaptiveIcon = false;
private ValueAnimator mRevealAnimator;
@@ -145,7 +144,8 @@
}
void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
- boolean isOpening, float scale, float minSize, LayoutParams parentLp) {
+ boolean isOpening, float scale, float minSize, LayoutParams parentLp,
+ boolean isVerticalBarLayout) {
DeviceProfile dp = mLauncher.getDeviceProfile();
float dX = mIsRtl
? rect.left - (dp.widthPx - parentLp.getMarginStart() - parentLp.width)
@@ -158,7 +158,7 @@
Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
LINEAR), 0, 1);
- if (mIsVerticalBarLayout) {
+ if (isVerticalBarLayout) {
mOutline.right = (int) (rect.width() / scale);
} else {
mOutline.bottom = (int) (rect.height() / scale);
@@ -183,16 +183,16 @@
mRevealAnimator.setCurrentFraction(shapeRevealProgress);
}
- float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height())
+ float drawableScale = (isVerticalBarLayout ? mOutline.width() : mOutline.height())
/ minSize;
- setBackgroundDrawableBounds(drawableScale);
+ setBackgroundDrawableBounds(drawableScale, isVerticalBarLayout);
if (isOpening) {
// Center align foreground
int height = mFinalDrawableBounds.height();
int width = mFinalDrawableBounds.width();
- int diffY = mIsVerticalBarLayout ? 0
+ int diffY = isVerticalBarLayout ? 0
: (int) (((height * drawableScale) - height) / 2);
- int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
+ int diffX = isVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
: 0;
sTmpRect.set(mFinalDrawableBounds);
sTmpRect.offset(diffX, diffY);
@@ -210,11 +210,11 @@
invalidateOutline();
}
- private void setBackgroundDrawableBounds(float scale) {
+ private void setBackgroundDrawableBounds(float scale, boolean isVerticalBarLayout) {
sTmpRect.set(mFinalDrawableBounds);
Utilities.scaleRectAboutCenter(sTmpRect, scale);
// Since the drawable is at the top of the view, we need to offset to keep it centered.
- if (mIsVerticalBarLayout) {
+ if (isVerticalBarLayout) {
sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top);
} else {
sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale));
@@ -228,7 +228,8 @@
}
}
- void setIcon(@Nullable Drawable drawable, int iconOffset, LayoutParams lp, boolean isOpening) {
+ void setIcon(@Nullable Drawable drawable, int iconOffset, LayoutParams lp, boolean isOpening,
+ boolean isVerticalBarLayout) {
mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
if (mIsAdaptiveIcon) {
boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
@@ -264,7 +265,7 @@
}
float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
- if (mIsVerticalBarLayout) {
+ if (isVerticalBarLayout) {
lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
} else {
lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
@@ -285,7 +286,7 @@
bgDrawableStartScale = scale;
mOutline.set(0, 0, lp.width, lp.height);
}
- setBackgroundDrawableBounds(bgDrawableStartScale);
+ setBackgroundDrawableBounds(bgDrawableStartScale, isVerticalBarLayout);
mEndRevealRect.set(0, 0, lp.width, lp.height);
setOutlineProvider(new ViewOutlineProvider() {
@Override
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index ad8d69d..177aff4 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -19,7 +19,6 @@
import static com.android.launcher3.Utilities.getBadge;
import static com.android.launcher3.Utilities.getFullDrawable;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.animation.Animator;
@@ -50,7 +49,6 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -58,8 +56,10 @@
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.testing.TestProtocol;
/**
* A view that is created to look like another view with the purpose of creating fluid animations.
@@ -100,6 +100,7 @@
private AnimatorSet mFadeAnimatorSet;
private ListenerView mListenerView;
+ private Runnable mFastFinishRunnable;
public FloatingIconView(Context context) {
this(context, null);
@@ -124,7 +125,6 @@
super.onAttachedToWindow();
if (!mIsOpening) {
getViewTreeObserver().addOnGlobalLayoutListener(this);
- mLauncher.getRotationHelper().setCurrentTransitionRequest(REQUEST_LOCK);
}
}
@@ -162,7 +162,7 @@
float scale = Math.max(1f, Math.min(scaleX, scaleY));
mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
- minSize, lp);
+ minSize, lp, mIsVerticalBarLayout);
setPivotX(0);
setPivotY(0);
@@ -336,7 +336,7 @@
final InsettableFrameLayout.LayoutParams lp =
(InsettableFrameLayout.LayoutParams) getLayoutParams();
mBadge = badge;
- mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening);
+ mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening, mIsVerticalBarLayout);
if (drawable instanceof AdaptiveIconDrawable) {
final int originalHeight = lp.height;
final int originalWidth = lp.width;
@@ -444,9 +444,21 @@
}
}
+ /**
+ * Sets a runnable that is called after a call to {@link #fastFinish()}.
+ */
+ public void setFastFinishRunnable(Runnable runnable) {
+ mFastFinishRunnable = runnable;
+ }
+
public void fastFinish() {
+ if (mFastFinishRunnable != null) {
+ mFastFinishRunnable.run();
+ mFastFinishRunnable = null;
+ }
if (mLoadIconSignal != null) {
mLoadIconSignal.cancel();
+ mLoadIconSignal = null;
}
if (mEndRunnable != null) {
mEndRunnable.run();
@@ -549,6 +561,11 @@
view.setVisibility(INVISIBLE);
parent.addView(view);
dragLayer.addView(view.mListenerView);
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "getFloatingIconView. listenerView "
+ + "added to dragLayer. listenerView=" + view.mListenerView + ", fiv=" + view,
+ new Exception());
+ }
view.mListenerView.setListener(view::fastFinish);
view.mEndRunnable = () -> {
@@ -628,6 +645,10 @@
private void finish(DragLayer dragLayer) {
((ViewGroup) dragLayer.getParent()).removeView(this);
dragLayer.removeView(mListenerView);
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "listenerView removed from dragLayer. "
+ + "listenerView=" + mListenerView + ", fiv=" + this, new Exception());
+ }
recycle();
mLauncher.getViewCache().recycleView(R.layout.floating_icon_view, this);
}
@@ -656,6 +677,7 @@
sTmpObjArray[0] = null;
mIconLoadResult = null;
mClipIconView.recycle();
+ mFastFinishRunnable = null;
}
private static class IconLoadResult {
diff --git a/src/com/android/launcher3/views/ListenerView.java b/src/com/android/launcher3/views/ListenerView.java
index 263f7c4..3ef778b 100644
--- a/src/com/android/launcher3/views/ListenerView.java
+++ b/src/com/android/launcher3/views/ListenerView.java
@@ -17,18 +17,20 @@
import android.content.Context;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.testing.TestProtocol;
/**
* An invisible AbstractFloatingView that can run a callback when it is being closed.
*/
public class ListenerView extends AbstractFloatingView {
- public Runnable mCloseListener;
+ private Runnable mCloseListener;
public ListenerView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -36,12 +38,20 @@
}
public void setListener(Runnable listener) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView setListener lv=" + this
+ + ", listener=" + listener, new Exception());
+ }
mCloseListener = listener;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView onAttachedToWindow lv=" + this,
+ new Exception());
+ }
mIsOpen = true;
}
@@ -49,10 +59,19 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mIsOpen = false;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView onDetachedFromView lv=" + this,
+ new Exception());
+ }
}
@Override
protected void handleClose(boolean animate) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView handeClose lv=" + this
+ + ", mIsOpen=" + mIsOpen + ", mCloseListener=" + mCloseListener
+ + ", getParent()=" + getParent(), new Exception());
+ }
if (mIsOpen) {
if (mCloseListener != null) {
mCloseListener.run();
@@ -77,10 +96,19 @@
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView touchEvent lv=" + this
+ + ", ev=" + ev, new Exception());
+ }
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
handleClose(false);
}
// We want other views to be able to intercept the touch so we return false here.
return false;
}
+
+ @Override
+ public boolean canInterceptEventsInSystemGestureRegion() {
+ return true;
+ }
}
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 880f123..d558781 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -17,6 +17,9 @@
import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS;
import android.content.Context;
import android.content.Intent;
@@ -35,13 +38,16 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.StatsLogManager.EventEnum;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.widget.WidgetsFullSheet;
import java.util.ArrayList;
@@ -66,21 +72,21 @@
@Override
public void onClick(View view) {
- handleViewClick(view, Action.Touch.TAP);
+ handleViewClick(view);
}
@Override
public boolean onLongClick(View view) {
- return handleViewClick(view, Action.Touch.LONGPRESS);
+ return handleViewClick(view);
}
- private boolean handleViewClick(View view, int action) {
+ private boolean handleViewClick(View view) {
OptionItem item = mItemMap.get(view);
if (item == null) {
return false;
}
- if (item.mControlTypeForLog > 0) {
- logTap(action, item.mControlTypeForLog);
+ if (item.mEventId.getId() > 0) {
+ mLauncher.getStatsLogManager().logger().log(item.mEventId);
}
if (item.mClickListener.onLongClick(view)) {
close(true);
@@ -89,10 +95,6 @@
return false;
}
- private void logTap(int action, int controlType) {
- mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType);
- }
-
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() != MotionEvent.ACTION_DOWN) {
@@ -157,13 +159,16 @@
int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
R.drawable.ic_palette : R.drawable.ic_wallpaper;
options.add(new OptionItem(resString, resDrawable,
- ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker));
+ IGNORE,
+ OptionsPopupView::startWallpaperPicker));
if (!WidgetsModel.GO_DISABLE_WIDGETS) {
options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
- ControlType.WIDGETS_BUTTON, OptionsPopupView::onWidgetsClicked));
+ LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
+ OptionsPopupView::onWidgetsClicked));
}
options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting,
- ControlType.SETTINGS_BUTTON, OptionsPopupView::startSettings));
+ LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
+ OptionsPopupView::startSettings));
show(launcher, target, options);
}
@@ -184,6 +189,7 @@
}
public static boolean startSettings(View view) {
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startSettings");
Launcher launcher = Launcher.getLauncher(view.getContext());
launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
.setPackage(launcher.getPackageName())
@@ -214,21 +220,29 @@
if (!TextUtils.isEmpty(pickerPackage)) {
intent.setPackage(pickerPackage);
}
- return launcher.startActivitySafely(v, intent, null, null);
+ return launcher.startActivitySafely(v, intent, dummyInfo(intent), null);
+ }
+
+ static WorkspaceItemInfo dummyInfo(Intent intent) {
+ WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
+ dummyInfo.intent = intent;
+ dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ dummyInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
+ return dummyInfo;
}
public static class OptionItem {
private final int mLabelRes;
private final int mIconRes;
- private final int mControlTypeForLog;
+ private final EventEnum mEventId;
private final OnLongClickListener mClickListener;
- public OptionItem(int labelRes, int iconRes, int controlTypeForLog,
+ public OptionItem(int labelRes, int iconRes, EventEnum eventId,
OnLongClickListener clickListener) {
mLabelRes = labelRes;
mIconRes = iconRes;
- mControlTypeForLog = controlTypeForLog;
+ mEventId = eventId;
mClickListener = clickListener;
}
}
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 5653801..6a83332 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -67,7 +67,6 @@
private final static int MAX_TRACK_ALPHA = 30;
private final static int SCROLL_BAR_VIS_DURATION = 150;
- private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 0.75f;
private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
Collections.singletonList(new Rect());
@@ -184,7 +183,7 @@
if (mThumbOffsetY == y) {
return;
}
- updatePopupY((int) y);
+ updatePopupY(y);
mThumbOffsetY = y;
invalidate();
}
@@ -237,7 +236,7 @@
} else if (mRv.supportsFastScrolling()
&& isNearScrollBar(mDownX)) {
calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
- updateFastScrollSectionNameAndThumbOffset(mLastY, y);
+ updateFastScrollSectionNameAndThumbOffset(y);
}
break;
case MotionEvent.ACTION_MOVE:
@@ -252,7 +251,7 @@
calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
}
if (mIsDragging) {
- updateFastScrollSectionNameAndThumbOffset(mLastY, y);
+ updateFastScrollSectionNameAndThumbOffset(y);
}
break;
case MotionEvent.ACTION_UP:
@@ -281,7 +280,7 @@
showActiveScrollbar(true);
}
- private void updateFastScrollSectionNameAndThumbOffset(int lastY, int y) {
+ private void updateFastScrollSectionNameAndThumbOffset(int y) {
// Update the fastscroller section name at this touch position
int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 39e1eac..22faf97 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -27,6 +27,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -53,6 +54,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.graphics.ColorUtils;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
@@ -62,10 +64,10 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -77,12 +79,11 @@
import java.util.List;
-
/**
* Simple scrim which draws a flat color
*/
public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener,
- AccessibilityStateChangeListener, StateListener {
+ AccessibilityStateChangeListener {
public static final IntProperty<ScrimView> DRAG_HANDLE_ALPHA =
new IntProperty<ScrimView>("dragHandleAlpha") {
@@ -115,6 +116,17 @@
private final WallpaperColorInfo mWallpaperColorInfo;
private final AccessibilityManager mAM;
protected final int mEndScrim;
+ protected final boolean mIsScrimDark;
+
+ private final StateListener<LauncherState> mAccessibilityLauncherStateListener =
+ new StateListener<LauncherState>() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ setImportantForAccessibility(finalState == ALL_APPS
+ ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
+ };
protected float mMaxScrimAlpha;
@@ -146,6 +158,7 @@
mLauncher = Launcher.cast(Launcher.getLauncher(context));
mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+ mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f;
mMaxScrimAlpha = 0.7f;
@@ -177,7 +190,7 @@
@Override
public void setInsets(Rect insets) {
updateDragHandleBounds();
- updateDragHandleVisibility(null);
+ updateDragHandleVisibility();
}
@Override
@@ -223,6 +236,7 @@
mProgress = progress;
stopDragHandleEducationAnim();
updateColors();
+ updateSysUiColors();
updateDragHandleAlpha();
invalidate();
}
@@ -235,6 +249,17 @@
mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha));
}
+ protected void updateSysUiColors() {
+ // Use a light system UI (dark icons) if all apps is behind at least half of the
+ // status bar.
+ boolean forceChange = mProgress <= 0.1f;
+ if (forceChange) {
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark);
+ } else {
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
+ }
+ }
+
protected void updateDragHandleAlpha() {
if (mDragHandle != null) {
mDragHandle.setAlpha(mDragHandleAlpha);
@@ -374,19 +399,23 @@
@Override
public void onAccessibilityStateChanged(boolean enabled) {
- LauncherStateManager stateManager = mLauncher.getStateManager();
- stateManager.removeStateListener(this);
+ StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+ stateManager.removeStateListener(mAccessibilityLauncherStateListener);
if (enabled) {
- stateManager.addStateListener(this);
- handleStateChangedComplete(stateManager.getState());
+ stateManager.addStateListener(mAccessibilityLauncherStateListener);
+ mAccessibilityLauncherStateListener.onStateTransitionComplete(stateManager.getState());
} else {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
+ updateDragHandleVisibility();
+ }
+
+ public void updateDragHandleVisibility() {
updateDragHandleVisibility(null);
}
- private void updateDragHandleVisibility(Drawable recycle) {
+ private void updateDragHandleVisibility(@Nullable Drawable recycle) {
boolean visible = shouldDragHandleBeVisible();
boolean wasVisible = mDragHandle != null;
if (visible != wasVisible) {
@@ -424,20 +453,6 @@
mAccessibilityHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
}
- @Override
- public void onStateTransitionStart(LauncherState toState) {}
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- handleStateChangedComplete(finalState);
- }
-
- private void handleStateChangedComplete(LauncherState finalState) {
- setImportantForAccessibility(finalState == ALL_APPS
- ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- }
-
protected class AccessibilityHelper extends ExploreByTouchHelper {
private static final int DRAG_HANDLE_ID = 1;
diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java
index dc0e2e0..513ce59 100644
--- a/src/com/android/launcher3/views/Snackbar.java
+++ b/src/com/android/launcher3/views/Snackbar.java
@@ -29,7 +29,7 @@
import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -44,7 +44,7 @@
private static final long HIDE_DURATION_MS = 180;
private static final int TIMEOUT_DURATION_MS = 4000;
- private final Launcher mLauncher;
+ private final BaseDraggingActivity mActivity;
private Runnable mOnDismissed;
public Snackbar(Context context, AttributeSet attrs) {
@@ -53,25 +53,25 @@
public Snackbar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
+ mActivity = BaseDraggingActivity.fromContext(context);
inflate(context, R.layout.snackbar, this);
}
- public static void show(Launcher launcher, int labelStringResId, int actionStringResId,
- Runnable onDismissed, Runnable onActionClicked) {
- closeOpenViews(launcher, true, TYPE_SNACKBAR);
- Snackbar snackbar = new Snackbar(launcher, null);
+ public static void show(BaseDraggingActivity activity, int labelStringResId,
+ int actionStringResId, Runnable onDismissed, Runnable onActionClicked) {
+ closeOpenViews(activity, true, TYPE_SNACKBAR);
+ Snackbar snackbar = new Snackbar(activity, null);
// Set some properties here since inflated xml only contains the children.
snackbar.setOrientation(HORIZONTAL);
snackbar.setGravity(Gravity.CENTER_VERTICAL);
- Resources res = launcher.getResources();
+ Resources res = activity.getResources();
snackbar.setElevation(res.getDimension(R.dimen.snackbar_elevation));
int padding = res.getDimensionPixelSize(R.dimen.snackbar_padding);
snackbar.setPadding(padding, padding, padding, padding);
snackbar.setBackgroundResource(R.drawable.round_rect_primary);
snackbar.mIsOpen = true;
- DragLayer dragLayer = launcher.getDragLayer();
+ BaseDragLayer dragLayer = activity.getDragLayer();
dragLayer.addView(snackbar);
DragLayer.LayoutParams params = (DragLayer.LayoutParams) snackbar.getLayoutParams();
@@ -80,7 +80,7 @@
int maxMarginLeftRight = res.getDimensionPixelSize(R.dimen.snackbar_max_margin_left_right);
int minMarginLeftRight = res.getDimensionPixelSize(R.dimen.snackbar_min_margin_left_right);
int marginBottom = res.getDimensionPixelSize(R.dimen.snackbar_margin_bottom);
- Rect insets = launcher.getDeviceProfile().getInsets();
+ Rect insets = activity.getDeviceProfile().getInsets();
int maxWidth = dragLayer.getWidth() - minMarginLeftRight * 2 - insets.left - insets.right;
int minWidth = dragLayer.getWidth() - maxMarginLeftRight * 2 - insets.left - insets.right;
params.width = minWidth;
@@ -135,7 +135,7 @@
.setDuration(SHOW_DURATION_MS)
.setInterpolator(Interpolators.ACCEL_DEACCEL)
.start();
- int timeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(launcher,
+ int timeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(activity,
TIMEOUT_DURATION_MS, FLAG_CONTENT_TEXT | FLAG_CONTENT_CONTROLS);
snackbar.postDelayed(() -> snackbar.close(true), timeout);
}
@@ -160,7 +160,7 @@
}
private void onClosed() {
- mLauncher.getDragLayer().removeView(this);
+ mActivity.getDragLayer().removeView(this);
if (mOnDismissed != null) {
mOnDismissed.run();
}
@@ -179,7 +179,7 @@
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- DragLayer dl = mLauncher.getDragLayer();
+ BaseDragLayer dl = mActivity.getDragLayer();
if (!dl.isEventOverView(this, ev)) {
close(true);
}
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index d849138..d35a38f 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -32,18 +32,19 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsPagedView;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.userevent.nano.LauncherLogProto;
/**
* On boarding flow for users right after setting up work profile
*/
-public class WorkEduView extends AbstractSlideInView implements Insettable {
+public class WorkEduView extends AbstractSlideInView
+ implements Insettable, StateListener<LauncherState> {
private static final int DEFAULT_CLOSE_DURATION = 200;
public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
@@ -82,6 +83,12 @@
}
@Override
+ protected void onCloseComplete() {
+ super.onCloseComplete();
+ mLauncher.getStateManager().removeStateListener(this);
+ }
+
+ @Override
public void logActionCommand(int command) {
// Since this is on-boarding popup, it is not a user controlled action.
}
@@ -150,6 +157,7 @@
private void show() {
attachToContainer();
animateOpen();
+ mLauncher.getStateManager().addStateListener(this);
}
@Override
@@ -177,8 +185,8 @@
/**
* Checks if user has not seen onboarding UI yet and shows it when user navigates to all apps
*/
- public static LauncherStateManager.StateListener showEduFlowIfNeeded(Launcher launcher,
- @Nullable LauncherStateManager.StateListener oldListener) {
+ public static StateListener<LauncherState> showEduFlowIfNeeded(Launcher launcher,
+ @Nullable StateListener<LauncherState> oldListener) {
if (oldListener != null) {
launcher.getStateManager().removeStateListener(oldListener);
}
@@ -187,12 +195,7 @@
return null;
}
- LauncherStateManager.StateListener listener = new LauncherStateManager.StateListener() {
- @Override
- public void onStateTransitionStart(LauncherState toState) {
-
- }
-
+ StateListener<LauncherState> listener = new StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState != LauncherState.ALL_APPS) return;
@@ -227,4 +230,9 @@
private static boolean hasSeenLegacyEdu(Launcher launcher) {
return launcher.getSharedPrefs().getBoolean(KEY_LEGACY_WORK_EDU_SEEN, false);
}
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ close(false);
+ }
}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 23c2160..3e5113a 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -28,10 +28,10 @@
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 78acc34..6f2e179 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -19,8 +19,6 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.PointF;
-import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
import android.util.SparseBooleanArray;
@@ -35,14 +33,13 @@
import android.widget.RemoteViews;
import com.android.launcher3.CheckLongPressHelper;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
@@ -51,7 +48,7 @@
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
- implements TouchCompleteListener, View.OnLongClickListener, DraggableView {
+ implements TouchCompleteListener, View.OnLongClickListener {
// Related to the auto-advancing of widgets
private static final long ADVANCE_INTERVAL = 20000;
@@ -73,15 +70,7 @@
private boolean mIsAutoAdvanceRegistered;
private Runnable mAutoAdvanceRunnable;
- /**
- * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
- */
- private float mScaleToFit = 1f;
- /**
- * The translation values to center the widget within its cellspans.
- */
- private final PointF mTranslationForCentering = new PointF(0, 0);
public LauncherAppWidgetHostView(Context context) {
super(context);
@@ -307,26 +296,6 @@
scheduleNextAdvance();
}
- public void setScaleToFit(float scale) {
- mScaleToFit = scale;
- setScaleX(scale);
- setScaleY(scale);
- }
-
- public float getScaleToFit() {
- return mScaleToFit;
- }
-
- public void setTranslationForCentering(float x, float y) {
- mTranslationForCentering.set(x, y);
- setTranslationX(x);
- setTranslationY(y);
- }
-
- public PointF getTranslationForCentering() {
- return mTranslationForCentering;
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -357,18 +326,4 @@
}
return false;
}
-
- @Override
- public int getViewType() {
- return DRAGGABLE_WIDGET;
- }
-
- @Override
- public void getVisualDragBounds(Rect bounds) {
- int x = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2;
- int y = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2;
- int width = (int) getScaleToFit() * getMeasuredWidth();
- int height = (int) getScaleToFit() * getMeasuredHeight();
- bounds.set(x, y , x + width, y + height);
- }
}
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 104ad77..ed42bc4 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -18,12 +18,14 @@
import android.appwidget.AppWidgetHostView;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
import java.util.ArrayList;
@@ -32,7 +34,21 @@
* Extension of AppWidgetHostView with support for controlled keyboard navigation.
*/
public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
- implements DraggableView {
+ implements DraggableView, Reorderable {
+
+ /**
+ * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
+ */
+ private float mScaleToFit = 1f;
+
+ /**
+ * The translation values to center the widget within its cellspans.
+ */
+ private final PointF mTranslationForCentering = new PointF(0, 0);
+
+ private final PointF mTranslationForReorderBounce = new PointF(0, 0);
+ private final PointF mTranslationForReorderPreview = new PointF(0, 0);
+ private float mScaleForReorderBounce = 1f;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
@@ -137,13 +153,75 @@
setSelected(childIsFocused);
}
+ public View getView() {
+ return this;
+ }
+
+ private void updateTranslation() {
+ super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+ + mTranslationForCentering.x);
+ super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
+ + mTranslationForCentering.y);
+ }
+
+ public void setTranslationForCentering(float x, float y) {
+ mTranslationForCentering.set(x, y);
+ updateTranslation();
+ }
+
+ public void setReorderBounceOffset(float x, float y) {
+ mTranslationForReorderBounce.set(x, y);
+ updateTranslation();
+ }
+
+ public void getReorderBounceOffset(PointF offset) {
+ offset.set(mTranslationForReorderBounce);
+ }
+
+ @Override
+ public void setReorderPreviewOffset(float x, float y) {
+ mTranslationForReorderPreview.set(x, y);
+ updateTranslation();
+ }
+
+ @Override
+ public void getReorderPreviewOffset(PointF offset) {
+ offset.set(mTranslationForReorderPreview);
+ }
+
+ private void updateScale() {
+ super.setScaleX(mScaleToFit * mScaleForReorderBounce);
+ super.setScaleY(mScaleToFit * mScaleForReorderBounce);
+ }
+
+ public void setReorderBounceScale(float scale) {
+ mScaleForReorderBounce = scale;
+ updateScale();
+ }
+
+ public float getReorderBounceScale() {
+ return mScaleForReorderBounce;
+ }
+
+ public void setScaleToFit(float scale) {
+ mScaleToFit = scale;
+ updateScale();
+ }
+
+ public float getScaleToFit() {
+ return mScaleToFit;
+ }
+
@Override
public int getViewType() {
return DRAGGABLE_WIDGET;
}
@Override
- public void getVisualDragBounds(Rect bounds) {
- bounds.set(0, 0 , getMeasuredWidth(), getMeasuredHeight());
+ public void getWorkspaceVisualDragBounds(Rect bounds) {
+ int width = (int) (getMeasuredWidth() * mScaleToFit);
+ int height = (int) (getMeasuredHeight() * mScaleToFit);
+
+ bounds.set(0, 0 , width, height);
}
}
diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
index 6e21a41..9601652 100644
--- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.widget;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
+
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
@@ -32,5 +34,6 @@
componentName = activityInfo.getComponent();
user = activityInfo.getUser();
itemType = activityInfo.getItemType();
+ this.container = CONTAINER_WIDGETS_TRAY;
}
}
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index bc40484..bef9a08 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.widget;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
+
import android.appwidget.AppWidgetHostView;
import android.os.Bundle;
@@ -50,6 +52,7 @@
spanY = i.spanY;
minSpanX = i.minSpanX;
minSpanY = i.minSpanY;
+ this.container = CONTAINER_WIDGETS_TRAY;
}
public WidgetAddFlowHandler getHandler() {
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 895f8de..9021d9e 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -37,12 +37,12 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.ItemInfoWithIcon;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.R;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
-import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Themes;
diff --git a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
index 5387be8..ebc2a25 100644
--- a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
+++ b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
@@ -20,10 +20,10 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.PendingRequestArgs;
/**
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 4a0b4ef..bef91d2 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -55,7 +55,7 @@
private static final int FADE_IN_DURATION_MS = 90;
/** Widget cell width is calculated by multiplying this factor to grid cell width. */
- private static final float WIDTH_SCALE = 2.6f;
+ private static final float WIDTH_SCALE = 3f;
/** Widget preview width is calculated by multiplying this factor to the widget cell width. */
private static final float PREVIEW_SCALE = 0.8f;
@@ -104,7 +104,7 @@
}
private void setContainerWidth() {
- mCellSize = (int) (mDeviceProfile.cellWidthPx * WIDTH_SCALE);
+ mCellSize = (int) (mDeviceProfile.allAppsIconSizePx * WIDTH_SCALE);
mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
}
diff --git a/src/com/android/launcher3/widget/WidgetListRowEntry.java b/src/com/android/launcher3/widget/WidgetListRowEntry.java
index 335b8c7..17e4673 100644
--- a/src/com/android/launcher3/widget/WidgetListRowEntry.java
+++ b/src/com/android/launcher3/widget/WidgetListRowEntry.java
@@ -15,9 +15,9 @@
*/
package com.android.launcher3.widget;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.PackageItemInfo;
import java.util.ArrayList;
diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java
index f3c7822..4b6c569 100644
--- a/src/com/android/launcher3/widget/WidgetManagerHelper.java
+++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java
@@ -28,10 +28,10 @@
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 8d1a3b0..30be7a6 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -32,12 +32,12 @@
import android.widget.TextView;
import com.android.launcher3.Insettable;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.PackageUserKey;
import java.util.List;
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
index f3b325d..df6e2c3 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -21,7 +21,7 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
import java.util.ArrayList;
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index b3e9734..68a3ec5 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -39,10 +38,8 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.TopRoundedCornerView;
@@ -71,14 +68,6 @@
}
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsFullSheet: " + ev);
- }
- return super.dispatchTouchEvent(ev);
- }
-
public WidgetsFullSheet(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -143,7 +132,7 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthUsed;
if (mInsets.bottom > 0) {
- widthUsed = 0;
+ widthUsed = mInsets.left + mInsets.right;
} else {
Rect padding = mLauncher.getDeviceProfile().workspacePadding;
widthUsed = Math.max(padding.left + padding.right,
@@ -164,7 +153,7 @@
// Content is laid out as center bottom aligned
int contentWidth = mContent.getMeasuredWidth();
- int contentLeft = (width - contentWidth) / 2;
+ int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
contentLeft + contentWidth, height);
@@ -193,6 +182,7 @@
.setDuration(DEFAULT_OPEN_DURATION)
.setInterpolator(AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.linear_out_slow_in));
+ mRecyclerView.setLayoutFrozen(true);
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -202,7 +192,6 @@
}
});
post(() -> {
- mRecyclerView.setLayoutFrozen(true);
mOpenCloseAnimator.start();
mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
});
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 7ec6214..82d4110 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -158,23 +158,13 @@
mScrollbar.isHitInParent(e.getX(), e.getY(), mFastScrollerOffset);
}
if (mTouchDownOnScroller) {
- final boolean result = mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onInterceptTouchEvent 1 " + result);
- }
- return result;
- }
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onInterceptTouchEvent 2 false");
+ return mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
}
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsRecyclerView.onTouchEvent");
- }
if (mTouchDownOnScroller) {
mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
}
@@ -182,42 +172,5 @@
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onRequestDisallowInterceptTouchEvent "
- + disallowIntercept);
- }
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- final boolean result = super.dispatchTouchEvent(ev);
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsRecyclerView: state: "
- + getScrollState()
- + " can scroll: " + getLayoutManager().canScrollVertically()
- + " result: " + result
- + " layout suppressed: " + isLayoutSuppressed()
- + " event: " + ev);
- }
- return result;
- }
-
- @Override
- public void stopNestedScroll() {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "stopNestedScroll");
- }
- super.stopNestedScroll();
- }
-
- @Override
- public void setLayoutFrozen(boolean frozen) {
- if (frozen != isLayoutSuppressed()) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "setLayoutFrozen " + frozen
- + " @ " + android.util.Log.getStackTraceString(new Throwable()));
- }
- }
- super.setLayoutFrozen(frozen);
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index 0ea7d85..0b66ec0 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -29,8 +29,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageUserKey;
diff --git a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
new file mode 100644
index 0000000..c57f07d
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
@@ -0,0 +1,50 @@
+/*
+ * 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.systemui.plugins;
+
+import android.app.Activity;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin interface to replace the all apps recycler view of the all apps drawer.
+ */
+@ProvidesInterface(action = AllAppsSearchPlugin.ACTION, version = AllAppsSearchPlugin.VERSION)
+public interface AllAppsSearchPlugin extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_ALL_APPS_SEARCH_ACTIONS";
+ int VERSION = 3;
+
+ /** Following are the order that these methods should be called. */
+ void setup(ViewGroup parent, Activity activity, float allAppsContainerHeight);
+
+ /**
+ * When drag starts, pass window inset related fields and the progress to indicate
+ * whether user is swiping down or swiping up
+ */
+ void onDragStart(float progress);
+
+ /** progress is between [0, 1] 1: down, 0: up */
+ void setProgress(float progress);
+
+ /** Called when container animation stops, so that plugin can perform cleanups */
+ void onAnimationEnd(float progress);
+
+ /** pass over the search box object */
+ void setEditText(EditText editText);
+}
diff --git a/src_plugins/com/android/systemui/plugins/OWNERS b/src_plugins/com/android/systemui/plugins/OWNERS
new file mode 100644
index 0000000..0514999
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/OWNERS
@@ -0,0 +1,4 @@
+# When changing interface for this plugin OR when increasing version code, please add Alex
+# Only add other owners if Alex is not available
+per-file AllAppsSearchPlugin.java, globs = set noparent
+per-file AllAppsSearchPlugin.java = alexmang@google.com, hyunyoungs@google.com, sunnygoyal@google.com, twickham@google.com
diff --git a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java
index 28a9193..a434d07 100644
--- a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.plugins;
+import android.view.MotionEvent;
+
import com.android.systemui.plugins.annotations.ProvidesInterface;
/**
@@ -28,7 +30,7 @@
public interface OverscrollPlugin extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERSCROLL";
- int VERSION = 3;
+ int VERSION = 4;
String DEVICE_STATE_LOCKED = "Locked";
String DEVICE_STATE_LAUNCHER = "Launcher";
@@ -41,33 +43,33 @@
boolean isActive();
/**
- * Called when a touch is down and has been recognized as an overscroll gesture.
- * A call of this method will always result in `onTouchUp` being called, and possibly
- * `onFling` as well.
- *
+ * Called when a touch has been recognized as an overscroll gesture.
+ * @param horizontalDistancePx Horizontal distance from the last finger location to the finger
+ * location when it first touched the screen.
+ * @param verticalDistancePx Horizontal distance from the last finger location to the finger
+ * location when it first touched the screen.
+ * @param thresholdPx Minimum distance for gesture.
+ * @param flingDistanceThresholdPx Minimum distance for gesture by fling.
+ * @param flingVelocityThresholdPx Minimum velocity for gesture by fling.
* @param deviceState String representing the current device state
* @param underlyingActivity String representing the currently active Activity
*/
- void onTouchStart(String deviceState, String underlyingActivity);
+ void onTouchEvent(MotionEvent event,
+ int horizontalDistancePx,
+ int verticalDistancePx,
+ int thresholdPx,
+ int flingDistanceThresholdPx,
+ int flingVelocityThresholdPx,
+ String deviceState,
+ String underlyingActivity);
/**
- * Called when a touch that was previously recognized has moved.
- *
- * @param px distance between the position of touch on this update and the position of the
- * touch when it was initially recognized.
+ * @return `true` if overscroll gesture handling should override all other gestures.
*/
- void onTouchTraveled(int px);
+ boolean blockOtherGestures();
/**
- * Called when a touch that was previously recognized has ended.
- *
- * @param px distance between the position of touch on this update and the position of the
- * touch when it was initially recognized.
+ * @return `true` if the overscroll gesture can pan the underlying app.
*/
- void onTouchEnd(int px);
-
- /**
- * Called when the user starts Compose with a fling. `onTouchUp` will also be called.
- */
- void onFling(float velocity);
+ boolean allowsUnderlyingActivityOverscroll();
}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 0b99e7a..9d87788 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -24,6 +24,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageUserKey;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/src_ui_overrides/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
similarity index 64%
rename from src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
rename to src_ui_overrides/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
index 4913cad..4893c17 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
@@ -13,15 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.launcher3.uioverrides;
-import android.content.Context;
-import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
-/** Render preview using surface view. */
-public class PreviewSurfaceRenderer {
+import com.android.launcher3.model.data.WorkspaceItemInfo;
- /** Handle a received surface view request. */
- public static void render(Context context, Bundle bundle) { }
+/** A util class that inflates a predicted app icon */
+public class PredictedAppIconInflater {
+ public static View inflate(LayoutInflater inflater, ViewGroup parent, WorkspaceItemInfo info) {
+ return null;
+ }
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index a56801f..ec3f93f 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -15,10 +15,10 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import com.android.launcher3.AbstractFloatingView;
+import android.content.Context;
+
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
@@ -31,7 +31,7 @@
private static final float PARALLAX_COEFFICIENT = .125f;
- private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
+ private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE;
private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
@Override
@@ -45,21 +45,11 @@
}
@Override
- public int getTransitionDuration(Launcher context) {
+ public int getTransitionDuration(Context context) {
return 320;
}
@Override
- public void onStateEnabled(Launcher launcher) {
- if (!launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
- launcher.getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
- }
-
- AbstractFloatingView.closeAllOpenViews(launcher);
- dispatchWindowStateChanged(launcher);
- }
-
- @Override
public String getDescription(Launcher launcher) {
return launcher.getString(R.string.all_apps_button_label);
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index e20b2ca..7a6332c 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -15,7 +15,8 @@
*/
package com.android.launcher3.uioverrides.states;
-import com.android.launcher3.Launcher;
+import android.content.Context;
+
import com.android.launcher3.LauncherState;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -29,7 +30,7 @@
}
@Override
- public int getTransitionDuration(Launcher context) {
+ public int getTransitionDuration(Context context) {
return 250;
}
@@ -44,4 +45,11 @@
public static OverviewState newSwitchState(int id) {
return new OverviewState(id);
}
+
+ /**
+ * New Overview substate that represents the overview in modal mode (one task shown on its own)
+ */
+ public static OverviewState newModalTaskState(int id) {
+ return new OverviewState(id);
+ }
}
diff --git a/tests/Android.mk b/tests/Android.mk
index a9fff8e..4d1bfa6 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -62,7 +62,11 @@
LOCAL_STATIC_JAVA_LIBRARIES += ub-launcher-aosp-tapl
endif
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, src_common)
+
+
LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
LOCAL_PACKAGE_NAME := Launcher3Tests
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 1c8f095..f243f27 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -29,6 +29,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig"
+ android:exported="true"
android:label="No Config">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -39,6 +40,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWdigetHidden"
+ android:exported="true"
android:label="Hidden widget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -49,6 +51,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
+ android:exported="true"
android:label="With Config">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -58,12 +61,14 @@
</receiver>
<activity
- android:name="com.android.launcher3.testcomponent.WidgetConfigActivity">
+ android:name="com.android.launcher3.testcomponent.WidgetConfigActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
- <activity android:name="com.android.launcher3.testcomponent.CustomShortcutConfigActivity">
+ <activity android:name="com.android.launcher3.testcomponent.CustomShortcutConfigActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
@@ -72,6 +77,7 @@
<activity
android:name="com.android.launcher3.testcomponent.RequestPinItemActivity"
android:icon="@drawable/test_drawable_pin_item"
+ android:exported="true"
android:label="Test Pin Item">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -102,6 +108,7 @@
android:stateNotNeeded="true"
android:taskAffinity=""
android:theme="@android:style/Theme.DeviceDefault.Light"
+ android:exported="true"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -114,6 +121,7 @@
<activity
android:name="com.android.launcher3.testcomponent.BaseTestingActivity"
android:label="LauncherTestApp"
+ android:exported="true"
android:taskAffinity="com.android.launcher3.testcomponent.Affinity1">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -128,6 +136,7 @@
</activity>
<activity-alias android:name="Activity2"
android:label="TestActivity2"
+ android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -136,6 +145,7 @@
</activity-alias>
<activity-alias android:name="Activity3"
android:label="TestActivity3"
+ android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -144,6 +154,7 @@
</activity-alias>
<activity-alias android:name="Activity4"
android:label="TestActivity4"
+ android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -152,6 +163,7 @@
</activity-alias>
<activity-alias android:name="Activity5"
android:label="TestActivity5"
+ android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -160,6 +172,7 @@
</activity-alias>
<activity-alias android:name="Activity6"
android:label="TestActivity6"
+ android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -168,6 +181,7 @@
</activity-alias>
<activity-alias android:name="Activity7"
android:label="TestActivity7"
+ android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -176,6 +190,7 @@
</activity-alias>
<activity-alias android:name="Activity8"
android:label="TestActivity8"
+ android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -184,6 +199,7 @@
</activity-alias>
<activity-alias android:name="Activity9"
android:label="TestActivity9"
+ android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -192,6 +208,7 @@
</activity-alias>
<activity-alias android:name="Activity10"
android:label="TestActivity10"
+ android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -200,6 +217,7 @@
</activity-alias>
<activity-alias android:name="Activity11"
android:label="TestActivity11"
+ android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/tests/dummy_app/AndroidManifest.xml b/tests/dummy_app/AndroidManifest.xml
index f00138c..d5e2320 100644
--- a/tests/dummy_app/AndroidManifest.xml
+++ b/tests/dummy_app/AndroidManifest.xml
@@ -26,6 +26,7 @@
<activity
android:name="Activity1"
android:icon="@mipmap/ic_launcher1"
+ android:exported="true"
android:label="Aardwolf">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java b/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
index 293b04a..bdf01f3 100644
--- a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
+++ b/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
@@ -20,14 +20,14 @@
import android.content.ComponentName;
-import com.android.launcher3.AppInfo;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.model.data.AppInfo;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
/**
* Unit tests for {@link DefaultAppSearchAlgorithm}
*/
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 873f1cb..858e183 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -17,7 +17,6 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -26,7 +25,6 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -39,6 +37,8 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -47,20 +47,20 @@
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.Utilities;
+import com.android.launcher3.common.WidgetUtils;
import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Wait;
@@ -100,9 +100,10 @@
public static final long DEFAULT_UI_TIMEOUT = 10000;
private static final String TAG = "AbstractLauncherUiTest";
- private static String sDetectedActivityLeak;
+ private static String sStrictmodeDetectedActivityLeak;
private static boolean sActivityLeakReported;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+ protected static final ActivityLeakTracker ACTIVITY_LEAK_TRACKER = new ActivityLeakTracker();
protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
@@ -115,30 +116,51 @@
if (TestHelpers.isInLauncherProcess()) {
StrictMode.VmPolicy.Builder builder =
new StrictMode.VmPolicy.Builder()
- .detectActivityLeaks()
+// b/154772063
+// .detectActivityLeaks()
.penaltyLog()
.penaltyListener(Runnable::run, violation -> {
- // Runs in the main thread. We can't dumpheap in the main thread,
- // so let's just mark the fact that the leak has happened.
- if (sDetectedActivityLeak == null) {
- sDetectedActivityLeak = violation.toString();
- try {
- Debug.dumpHprofData(
- getInstrumentation().getTargetContext()
- .getFilesDir().getPath()
- + "/ActivityLeakHeapDump.hprof");
- } catch (Throwable e) {
- Log.e(TAG, "dumpHprofData failed", e);
- }
+ if (sStrictmodeDetectedActivityLeak == null) {
+ sStrictmodeDetectedActivityLeak = violation.toString() + ", "
+ + dumpHprofData() + ".";
}
});
StrictMode.setVmPolicy(builder.build());
}
}
- public static void checkDetectedLeaks() {
- if (sDetectedActivityLeak != null && !sActivityLeakReported) {
+ public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
+ if (sActivityLeakReported) return;
+
+ if (sStrictmodeDetectedActivityLeak != null) {
+ // Report from the test thread strictmode violations detected in the main thread.
sActivityLeakReported = true;
+ Assert.fail(sStrictmodeDetectedActivityLeak);
+ }
+
+ // Check whether activity leak detector has found leaked activities.
+ Wait.atMost(AbstractLauncherUiTest::getActivityLeakErrorMessage,
+ () -> {
+ launcher.getTotalPssKb(); // Triggers GC
+ return MAIN_EXECUTOR.submit(
+ () -> ACTIVITY_LEAK_TRACKER.noLeakedActivities()).get();
+ }, DEFAULT_UI_TIMEOUT, launcher);
+ }
+
+ private static String getActivityLeakErrorMessage() {
+ sActivityLeakReported = true;
+ return "Activity leak detector has found leaked activities, " + dumpHprofData() + ".";
+ }
+
+ private static String dumpHprofData() {
+ try {
+ final String fileName = getInstrumentation().getTargetContext().getFilesDir().getPath()
+ + "/ActivityLeakHeapDump.hprof";
+ Debug.dumpHprofData(fileName);
+ return "memory dump filename: " + fileName;
+ } catch (Throwable e) {
+ Log.e(TAG, "dumpHprofData failed", e);
+ return "failed to save memory dump";
}
}
@@ -162,7 +184,6 @@
mLauncher.enableDebugTracing();
// Avoid double-reporting of Launcher crashes.
mLauncher.setOnLauncherCrashed(() -> mLauncherPid = 0);
- mLauncher.disableSensorRotation();
}
protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@@ -257,6 +278,15 @@
mTargetContext = InstrumentationRegistry.getTargetContext();
mTargetPackage = mTargetContext.getPackageName();
mLauncherPid = mLauncher.getPid();
+
+ UserManager userManager = mTargetContext.getSystemService(UserManager.class);
+ if (userManager != null) {
+ for (UserHandle userHandle : userManager.getUserProfiles()) {
+ if (!userHandle.isSystem()) {
+ mDevice.executeShellCommand("pm remove-user " + userHandle.getIdentifier());
+ }
+ }
+ }
}
@After
@@ -264,22 +294,15 @@
// Limits UI tests affecting tests running after them.
mLauncher.waitForLauncherInitialized();
if (mLauncherPid != 0) {
- assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+ assertEquals("Launcher crashed, pid mismatch:",
+ mLauncherPid, mLauncher.getPid().intValue());
}
- checkDetectedLeaks();
+ checkDetectedLeaks(mLauncher);
}
- protected void clearLauncherData() throws IOException, InterruptedException {
- if (TestHelpers.isInLauncherProcess()) {
- LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
- resetLoaderState();
- } else {
- clearPackageData(mDevice.getLauncherPackageName());
- mLauncher.enableDebugTracing();
- mLauncherPid = mLauncher.getPid();
- mLauncher.disableSensorRotation();
- }
+ protected void clearLauncherData() {
+ mLauncher.clearLauncherData();
+ mLauncher.waitForLauncherInitialized();
}
/**
@@ -308,26 +331,7 @@
* Adds {@param item} on the homescreen on the 0th screen
*/
protected void addItemToScreen(ItemInfo item) {
- ContentResolver resolver = mTargetContext.getContentResolver();
- int screenId = FIRST_SCREEN_ID;
- // Update the screen id counter for the provider.
- LauncherSettings.Settings.call(resolver,
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
-
- if (screenId > FIRST_SCREEN_ID) {
- screenId = FIRST_SCREEN_ID;
- }
-
- // Insert the item
- ContentWriter writer = new ContentWriter(mTargetContext);
- item.id = LauncherSettings.Settings.call(
- resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
- item.screenId = screenId;
- item.onAddToDatabase(writer);
- writer.put(LauncherSettings.Favorites._ID, item.id);
- resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
- writer.getValues(mTargetContext));
+ WidgetUtils.addItemToScreen(item, mTargetContext);
resetLoaderState();
// Launch the home activity
@@ -526,7 +530,7 @@
private static void checkLauncherIntegrity(
Launcher launcher, ContainerType expectedContainerType) {
if (launcher != null) {
- final LauncherStateManager stateManager = launcher.getStateManager();
+ final StateManager<LauncherState> stateManager = launcher.getStateManager();
final LauncherState stableState = stateManager.getCurrentStableState();
assertTrue("Stable state != state: " + stableState.getClass().getSimpleName() + ", "
diff --git a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
new file mode 100644
index 0000000..202dcb1
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
@@ -0,0 +1,92 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.launcher3.tapl.TestHelpers;
+
+import java.util.WeakHashMap;
+
+public class ActivityLeakTracker implements Application.ActivityLifecycleCallbacks {
+ private final WeakHashMap<Activity, Boolean> mActivities = new WeakHashMap<>();
+
+ private int mActivitiesCreated;
+
+ ActivityLeakTracker() {
+ if (!TestHelpers.isInLauncherProcess()) return;
+ final Application app =
+ (Application) InstrumentationRegistry.getTargetContext().getApplicationContext();
+ app.registerActivityLifecycleCallbacks(this);
+ }
+
+ public int getActivitiesCreated() {
+ return mActivitiesCreated;
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ mActivities.put(activity, true);
+ ++mActivitiesCreated;
+ }
+
+ @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) {
+ }
+
+ public boolean noLeakedActivities() {
+ int liveActivities = 0;
+ int destroyedActivities = 0;
+
+ for (Activity activity : mActivities.keySet()) {
+ if (activity.isDestroyed()) {
+ ++destroyedActivities;
+ } else {
+ ++liveActivities;
+ }
+ }
+
+ if (liveActivities > 2) return false;
+
+ // It's OK to have 1 leaked activity if no active activities exist.
+ return liveActivities == 0 ? destroyedActivities <= 1 : destroyedActivities == 0;
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 38f50c1..266f0ae 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -56,7 +56,7 @@
private void evaluateInPortrait() throws Throwable {
mTest.mDevice.setOrientationNatural();
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
- AbstractLauncherUiTest.checkDetectedLeaks();
+ AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
base.evaluate();
mTest.getDevice().pressHome();
}
@@ -64,7 +64,7 @@
private void evaluateInLandscape() throws Throwable {
mTest.mDevice.setOrientationLeft();
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
- AbstractLauncherUiTest.checkDetectedLeaks();
+ AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
base.evaluate();
mTest.getDevice().pressHome();
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index de1ada4..34e425d 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -64,7 +64,7 @@
test.waitForResumed("Launcher internal state is still Background");
// Check that we switched to home.
test.mLauncher.getWorkspace();
- AbstractLauncherUiTest.checkDetectedLeaks();
+ AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher);
}
// Please don't add negative test cases for methods that fail only after a long wait.
@@ -314,7 +314,7 @@
switchToAllApps();
allApps.freeze();
try {
- allApps.getAppIcon(APP_NAME).dragToWorkspace(false);
+ allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
} finally {
allApps.unfreeze();
@@ -342,7 +342,7 @@
getMenuItem(0);
final String shortcutName = menuItem.getText();
- menuItem.dragToWorkspace(false);
+ menuItem.dragToWorkspace(false, false);
mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());
} finally {
allApps.unfreeze();
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index d93915c..9d4ccff 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -28,10 +28,10 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Workspace;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.testcomponent.WidgetConfigActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
@@ -94,7 +94,7 @@
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
widgets.
getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager())).
- dragToWorkspace(true);
+ dragToWorkspace(true, false);
// Widget id for which the config activity was opened
mWidgetId = monitor.getWidgetId();
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 788e041..f146db5 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -23,8 +23,8 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.tapl.Widget;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
@@ -57,7 +57,7 @@
getWorkspace().
openAllWidgets().
getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())).
- dragToWorkspace(false);
+ dragToWorkspace(false, false);
assertTrue(mActivityMonitor.itemExists(
(info, view) -> info instanceof LauncherAppWidgetInfo &&
@@ -83,7 +83,7 @@
mDevice.pressHome();
mLauncher.getWorkspace().openAllWidgets()
.getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
- .dragToWorkspace(false);
+ .dragToWorkspace(false, true);
mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
.launch(getAppPackageName());
}
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 001a88f..a4aa9f2 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -17,18 +17,16 @@
import static androidx.test.InstrumentationRegistry.getTargetContext;
-import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
+import static com.android.launcher3.common.WidgetUtils.createWidgetInfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentResolver;
-import android.content.Context;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
@@ -39,18 +37,16 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.LauncherAppWidgetHost;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.tapl.Widget;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
import org.junit.After;
@@ -108,7 +104,7 @@
@Test
public void testBindNormalWidget_withConfig() {
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true);
- LauncherAppWidgetInfo item = createWidgetInfo(info, true);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
addItemToScreen(item);
verifyWidgetPresent(info);
@@ -117,7 +113,7 @@
@Test
public void testBindNormalWidget_withoutConfig() {
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
- LauncherAppWidgetInfo item = createWidgetInfo(info, true);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
addItemToScreen(item);
verifyWidgetPresent(info);
@@ -126,7 +122,7 @@
@Test
public void testUnboundWidget_removed() {
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
- LauncherAppWidgetInfo item = createWidgetInfo(info, false);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
item.appWidgetId = -33;
addItemToScreen(item);
@@ -147,7 +143,7 @@
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
// Do not bind the widget
- LauncherAppWidgetInfo item = createWidgetInfo(info, false);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
addItemToScreen(item);
@@ -160,7 +156,7 @@
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true);
// Do not bind the widget
- LauncherAppWidgetInfo item = createWidgetInfo(info, false);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
addItemToScreen(item);
@@ -269,60 +265,17 @@
private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT);
- if (widget == null) mLauncher.dumpViewHierarchy(); // b/152645831
assertTrue("Widget is not present",
widget != null);
}
private void verifyPendingWidgetPresent() {
final Widget widget = mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT);
- if (widget == null) mLauncher.dumpViewHierarchy(); // b/152645831
assertTrue("Pending widget is not present",
widget != null);
}
/**
- * Creates a LauncherAppWidgetInfo corresponding to {@param info}
- *
- * @param bindWidget if true the info is bound and a valid widgetId is assigned to
- * the LauncherAppWidgetInfo
- */
- public static LauncherAppWidgetInfo createWidgetInfo(
- LauncherAppWidgetProviderInfo info, boolean bindWidget) {
- Context targetContext = getTargetContext();
-
- LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(
- LauncherAppWidgetInfo.NO_ID, info.provider);
- item.spanX = info.minSpanX;
- item.spanY = info.minSpanY;
- item.minSpanX = info.minSpanX;
- item.minSpanY = info.minSpanY;
- item.user = info.getProfile();
- item.cellX = 0;
- item.cellY = 1;
- item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
-
- if (bindWidget) {
- PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info);
- pendingInfo.spanX = item.spanX;
- pendingInfo.spanY = item.spanY;
- pendingInfo.minSpanX = item.minSpanX;
- pendingInfo.minSpanY = item.minSpanY;
- Bundle options = getDefaultOptionsForWidget(targetContext, pendingInfo);
-
- AppWidgetHost host = new LauncherAppWidgetHost(targetContext);
- int widgetId = host.allocateAppWidgetId();
- if (!new WidgetManagerHelper(targetContext)
- .bindAppWidgetIdIfAllowed(widgetId, info, options)) {
- host.deleteAppWidgetId(widgetId);
- throw new IllegalArgumentException("Unable to bind widget id");
- }
- item.appWidgetId = widgetId;
- }
- return item;
- }
-
- /**
* Returns a LauncherAppWidgetInfo with package name which is not present on the device
*/
private LauncherAppWidgetInfo getInvalidWidgetInfo() {
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 0246f95..0e43d81 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -29,12 +29,12 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace.ItemOperator;
-import com.android.launcher3.WorkspaceItemInfo;
+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 com.android.launcher3.tapl.AddToHomeScreenPrompt;
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
diff --git a/tests/src_common/README.md b/tests/src_common/README.md
new file mode 100644
index 0000000..2bc9e73
--- /dev/null
+++ b/tests/src_common/README.md
@@ -0,0 +1 @@
+Common source code used by both android tests and robolectric tests
\ No newline at end of file
diff --git a/tests/src_common/com/android/launcher3/common/WidgetUtils.java b/tests/src_common/com/android/launcher3/common/WidgetUtils.java
new file mode 100644
index 0000000..c0913bf
--- /dev/null
+++ b/tests/src_common/com/android/launcher3/common/WidgetUtils.java
@@ -0,0 +1,104 @@
+/*
+ * 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.common;
+
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
+
+import android.appwidget.AppWidgetHost;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.launcher3.LauncherAppWidgetHost;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetManagerHelper;
+
+/**
+ * Common method for widget binding
+ */
+public class WidgetUtils {
+
+ /**
+ * Creates a LauncherAppWidgetInfo corresponding to {@param info}
+ *
+ * @param bindWidget if true the info is bound and a valid widgetId is assigned to
+ * the LauncherAppWidgetInfo
+ */
+ public static LauncherAppWidgetInfo createWidgetInfo(
+ LauncherAppWidgetProviderInfo info, Context targetContext, boolean bindWidget) {
+ LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(
+ LauncherAppWidgetInfo.NO_ID, info.provider);
+ item.spanX = info.minSpanX;
+ item.spanY = info.minSpanY;
+ item.minSpanX = info.minSpanX;
+ item.minSpanY = info.minSpanY;
+ item.user = info.getProfile();
+ item.cellX = 0;
+ item.cellY = 1;
+ item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+
+ if (bindWidget) {
+ PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info);
+ pendingInfo.spanX = item.spanX;
+ pendingInfo.spanY = item.spanY;
+ pendingInfo.minSpanX = item.minSpanX;
+ pendingInfo.minSpanY = item.minSpanY;
+ Bundle options = getDefaultOptionsForWidget(targetContext, pendingInfo);
+
+ AppWidgetHost host = new LauncherAppWidgetHost(targetContext);
+ int widgetId = host.allocateAppWidgetId();
+ if (!new WidgetManagerHelper(targetContext)
+ .bindAppWidgetIdIfAllowed(widgetId, info, options)) {
+ host.deleteAppWidgetId(widgetId);
+ throw new IllegalArgumentException("Unable to bind widget id");
+ }
+ item.appWidgetId = widgetId;
+ }
+ return item;
+ }
+
+ /**
+ * Adds {@param item} on the homescreen on the 0th screen
+ */
+ public static void addItemToScreen(ItemInfo item, Context targetContext) {
+ ContentResolver resolver = targetContext.getContentResolver();
+ int screenId = FIRST_SCREEN_ID;
+ // Update the screen id counter for the provider.
+ LauncherSettings.Settings.call(resolver,
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
+
+ if (screenId > FIRST_SCREEN_ID) {
+ screenId = FIRST_SCREEN_ID;
+ }
+
+ // Insert the item
+ ContentWriter writer = new ContentWriter(targetContext);
+ item.id = LauncherSettings.Settings.call(
+ resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ item.screenId = screenId;
+ item.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites._ID, item.id);
+ resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
+ writer.getValues(targetContext));
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
index 5daac39..e1fde3b 100644
--- a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
+++ b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
@@ -19,6 +19,7 @@
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiObject2;
import com.android.launcher3.testing.TestProtocol;
@@ -33,11 +34,14 @@
AddToHomeScreenPrompt(LauncherInstrumentation launcher) {
mLauncher = launcher;
- mWidgetCell = launcher.waitForLauncherObject(By.clazz(
- "com.android.launcher3.widget.WidgetCell"));
+ mWidgetCell = launcher.waitForLauncherObject(getSelector());
mLauncher.assertNotNull("Can't find widget cell object", mWidgetCell);
}
+ private static BySelector getSelector() {
+ return By.clazz("com.android.launcher3.widget.WidgetCell");
+ }
+
public void addAutomatically() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
if (mLauncher.getNavigationModel()
@@ -53,6 +57,7 @@
mLauncher.waitForObjectInContainer(
mWidgetCell.getParent().getParent().getParent().getParent(),
By.text(ADD_AUTOMATICALLY)).click();
+ mLauncher.waitUntilLauncherObjectGone(getSelector());
}
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 808be66..b6c17df 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -18,6 +18,7 @@
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Bundle;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -244,8 +245,8 @@
}
private void verifyNotFrozen(String message) {
- mLauncher.assertEquals(message, 0, mLauncher.getTestInfo(
- TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS).
- getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD));
+ final Bundle testInfo = mLauncher.getTestInfo(TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS);
+ if (testInfo == null) return;
+ mLauncher.assertEquals(message, 0, testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD));
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index bdfd563..5de5b4a 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -31,7 +31,6 @@
*/
public final class AppIcon extends Launchable {
- private static final Pattern START_EVENT = Pattern.compile("start:");
private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick");
AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
@@ -64,6 +63,6 @@
@Override
protected void expectActivityStartEvents() {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, START_EVENT);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
index 37a7b91..a40919b 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -27,8 +27,6 @@
*/
public class AppIconMenuItem extends Launchable {
- private static final Pattern START_SHORTCUT_EVENT = Pattern.compile("start: shortcut:");
-
AppIconMenuItem(LauncherInstrumentation launcher, UiObject2 shortcut) {
super(launcher, shortcut);
}
@@ -51,6 +49,6 @@
@Override
protected void expectActivityStartEvents() {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, START_SHORTCUT_EVENT);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 80b8e89..ce94a3e 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -71,7 +71,6 @@
}
protected void goToOverviewUnchecked() {
- final boolean launcherWasVisible = mLauncher.isLauncherVisible();
switch (mLauncher.getNavigationModel()) {
case ZERO_BUTTON: {
final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
@@ -87,6 +86,11 @@
zeroButtonToOverviewGestureStartsInLauncher()
? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
: LauncherInstrumentation.GestureScope.OUTSIDE;
+
+ // b/156044202
+ mLauncher.log("Hierarchy before swiping up to overview:");
+ mLauncher.dumpViewHierarchy();
+
mLauncher.sendPointer(
downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
mLauncher.executeAndWaitForEvent(
@@ -138,11 +142,6 @@
break;
}
expectSwitchToOverviewEvents();
-
- if (!launcherWasVisible) {
- mLauncher.expectEvent(
- TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START_ACTIVITY);
- }
}
private void expectSwitchToOverviewEvents() {
@@ -192,11 +191,6 @@
}
final boolean isZeroButton = mLauncher.getNavigationModel()
== LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
- if (!launcherWasVisible) {
- mLauncher.expectEvent(
- TestProtocol.SEQUENCE_MAIN,
- LauncherInstrumentation.EVENT_START_ACTIVITY);
- }
mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState,
launcherWasVisible && isZeroButton
? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
@@ -208,11 +202,6 @@
// Double press the recents button.
UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
- if (!launcherWasVisible) {
- mLauncher.expectEvent(
- TestProtocol.SEQUENCE_MAIN,
- LauncherInstrumentation.EVENT_START_ACTIVITY);
- }
mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
mLauncher.getOverview();
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
@@ -220,8 +209,6 @@
break;
}
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
- mLauncher.expectEvent(
- TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
}
protected String getSwipeHeightRequestName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 69afcc4..223ae29 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -106,21 +106,33 @@
*/
@NonNull
public OverviewTask getCurrentTask() {
+ final List<UiObject2> taskViews = getTasks();
+ mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
+
+ // taskViews contains up to 3 task views: the 'main' (having the widest visible part) one
+ // in the center, and parts of its right and left siblings. Find the main task view by
+ // its width.
+ final UiObject2 widestTask = Collections.max(taskViews,
+ (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(),
+ mLauncher.getVisibleBounds(t2).width()));
+
+ return new OverviewTask(mLauncher, widestTask, this);
+ }
+
+ @NonNull
+ private List<UiObject2> getTasks() {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to get current task")) {
+ "want to get overview tasks")) {
verifyActiveContainer();
- final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
+ return mLauncher.getDevice().findObjects(
mLauncher.getOverviewObjectSelector("snapshot"));
- mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
-
- // taskViews contains up to 3 task views: the 'main' (having the widest visible
- // part) one in the center, and parts of its right and left siblings. Find the
- // main task view by its width.
- final UiObject2 widestTask = Collections.max(taskViews,
- (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(),
- mLauncher.getVisibleBounds(t2).width()));
-
- return new OverviewTask(mLauncher, widestTask, this);
}
}
+
+ /**
+ * Returns whether Overview has tasks.
+ */
+ public boolean hasTasks() {
+ return getTasks().size() > 0;
+ }
}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 2922acf..13ecfb8 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -25,8 +25,6 @@
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
-import com.android.launcher3.testing.TestProtocol;
-
/**
* Ancestor for AppIcon and AppMenuItem.
*/
@@ -64,8 +62,6 @@
event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
() -> "Launching an app didn't open a new window: " + mObject.getText());
expectActivityStartEvents();
- mLauncher.expectEvent(
- TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
mLauncher.assertTrue(
"App didn't start: " + selector,
@@ -76,9 +72,11 @@
/**
* Drags an object to the center of homescreen.
- * @param startsActivity whether it's expected to start an activity.
+ *
+ * @param startsActivity whether it's expected to start an activity.
+ * @param isWidgetShortcut whether we drag a widget shortcut
*/
- public void dragToWorkspace(boolean startsActivity) {
+ public void dragToWorkspace(boolean startsActivity, boolean isWidgetShortcut) {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
final Point launchableCenter = getObject().getVisibleCenter();
final Point displaySize = mLauncher.getRealDisplaySize();
@@ -93,6 +91,7 @@
displaySize.y / 2),
getLongPressIndicator(),
startsActivity,
+ isWidgetShortcut,
() -> addExpectedEventsForLongClick());
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 975fe9c..f4274a8 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -28,6 +28,7 @@
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.ComponentName;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -39,6 +40,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
@@ -97,8 +99,7 @@
private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
private static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
- static final Pattern EVENT_START_ACTIVITY = Pattern.compile("Activity\\.onStart");
- static final Pattern EVENT_STOP_ACTIVITY = Pattern.compile("Activity\\.onStop");
+ static final Pattern EVENT_START = Pattern.compile("start:");
static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
@@ -164,12 +165,9 @@
private Consumer<ContainerType> mOnSettledStateAction;
- private static LogEventChecker sEventChecker;
- // True if there is an gesture in progress that needs event verification.
- private static boolean sCheckingEvents;
+ private LogEventChecker mEventChecker;
private boolean mCheckEventsForSuccessfulGestures = false;
- private int mExpectedPid;
private Runnable mOnLauncherCrashed;
private static Pattern getTouchEventPattern(String prefix, String action) {
@@ -262,7 +260,12 @@
}
Bundle getTestInfo(String request) {
- return getContext().getContentResolver().call(mTestProviderUri, request, null, null);
+ try (ContentProviderClient client = getContext().getContentResolver()
+ .acquireContentProviderClient(mTestProviderUri)) {
+ return client.call(request, null, null);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
}
Insets getTargetInsets() {
@@ -334,26 +337,31 @@
private String getSystemAnomalyMessage() {
try {
- final StringBuilder sb = new StringBuilder();
+ {
+ final StringBuilder sb = new StringBuilder();
- UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
- if (object != null) {
- sb.append("TITLE: ").append(object.getText());
- }
+ UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
+ if (object != null) {
+ sb.append("TITLE: ").append(object.getText());
+ }
- object = mDevice.findObject(By.res("android", "message"));
- if (object != null) {
- sb.append(" PACKAGE: ").append(object.getApplicationPackage())
- .append(" MESSAGE: ").append(object.getText());
- }
+ object = mDevice.findObject(By.res("android", "message"));
+ if (object != null) {
+ sb.append(" PACKAGE: ").append(object.getApplicationPackage())
+ .append(" MESSAGE: ").append(object.getText());
+ }
- if (sb.length() != 0) {
- return "System alert popup is visible: " + sb;
+ if (sb.length() != 0) {
+ return "System alert popup is visible: " + sb;
+ }
}
if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty";
+
+ final String navigationModeError = getNavigationModeMismatchError();
+ if (navigationModeError != null) return navigationModeError;
} catch (Throwable e) {
Log.w(TAG, "getSystemAnomalyMessage failed", e);
}
@@ -361,33 +369,12 @@
return null;
}
- private String getAnomalyMessage() {
- if (mExpectedPid != 0 && mExpectedPid != getPid()) {
- mExpectedPid = 0;
- if (mOnLauncherCrashed != null) mOnLauncherCrashed.run();
- return "Launcher crashed";
- }
-
+ public void checkForAnomaly() {
final String systemAnomalyMessage = getSystemAnomalyMessage();
if (systemAnomalyMessage != null) {
- return "http://go/tapl : Tests are broken by a non-Launcher system error: "
- + systemAnomalyMessage;
- }
-
- return null;
- }
-
- public void checkForAnomaly() {
- final String anomalyMessage = getAnomalyMessage();
- if (anomalyMessage != null) {
- if (sCheckingEvents) {
- sCheckingEvents = false;
- sEventChecker.finishNoWait();
- }
- log("Hierarchy dump for: " + anomalyMessage);
- dumpViewHierarchy();
-
- Assert.fail(formatSystemHealthMessage(anomalyMessage));
+ Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
+ "http://go/tapl : Tests are broken by a non-Launcher system error: "
+ + systemAnomalyMessage, false)));
}
}
@@ -447,23 +434,51 @@
return message;
}
- private void fail(String message) {
- checkForAnomaly();
-
- message = "http://go/tapl : " + getContextDescription() + message
- + " (visible state: " + getVisibleStateMessage() + ")";
- log("Hierarchy dump for: " + message);
- dumpViewHierarchy();
-
- if (sCheckingEvents) {
- sCheckingEvents = false;
- final String eventMismatch = sEventChecker.verify(0);
- if (eventMismatch != null) {
- message = message + ", having produced " + eventMismatch;
+ private String formatErrorWithEvents(String message, boolean checkEvents) {
+ if (mEventChecker != null) {
+ final LogEventChecker eventChecker = mEventChecker;
+ mEventChecker = null;
+ if (checkEvents) {
+ final String eventMismatch = eventChecker.verify(0, false);
+ if (eventMismatch != null) {
+ message = message + ", having produced " + eventMismatch;
+ }
+ } else {
+ eventChecker.finishNoWait();
}
}
- Assert.fail(formatSystemHealthMessage(message));
+ dumpDiagnostics();
+
+ log("Hierarchy dump for: " + message);
+ dumpViewHierarchy();
+
+ return message;
+ }
+
+ private void dumpDiagnostics() {
+ Log.e("b/156287114", "Input:");
+ logShellCommand("dumpsys input");
+ Log.e("b/156287114", "TIS:");
+ logShellCommand("dumpsys activity service TouchInteractionService");
+ }
+
+ private void logShellCommand(String command) {
+ try {
+ for (String line : mDevice.executeShellCommand(command).split("\\n")) {
+ SystemClock.sleep(10);
+ Log.d("b/156287114", line);
+ }
+ } catch (IOException e) {
+ Log.d("b/156287114", "Failed to execute " + command);
+ }
+ }
+
+ private void fail(String message) {
+ checkForAnomaly();
+ Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
+ "http://go/tapl : " + getContextDescription() + message
+ + " (visible state: " + getVisibleStateMessage() + ")", true)));
}
private String getContextDescription() {
@@ -534,13 +549,14 @@
mExpectedRotation, mDevice.getDisplayRotation());
// b/148422894
+ String error = null;
for (int i = 0; i != 600; ++i) {
- if (getNavigationModeMismatchError() == null) break;
+ error = getNavigationModeMismatchError();
+ if (error == null) break;
sleep(100);
}
-
- final String error = getNavigationModeMismatchError();
assertTrue(error, error == null);
+
log("verifyContainerType: " + containerType);
final UiObject2 container = verifyVisibleObjects(containerType);
@@ -558,32 +574,32 @@
if (mDevice.isNaturalOrientation()) {
waitForLauncherObject(APPS_RES_ID);
} else {
- waitUntilGone(APPS_RES_ID);
+ waitUntilLauncherObjectGone(APPS_RES_ID);
}
- waitUntilGone(OVERVIEW_RES_ID);
- waitUntilGone(WIDGETS_RES_ID);
+ waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
+ waitUntilLauncherObjectGone(WIDGETS_RES_ID);
return waitForLauncherObject(WORKSPACE_RES_ID);
}
case WIDGETS: {
- waitUntilGone(WORKSPACE_RES_ID);
- waitUntilGone(APPS_RES_ID);
- waitUntilGone(OVERVIEW_RES_ID);
+ waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
+ waitUntilLauncherObjectGone(APPS_RES_ID);
+ waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
return waitForLauncherObject(WIDGETS_RES_ID);
}
case ALL_APPS: {
- waitUntilGone(WORKSPACE_RES_ID);
- waitUntilGone(OVERVIEW_RES_ID);
- waitUntilGone(WIDGETS_RES_ID);
+ waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
+ waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
+ waitUntilLauncherObjectGone(WIDGETS_RES_ID);
return waitForLauncherObject(APPS_RES_ID);
}
case OVERVIEW: {
if (hasAllAppsInOverview()) {
waitForLauncherObject(APPS_RES_ID);
} else {
- waitUntilGone(APPS_RES_ID);
+ waitUntilLauncherObjectGone(APPS_RES_ID);
}
- waitUntilGone(WORKSPACE_RES_ID);
- waitUntilGone(WIDGETS_RES_ID);
+ waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
+ waitUntilLauncherObjectGone(WIDGETS_RES_ID);
return waitForLauncherObject(OVERVIEW_RES_ID);
}
@@ -591,10 +607,10 @@
return waitForFallbackLauncherObject(OVERVIEW_RES_ID);
}
case BACKGROUND: {
- waitUntilGone(WORKSPACE_RES_ID);
- waitUntilGone(APPS_RES_ID);
- waitUntilGone(OVERVIEW_RES_ID);
- waitUntilGone(WIDGETS_RES_ID);
+ waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
+ waitUntilLauncherObjectGone(APPS_RES_ID);
+ waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
+ waitUntilLauncherObjectGone(WIDGETS_RES_ID);
return null;
}
default:
@@ -639,6 +655,7 @@
*/
public Workspace pressHome() {
try (LauncherInstrumentation.Closable e = eventsCheck()) {
+ waitForLauncherInitialized();
// Click home, then wait for any accessibility event, then wait until accessibility
// events stop.
// We need waiting for any accessibility event generated after pressing Home because
@@ -659,7 +676,7 @@
false, GestureScope.INSIDE_TO_OUTSIDE);
try (LauncherInstrumentation.Closable c = addContextLayer(
"Swiped up from context menu to home")) {
- waitUntilGone(CONTEXT_MENU_RES_ID);
+ waitUntilLauncherObjectGone(CONTEXT_MENU_RES_ID);
}
}
if (hasLauncherObject(WORKSPACE_RES_ID)) {
@@ -667,7 +684,7 @@
} else {
log("Hierarchy before swiping up to home:");
dumpViewHierarchy();
- log(action = "swiping up to home from " + getVisibleStateMessage());
+ action = "swiping up to home";
try (LauncherInstrumentation.Closable c = addContextLayer(action)) {
swipeToState(
@@ -678,20 +695,12 @@
? GestureScope.INSIDE_TO_OUTSIDE
: GestureScope.OUTSIDE);
}
- if (!launcherWasVisible) {
- expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_START_ACTIVITY);
- }
}
} else {
- if (!launcherWasVisible) {
- expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_START_ACTIVITY);
- }
log("Hierarchy before clicking home:");
dumpViewHierarchy();
- log(action = "clicking home button from " + getVisibleStateMessage());
+ action = "clicking home button";
try (LauncherInstrumentation.Closable c = addContextLayer(action)) {
- mDevice.waitForIdle();
-
if (!isLauncher3() && getNavigationModel() == NavigationModel.TWO_BUTTON) {
expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
@@ -703,7 +712,6 @@
!hasLauncherObject(WORKSPACE_RES_ID)
&& (hasLauncherObject(APPS_RES_ID)
|| hasLauncherObject(OVERVIEW_RES_ID)));
- mDevice.waitForIdle();
}
}
try (LauncherInstrumentation.Closable c = addContextLayer(
@@ -819,9 +827,17 @@
}
}
- void waitUntilGone(String resId) {
- assertTrue("Unexpected launcher object visible: " + resId,
- mDevice.wait(Until.gone(getLauncherObjectSelector(resId)),
+ void waitUntilLauncherObjectGone(String resId) {
+ waitUntilGoneBySelector(getLauncherObjectSelector(resId));
+ }
+
+ void waitUntilLauncherObjectGone(BySelector selector) {
+ waitUntilGoneBySelector(makeLauncherSelector(selector));
+ }
+
+ private void waitUntilGoneBySelector(BySelector launcherSelector) {
+ assertTrue("Unexpected launcher object visible: " + launcherSelector,
+ mDevice.wait(Until.gone(launcherSelector),
WAIT_TIME_MS));
}
@@ -839,27 +855,42 @@
@NonNull
List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
- return container.findObjects(getLauncherObjectSelector(resName));
+ try {
+ return container.findObjects(getLauncherObjectSelector(resName));
+ } catch (StaleObjectException e) {
+ fail("The container disappeared from screen");
+ return null;
+ }
}
@NonNull
UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
- final UiObject2 object = container.wait(
- Until.findObject(getLauncherObjectSelector(resName)),
- WAIT_TIME_MS);
- assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: "
- + container.getResourceName(), object);
- return object;
+ try {
+ final UiObject2 object = container.wait(
+ Until.findObject(getLauncherObjectSelector(resName)),
+ WAIT_TIME_MS);
+ assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: "
+ + container.getResourceName(), object);
+ return object;
+ } catch (StaleObjectException e) {
+ fail("The container disappeared from screen");
+ return null;
+ }
}
@NonNull
UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) {
- final UiObject2 object = container.wait(
- Until.findObject(selector),
- WAIT_TIME_MS);
- assertNotNull("Can't find a view in Launcher, id: " + selector + " in container: "
- + container.getResourceName(), object);
- return object;
+ try {
+ final UiObject2 object = container.wait(
+ Until.findObject(selector),
+ WAIT_TIME_MS);
+ assertNotNull("Can't find a view in Launcher, id: " + selector + " in container: "
+ + container.getResourceName(), object);
+ return object;
+ } catch (StaleObjectException e) {
+ fail("The container disappeared from screen");
+ return null;
+ }
}
private boolean hasLauncherObject(String resId) {
@@ -948,9 +979,9 @@
executeAndWaitForEvent(
command,
event -> isSwitchToStateEvent(event, expectedState, actualEvents),
- () -> "Failed to receive an event for the state change: expected "
+ () -> "Failed to receive an event for the state change: expected ["
+ TestProtocol.stateOrdinalToString(expectedState)
- + ", actual: " + eventListToString(actualEvents));
+ + "], actual: " + eventListToString(actualEvents));
}
private boolean isSwitchToStateEvent(
@@ -1150,7 +1181,8 @@
}
final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
- mInstrumentation.getUiAutomation().injectInputEvent(event, true);
+ assertTrue("injectInputEvent failed",
+ mInstrumentation.getUiAutomation().injectInputEvent(event, true));
event.recycle();
}
@@ -1263,7 +1295,7 @@
TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
- public void disableSensorRotation() {
+ private void disableSensorRotation() {
getTestInfo(TestProtocol.REQUEST_MOCK_SENSOR_ROTATION);
}
@@ -1276,8 +1308,9 @@
getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
- public int getPid() {
- return getTestInfo(TestProtocol.REQUEST_PID).getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ public Integer getPid() {
+ final Bundle testInfo = getTestInfo(TestProtocol.REQUEST_PID);
+ return testInfo != null ? testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) : null;
}
public void produceJavaLeak() {
@@ -1302,26 +1335,38 @@
return tasks;
}
+ public void clearLauncherData() {
+ getTestInfo(TestProtocol.REQUEST_CLEAR_DATA);
+ }
+
public Closable eventsCheck() {
- Assert.assertTrue("Nested event checking", !sCheckingEvents);
- sCheckingEvents = true;
- mExpectedPid = getPid();
- if (sEventChecker == null) sEventChecker = new LogEventChecker();
- sEventChecker.start();
+ Assert.assertTrue("Nested event checking", mEventChecker == null);
+ disableSensorRotation();
+ final Integer initialPid = getPid();
+ final LogEventChecker eventChecker = new LogEventChecker(this);
+ if (eventChecker.start()) mEventChecker = eventChecker;
return () -> {
- checkForAnomaly();
+ if (initialPid != null && initialPid.intValue() != getPid()) {
+ if (mOnLauncherCrashed != null) mOnLauncherCrashed.run();
+ checkForAnomaly();
+ Assert.fail(
+ formatSystemHealthMessage(
+ formatErrorWithEvents("Launcher crashed", false)));
+ }
- if (sCheckingEvents) {
- sCheckingEvents = false;
+ if (mEventChecker != null) {
+ mEventChecker = null;
if (mCheckEventsForSuccessfulGestures) {
- final String message = sEventChecker.verify(WAIT_TIME_MS);
+ final String message = eventChecker.verify(WAIT_TIME_MS, true);
if (message != null) {
+ dumpDiagnostics();
+ checkForAnomaly();
Assert.fail(formatSystemHealthMessage(
"http://go/tapl : successful gesture produced " + message));
}
} else {
- sEventChecker.finishNoWait();
+ eventChecker.finishNoWait();
}
}
};
@@ -1332,7 +1377,11 @@
}
void expectEvent(String sequence, Pattern expected) {
- if (sCheckingEvents) sEventChecker.expectPattern(sequence, expected);
+ if (mEventChecker != null) {
+ mEventChecker.expectPattern(sequence, expected);
+ } else {
+ Log.d(TAG, "Expecting: " + sequence + " / " + expected);
+ }
}
Rect getVisibleBounds(UiObject2 object) {
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 49901ea..4440b82 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -15,147 +15,84 @@
*/
package com.android.launcher3.tapl;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import android.util.Log;
+import android.os.SystemClock;
import com.android.launcher3.testing.TestProtocol;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Semaphore;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- * Utility class to read log on a background thread.
+ * Utility class to verify expected events.
*/
public class LogEventChecker {
- private static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
- ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<sequence>[a-zA-Z]+) / (?<event>.*)");
-
- private static final String START_PREFIX = "START_READER ";
- private static final String FINISH_PREFIX = "FINISH_READER ";
-
- private volatile CountDownLatch mFinished;
+ private final LauncherInstrumentation mLauncher;
// Map from an event sequence name to an ordered list of expected events in that sequence.
private final ListMap<Pattern> mExpectedEvents = new ListMap<>();
- private final ListMap<String> mEvents = new ListMap<>();
- private final Semaphore mEventsCounter = new Semaphore(0);
-
- private volatile String mStartCommand;
- private volatile String mFinishCommand;
-
- LogEventChecker() {
- final Thread thread = new Thread(this::onRun, "log-reader-thread");
- thread.setPriority(Thread.NORM_PRIORITY);
- thread.start();
+ LogEventChecker(LauncherInstrumentation launcher) {
+ mLauncher = launcher;
}
- void start() {
- if (mFinished != null) {
- try {
- mFinished.await();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- mFinished = null;
- }
- mEvents.clear();
+ boolean start() {
mExpectedEvents.clear();
- mEventsCounter.drainPermits();
- final String id = UUID.randomUUID().toString();
- mStartCommand = START_PREFIX + id;
- mFinishCommand = FINISH_PREFIX + id;
- Log.d(TestProtocol.TAPL_EVENTS_TAG, mStartCommand);
- }
-
- private void onRun() {
- try {
- // Note that we use Runtime.exec to start the log reading process instead of running
- // it via UIAutomation, so that we can directly access the "Process" object and
- // ensure that the instrumentation is not stuck forever.
- final String cmd = "logcat -s " + TestProtocol.TAPL_EVENTS_TAG;
-
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(
- Runtime.getRuntime().exec(cmd).getInputStream()))) {
- for (;;) {
- // Skip everything before the next start command.
- for (;;) {
- final String event = reader.readLine();
- if (event.contains(TestProtocol.TAPL_EVENTS_TAG)
- && event.contains(mStartCommand)) {
- break;
- }
- }
-
- // Store all actual events until the finish command.
- for (;;) {
- final String event = reader.readLine();
- if (event.contains(TestProtocol.TAPL_EVENTS_TAG)) {
- if (event.contains(mFinishCommand)) {
- mFinished.countDown();
- break;
- } else {
- final Matcher matcher = EVENT_LOG_ENTRY.matcher(event);
- if (matcher.find()) {
- mEvents.add(matcher.group("sequence"), matcher.group("event"));
- mEventsCounter.release();
- }
- }
- }
- }
- }
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ return mLauncher.getTestInfo(TestProtocol.REQUEST_START_EVENT_LOGGING) != null;
}
void expectPattern(String sequence, Pattern pattern) {
mExpectedEvents.add(sequence, pattern);
}
- private void finishSync(long waitForExpectedCountMs) {
- try {
- // Wait until Launcher generates the expected number of events.
- int expectedCount = mExpectedEvents.entrySet()
+ // Waits for the expected number of events and returns them.
+ private ListMap<String> finishSync(long waitForExpectedCountMs) {
+ final long startTime = SystemClock.uptimeMillis();
+ // Event strings with '/' separating the sequence and the event.
+ ArrayList<String> rawEvents;
+
+ while (true) {
+ rawEvents = mLauncher.getTestInfo(TestProtocol.REQUEST_GET_TEST_EVENTS)
+ .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ final int expectedCount = mExpectedEvents.entrySet()
.stream().mapToInt(e -> e.getValue().size()).sum();
- mEventsCounter.tryAcquire(expectedCount, waitForExpectedCountMs, MILLISECONDS);
- finishNoWait();
- mFinished.await();
- mFinished = null;
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
+ if (rawEvents.size() >= expectedCount
+ || SystemClock.uptimeMillis() > startTime + waitForExpectedCountMs) {
+ break;
+ }
+ SystemClock.sleep(100);
}
+
+ finishNoWait();
+
+ // Parse raw events into a map.
+ final ListMap<String> eventSequences = new ListMap<>();
+ for (String rawEvent : rawEvents) {
+ final String[] split = rawEvent.split("/");
+ eventSequences.add(split[0], split[1]);
+ }
+ return eventSequences;
}
void finishNoWait() {
- mFinished = new CountDownLatch(1);
- Log.d(TestProtocol.TAPL_EVENTS_TAG, mFinishCommand);
+ mLauncher.getTestInfo(TestProtocol.REQUEST_STOP_EVENT_LOGGING);
}
- String verify(long waitForExpectedCountMs) {
- finishSync(waitForExpectedCountMs);
+ String verify(long waitForExpectedCountMs, boolean successfulGesture) {
+ final ListMap<String> actualEvents = finishSync(waitForExpectedCountMs);
final StringBuilder sb = new StringBuilder();
boolean hasMismatches = false;
for (Map.Entry<String, List<Pattern>> expectedEvents : mExpectedEvents.entrySet()) {
String sequence = expectedEvents.getKey();
- List<String> actual = new ArrayList<>(mEvents.getNonNull(sequence));
+ List<String> actual = new ArrayList<>(actualEvents.getNonNull(sequence));
final int mismatchPosition = getMismatchPosition(expectedEvents.getValue(), actual);
- hasMismatches = hasMismatches || mismatchPosition != -1;
+ hasMismatches = hasMismatches
+ || mismatchPosition != -1 && !ignoreMistatch(successfulGesture, sequence);
formatSequenceWithMismatch(
sb,
sequence,
@@ -164,14 +101,15 @@
mismatchPosition);
}
// Check for unexpected event sequences in the actual data.
- for (String actualNamedSequence : mEvents.keySet()) {
+ for (String actualNamedSequence : actualEvents.keySet()) {
if (!mExpectedEvents.containsKey(actualNamedSequence)) {
- hasMismatches = true;
+ hasMismatches = hasMismatches
+ || !ignoreMistatch(successfulGesture, actualNamedSequence);
formatSequenceWithMismatch(
sb,
actualNamedSequence,
new ArrayList<>(),
- mEvents.get(actualNamedSequence),
+ actualEvents.get(actualNamedSequence),
0);
}
}
@@ -179,6 +117,13 @@
return hasMismatches ? "mismatching events: " + sb.toString() : null;
}
+ // Workaround for b/154157191
+ private static boolean ignoreMistatch(boolean successfulGesture, String sequence) {
+ // b/156287114
+ return false;
+// return TestProtocol.SEQUENCE_TIS.equals(sequence) && successfulGesture;
+ }
+
// If the list of actual events matches the list of expected events, returns -1, otherwise
// the position of the mismatch.
private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
index 63a97f4..42b6bc9 100644
--- a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.tapl;
-import android.os.Build;
-
import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
@@ -43,12 +41,7 @@
LauncherInstrumentation.log("OptionsPopupMenuItem before click "
+ mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
mLauncher.clickLauncherObject(mObject);
- if (!Build.MODEL.contains("Cuttlefish") ||
- Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q &&
- !"R".equals(Build.VERSION.CODENAME)) {
- mLauncher.expectEvent(
- TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
- }
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START);
mLauncher.assertTrue(
"App didn't start: " + By.pkg(expectedPackageName),
mLauncher.getDevice().wait(Until.hasObject(By.pkg(expectedPackageName)),
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index fae5f19..b235919 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -79,8 +79,6 @@
() -> "Launching task didn't open a new window: "
+ mTask.getParent().getContentDescription());
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
- mLauncher.expectEvent(
- TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
}
return new Background(mLauncher);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 5be57c6..39ac645 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -16,6 +16,8 @@
package com.android.launcher3.tapl;
+import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
+
import android.graphics.Point;
import android.graphics.Rect;
@@ -23,6 +25,7 @@
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
import com.android.launcher3.tapl.LauncherInstrumentation.GestureScope;
@@ -88,9 +91,12 @@
}
public Widget getWidget(String labelText) {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "getting widget " + labelText + " in widgets list")) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "getting widget " + labelText + " in widgets list")) {
final UiObject2 widgetsContainer = verifyActiveContainer();
+ mLauncher.assertTrue("Widgets container didn't become scrollable",
+ widgetsContainer.wait(Until.scrollable(true), WAIT_TIME_MS));
final Point displaySize = mLauncher.getRealDisplaySize();
final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
@@ -114,17 +120,17 @@
maxWidth = Math.max(mLauncher.getVisibleBounds(sibling).width(), maxWidth);
}
- int visibleDelta = maxWidth - mLauncher.getVisibleBounds(widget).width();
- if (visibleDelta > 0) {
- Rect parentBounds = mLauncher.getVisibleBounds(cell);
- mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
- + mLauncher.getTouchSlop(),
- parentBounds.centerY(), parentBounds.centerX(),
- parentBounds.centerY(), 10, true, GestureScope.INSIDE);
- }
-
if (mLauncher.getVisibleBounds(widget).bottom
<= displaySize.y - mLauncher.getBottomGestureSize()) {
+ int visibleDelta = maxWidth - mLauncher.getVisibleBounds(widget).width();
+ if (visibleDelta > 0) {
+ Rect parentBounds = mLauncher.getVisibleBounds(cell);
+ mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
+ + mLauncher.getTouchSlop(),
+ parentBounds.centerY(), parentBounds.centerX(),
+ parentBounds.centerY(), 10, true, GestureScope.INSIDE);
+ }
+
return new Widget(mLauncher, widget);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index b3b5e32..f0e686f 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -180,6 +180,7 @@
mLauncher.getVisibleBounds(workspace).centerY()),
"deep_shortcuts_container",
false,
+ false,
() -> mLauncher.expectEvent(
TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT));
verifyActiveContainer();
@@ -202,7 +203,8 @@
static void dragIconToWorkspace(
LauncherInstrumentation launcher, Launchable launchable, Point dest,
- String longPressIndicator, boolean startsActivity, Runnable expectLongClickEvents) {
+ String longPressIndicator, boolean startsActivity, boolean isWidgetShortcut,
+ Runnable expectLongClickEvents) {
LauncherInstrumentation.log("dragIconToWorkspace: begin");
final Point launchableCenter = launchable.getObject().getVisibleCenter();
final long downTime = SystemClock.uptimeMillis();
@@ -224,12 +226,11 @@
downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest,
LauncherInstrumentation.GestureScope.INSIDE),
NORMAL_STATE_ORDINAL);
- if (startsActivity) {
- launcher.expectEvent(
- TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
+ if (startsActivity || isWidgetShortcut) {
+ launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START);
}
LauncherInstrumentation.log("dragIconToWorkspace: end");
- launcher.waitUntilGone("drop_target_bar");
+ launcher.waitUntilLauncherObjectGone("drop_target_bar");
}
/**