Merge master@5406228 into git_qt-dev-plus-aosp.
Change-Id: I2450207dda942ef17ddeb6a40fef610e80796611
BUG: 129345239
diff --git a/Android.bp b/Android.bp
index c583244..1121a79 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-java_library_static {
+android_library {
name: "launcher-aosp-tapl",
static_libs: [
"androidx.annotation_annotation",
@@ -27,5 +27,6 @@
"src/com/android/launcher3/util/SecureSettingsObserver.java",
"src/com/android/launcher3/TestProtocol.java",
],
+ manifest: "tests/tapl/AndroidManifest.xml",
platform_apis: true,
}
diff --git a/Android.mk b/Android.mk
index 9d92a9d..06ee66f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -16,18 +16,6 @@
LOCAL_PATH := $(call my-dir)
-#
-# Prebuilt Java Libraries
-#
-include $(CLEAR_VARS)
-LOCAL_MODULE := libSharedSystemUI
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_SRC_FILES := quickstep/libs/sysui_shared.jar
-LOCAL_UNINSTALLABLE_MODULE := true
-LOCAL_SDK_VERSION := current
-include $(BUILD_PREBUILT)
-
include $(CLEAR_VARS)
LOCAL_MODULE := libPluginCore
LOCAL_MODULE_TAGS := optional
@@ -37,14 +25,6 @@
LOCAL_SDK_VERSION := current
include $(BUILD_PREBUILT)
-include $(CLEAR_VARS)
-LOCAL_MODULE := libLauncherProtos
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_SRC_FILES := libs/launcher_protos.jar
-LOCAL_UNINSTALLABLE_MODULE := true
-LOCAL_SDK_VERSION := current
-include $(BUILD_PREBUILT)
#
# Build rule for plugin lib (needed to write a plugin).
#
@@ -53,7 +33,7 @@
LOCAL_AAPT2_ONLY := true
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore
+LOCAL_STATIC_JAVA_LIBRARIES:= libPluginCore
LOCAL_SRC_FILES := \
$(call all-java-files-under, src_plugins)
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java
index 12fa46b..0a2f18f 100644
--- a/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java
+++ b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java
@@ -26,12 +26,11 @@
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
-import com.google.android.material.circularreveal.cardview.CircularRevealCardView;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewAnimationUtils;
+import android.view.inputmethod.InputMethodManager;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.PopupMenu;
@@ -41,6 +40,9 @@
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
+import com.google.android.material.circularreveal.cardview.CircularRevealCardView;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
import java.util.HashSet;
import java.util.Set;
@@ -141,6 +143,16 @@
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
+
+ if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+ // Hide keyboard.
+ final View v = getWindow().peekDecorView();
+ if (v != null && v.getWindowToken() != null) {
+ getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
+ v.getWindowToken(), 0);
+ }
+ }
+
// A new intent will bring the launcher to top. Hide the app drawer to reset the state.
showAppDrawer(false);
}
diff --git a/build.gradle b/build.gradle
index 15a3513..e296455 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,6 +9,9 @@
}
}
+final String ANDROID_TOP = "${rootDir}/../../.."
+final String FRAMEWORK_PREBUILTS_DIR = "${ANDROID_TOP}/prebuilts/framework_intermediates/"
+
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
@@ -148,16 +151,16 @@
implementation "androidx.recyclerview:recyclerview:${ANDROID_X_VERSION}"
implementation "androidx.preference:preference:${ANDROID_X_VERSION}"
implementation project(':IconLoader')
- implementation fileTree(dir: "libs", include: 'launcher_protos.jar')
+ implementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'launcher_protos.jar')
// Recents lib dependency
- withQuickstepImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
+ withQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
// Recents lib dependency for Go
- withQuickstepIconRecentsImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
+ withQuickstepIconRecentsImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
// Required for AOSP to compile. This is already included in the sysui_shared.jar
- withoutQuickstepImplementation fileTree(dir: "libs", include: 'plugin_core.jar')
+ withoutQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'plugin_core.jar')
testImplementation 'junit:junit:4.12'
androidTestImplementation "org.mockito:mockito-core:1.9.5"
diff --git a/go/quickstep/res/drawable/clear_all_button.xml b/go/quickstep/res/drawable/clear_all_button.xml
new file mode 100644
index 0000000..acac32d
--- /dev/null
+++ b/go/quickstep/res/drawable/clear_all_button.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/clear_all_button_bg"/>
+ <corners android:radius="4dp"/>
+</shape>
diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml
index 6c50950..fddb1d3 100644
--- a/go/quickstep/res/layout/icon_recents_root_view.xml
+++ b/go/quickstep/res/layout/icon_recents_root_view.xml
@@ -19,10 +19,38 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
- <androidx.recyclerview.widget.RecyclerView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/recent_task_recycler_view"
+ <LinearLayout
+ android:id="@+id/recent_task_content_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:scrollbars="none"/>
+ android:orientation="vertical"
+ android:visibility="gone">
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/recent_task_recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:scrollbars="none"/>
+ <Button
+ android:id="@+id/clear_all_button"
+ android:layout_width="@dimen/clear_all_button_width"
+ android:layout_height="@dimen/clear_all_button_height"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginVertical="@dimen/task_item_half_vert_margin"
+ android:background="@drawable/clear_all_button"
+ android:gravity="center"
+ android:text="@string/recents_clear_all"
+ android:textAllCaps="false"
+ android:textColor="@color/clear_all_button_text"
+ android:textSize="14sp"/>
+ </LinearLayout>
+ <TextView
+ android:id="@+id/recent_task_empty_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/recents_empty_message"
+ android:textColor="@android:color/white"
+ android:textSize="25sp"
+ android:visibility="gone"/>
</com.android.quickstep.views.IconRecentsView>
\ No newline at end of file
diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml
new file mode 100644
index 0000000..048e9c5
--- /dev/null
+++ b/go/quickstep/res/layout/task_item_view.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<com.android.quickstep.views.TaskItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <FrameLayout
+ android:id="@+id/task_icon_and_thumbnail"
+ android:layout_width="@dimen/task_thumbnail_and_icon_view_size"
+ android:layout_height="@dimen/task_thumbnail_and_icon_view_size"
+ android:layout_gravity="center_vertical"
+ android:layout_marginHorizontal="8dp"
+ android:layout_marginVertical="@dimen/task_item_half_vert_margin">
+ <ImageView
+ android:id="@+id/task_thumbnail"
+ android:layout_width="@dimen/task_thumbnail_width"
+ android:layout_height="@dimen/task_thumbnail_height"
+ android:layout_gravity="top|start"/>
+ <ImageView
+ android:id="@+id/task_icon"
+ android:layout_width="@dimen/task_icon_size"
+ android:layout_height="@dimen/task_icon_size"
+ android:layout_gravity="bottom|end"/>
+ </FrameLayout>
+ <TextView
+ android:id="@+id/task_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginHorizontal="8dp"
+ android:singleLine="true"
+ android:textColor="@android:color/white"
+ android:textSize="24sp"/>
+</com.android.quickstep.views.TaskItemView>
diff --git a/go/quickstep/res/values/colors.xml b/go/quickstep/res/values/colors.xml
new file mode 100644
index 0000000..ff9dc9c
--- /dev/null
+++ b/go/quickstep/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<resources>
+ <color name="clear_all_button_bg">#FFDADCE0</color>
+ <color name="clear_all_button_text">#FF5F6368</color>
+</resources>
diff --git a/go/quickstep/res/values/dimens.xml b/go/quickstep/res/values/dimens.xml
new file mode 100644
index 0000000..e2fa387
--- /dev/null
+++ b/go/quickstep/res/values/dimens.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<resources>
+ <dimen name="task_item_half_vert_margin">8dp</dimen>
+ <dimen name="task_thumbnail_and_icon_view_size">60dp</dimen>
+ <dimen name="task_thumbnail_height">60dp</dimen>
+ <dimen name="task_thumbnail_width">36dp</dimen>
+ <dimen name="task_icon_size">36dp</dimen>
+
+ <dimen name="clear_all_button_width">106dp</dimen>
+ <dimen name="clear_all_button_height">36dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
index 96b4ae5..d189c50 100644
--- a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
+++ b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
@@ -31,7 +31,8 @@
@Override
protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
- //TODO: Implement this based off IconRecentsView
+ // Stubbed. Recents launch animation will come from the recents view itself and will not
+ // use remote animations.
}
@Override
diff --git a/go/quickstep/src/com/android/launcher3/LauncherRecentsToActivityHelper.java b/go/quickstep/src/com/android/launcher3/LauncherRecentsToActivityHelper.java
new file mode 100644
index 0000000..c12da94
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/LauncherRecentsToActivityHelper.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import com.android.quickstep.RecentsToActivityHelper;
+
+/**
+ * {@link RecentsToActivityHelper} for when the recents implementation is contained in
+ * {@link Launcher}.
+ */
+public final class LauncherRecentsToActivityHelper implements RecentsToActivityHelper {
+
+ private final Launcher mLauncher;
+
+ public LauncherRecentsToActivityHelper(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ @Override
+ public void leaveRecents() {
+ mLauncher.getStateManager().goToState(NORMAL);
+ }
+}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 47e0e61..cec12a8 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -43,7 +43,7 @@
}
@Override
- public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+ public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
return new float[] {1f, 0f};
}
@@ -84,9 +84,11 @@
super.onBackPressed(launcher);
}
-
public static float getDefaultSwipeHeight(Launcher launcher) {
- DeviceProfile dp = launcher.getDeviceProfile();
+ return getDefaultSwipeHeight(launcher.getDeviceProfile());
+ }
+
+ public static float getDefaultSwipeHeight(DeviceProfile dp) {
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
}
}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index d7cba39..29e650c 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -28,6 +28,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.views.IconRecentsView;
import java.util.ArrayList;
@@ -35,7 +36,7 @@
* Provides recents-related {@link UiFactory} logic and classes.
*/
public abstract class RecentsUiFactory {
-
+
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
// Scale recents takes before animating in
private static final float RECENTS_PREPARE_SCALE = 1.33f;
@@ -87,7 +88,10 @@
*
* @param launcher the launcher activity
*/
- public static void resetOverview(Launcher launcher) {}
+ public static void resetOverview(Launcher launcher) {
+ IconRecentsView recentsView = launcher.getOverviewPanel();
+ recentsView.setTransitionedFromApp(false);
+ }
/**
* Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index f1cb75b..0b12ab0 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,15 +16,15 @@
package com.android.launcher3.uioverrides;
import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
-import static com.android.quickstep.views.IconRecentsView.TRANSLATION_Y_FACTOR;
import android.util.FloatProperty;
-import androidx.annotation.NonNull;
-
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherRecentsToActivityHelper;
import com.android.quickstep.views.IconRecentsView;
+import androidx.annotation.NonNull;
+
/**
* State handler for Go's {@link IconRecentsView}.
*/
@@ -33,11 +33,8 @@
public RecentsViewStateController(@NonNull Launcher launcher) {
super(launcher);
- }
-
- @Override
- FloatProperty<IconRecentsView> getTranslationYFactorProperty() {
- return TRANSLATION_Y_FACTOR;
+ launcher.<IconRecentsView>getOverviewPanel().setRecentsToActivityHelper(
+ new LauncherRecentsToActivityHelper(launcher));
}
@Override
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index f199643..051c80f 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -15,15 +15,30 @@
*/
package com.android.quickstep;
+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.FAST_OUT_SLOW_IN;
+import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.graphics.Matrix;
+import android.graphics.Rect;
import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.views.IconRecentsView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
/**
* Provider for the atomic remote window animation from the app to the overview.
@@ -33,13 +48,17 @@
final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements
RemoteAnimationProvider {
- private static final long RECENTS_LAUNCH_DURATION = 250;
+ private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50;
+ private static final long APP_SCALE_DOWN_DURATION = 400;
private static final String TAG = "AppToOverviewAnimationProvider";
private final ActivityControlHelper<T> mHelper;
+ private final int mTargetTaskId;
+ private IconRecentsView mRecentsView;
AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
mHelper = helper;
+ mTargetTaskId = targetTaskId;
}
/**
@@ -54,35 +73,161 @@
false /* animate activity */, (controller) -> {
controller.dispatchOnStart();
ValueAnimator anim = controller.getAnimationPlayer()
- .setDuration(RECENTS_LAUNCH_DURATION);
+ .setDuration(getRecentsLaunchDuration());
anim.setInterpolator(FAST_OUT_SLOW_IN);
anim.start();
});
factory.onRemoteAnimationReceived(null);
- factory.createActivityController(RECENTS_LAUNCH_DURATION);
+ factory.createActivityController(getRecentsLaunchDuration());
+ mRecentsView = activity.getOverviewPanel();
return false;
}
/**
- * Create remote window animation from the currently running app to the overview panel.
+ * Create remote window animation from the currently running app to the overview panel. Should
+ * be called after {@link #onActivityReady}.
*
* @param targetCompats the target apps
* @return animation from app to overview
*/
@Override
public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
- //TODO: Implement the overview to app window animation for Go.
AnimatorSet anim = new AnimatorSet();
- anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION));
+ if (mRecentsView == null) {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No recents view. Using stub animation.");
+ }
+ anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
+ return anim;
+ }
+
+ RemoteAnimationTargetSet targetSet =
+ new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
+ mRecentsView.setTransitionedFromApp(!targetSet.isAnimatingHome());
+
+ RemoteAnimationTargetCompat recentsTarget = null;
+ RemoteAnimationTargetCompat closingAppTarget = null;
+
+ for (RemoteAnimationTargetCompat target : targetCompats) {
+ if (target.mode == MODE_OPENING) {
+ recentsTarget = target;
+ } else if (target.mode == MODE_CLOSING && target.taskId == mTargetTaskId) {
+ closingAppTarget = target;
+ }
+ }
+
+ if (closingAppTarget == null) {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No closing app target. Using stub animation.");
+ }
+ anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
+ return anim;
+ }
+ if (recentsTarget == null) {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No recents target. Using stub animation.");
+ }
+ anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
+ return anim;
+ }
+
+ View thumbnailView = mRecentsView.getThumbnailViewForTask(mTargetTaskId);
+ if (thumbnailView == null) {
+ // TODO: We should either 1) guarantee the view is loaded before attempting this
+ // or 2) have a backup animation.
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No thumbnail view for running task. Using stub animation.");
+ }
+ anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
+ return anim;
+ }
+
+ playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView);
+
return anim;
}
/**
+ * Animate a closing app to scale down to the location of the thumbnail view in recents.
+ *
+ * @param anim animator set
+ * @param appTarget the app surface thats closing
+ * @param recentsTarget the surface containing recents
+ * @param thumbnailView the thumbnail view to animate to
+ */
+ private void playAppScaleDownAnim(@NonNull AnimatorSet anim,
+ @NonNull RemoteAnimationTargetCompat appTarget,
+ @NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) {
+
+ // Identify where the entering remote app should animate to.
+ Rect endRect = new Rect();
+ thumbnailView.getGlobalVisibleRect(endRect);
+
+ Rect appBounds = appTarget.sourceContainerBounds;
+
+ ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1);
+ valueAnimator.setDuration(APP_SCALE_DOWN_DURATION);
+
+ SyncRtSurfaceTransactionApplierCompat surfaceApplier =
+ new SyncRtSurfaceTransactionApplierCompat(thumbnailView);
+
+ // Keep recents visible throughout the animation.
+ SurfaceParams[] params = new SurfaceParams[2];
+ params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
+ null /* windowCrop */, getLayer(recentsTarget, MODE_OPENING), 0 /* cornerRadius */);
+
+ valueAnimator.addUpdateListener(new MultiValueUpdateListener() {
+ private final FloatProp mScaleX;
+ private final FloatProp mScaleY;
+ private final FloatProp mTranslationX;
+ private final FloatProp mTranslationY;
+ private final FloatProp mAlpha;
+
+ {
+ // Scale down and move to view location.
+ float endScaleX = ((float) endRect.width()) / appBounds.width();
+ mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION,
+ ACCEL_DEACCEL);
+ float endScaleY = ((float) endRect.height()) / appBounds.height();
+ mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION,
+ ACCEL_DEACCEL);
+ float endTranslationX = endRect.left -
+ (appBounds.width() - thumbnailView.getWidth()) / 2.0f;
+ mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION,
+ ACCEL_DEACCEL);
+ float endTranslationY = endRect.top -
+ (appBounds.height() - thumbnailView.getHeight()) / 2.0f;
+ mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION,
+ ACCEL_DEACCEL);
+
+ // Fade out quietly near the end to be replaced by the real view.
+ mAlpha = new FloatProp(1.0f, 0,
+ APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION,
+ APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2);
+ }
+
+ @Override
+ public void onUpdate(float percent) {
+ Matrix m = new Matrix();
+ m.setScale(mScaleX.value, mScaleY.value,
+ appBounds.width() / 2.0f, appBounds.height() / 2.0f);
+ m.postTranslate(mTranslationX.value, mTranslationY.value);
+
+ params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m,
+ null /* windowCrop */, getLayer(appTarget, MODE_CLOSING),
+ 0 /* cornerRadius */);
+ surfaceApplier.scheduleApply(params);
+ }
+ });
+ anim.play(valueAnimator);
+ }
+
+ /**
* Get duration of animation from app to overview.
*
* @return duration of animation
*/
long getRecentsLaunchDuration() {
- return RECENTS_LAUNCH_DURATION;
+ return APP_SCALE_DOWN_DURATION;
}
}
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
index abb9242..bba08a4 100644
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -26,7 +26,6 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.IconRecentsView;
@@ -110,11 +109,6 @@
}
@Override
- public AlphaProperty getAlphaProperty(RecentsActivity activity) {
- return activity.getDragLayer().getAlphaProperty(0);
- }
-
- @Override
public int getContainerType() {
return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
}
diff --git a/go/quickstep/src/com/android/quickstep/FallbackRecentsToActivityHelper.java b/go/quickstep/src/com/android/quickstep/FallbackRecentsToActivityHelper.java
new file mode 100644
index 0000000..a845f93
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/FallbackRecentsToActivityHelper.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * {@link RecentsToActivityHelper} for when we are using the fallback recents in
+ * {@link BaseRecentsActivity}.
+ */
+public final class FallbackRecentsToActivityHelper implements RecentsToActivityHelper {
+
+ BaseRecentsActivity mActivity;
+
+ public FallbackRecentsToActivityHelper(BaseRecentsActivity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public void leaveRecents() {
+ mActivity.startHome();
+ }
+}
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 76685f3..40db7dd 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -21,10 +21,9 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherInitListener;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.views.IconRecentsView;
import java.util.function.BiPredicate;
@@ -40,10 +39,14 @@
public AnimationFactory prepareRecentsUI(Launcher activity,
boolean activityVisible, boolean animateActivity,
Consumer<AnimatorPlaybackController> callback) {
+ LauncherState fromState = activity.getStateManager().getState();
//TODO: Implement this based off where the recents view needs to be for app => recents anim.
return new AnimationFactory() {
@Override
- public void createActivityController(long transitionLength) {}
+ public void createActivityController(long transitionLength) {
+ callback.accept(activity.getStateManager().createAnimationToNewWorkspace(
+ fromState, OVERVIEW, transitionLength));
+ }
@Override
public void onTransitionCancelled() {}
@@ -94,11 +97,6 @@
}
@Override
- public AlphaProperty getAlphaProperty(Launcher activity) {
- return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
- }
-
- @Override
public int getContainerType() {
final Launcher launcher = getVisibleLauncher();
return launcher != null ? launcher.getStateManager().getState().containerType
diff --git a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index d9873fe..379cc10 100644
--- a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -96,7 +96,7 @@
if (recents == null) {
return false;
}
- //TODO: Launch last running task or go to home.
+ recents.handleOverviewCommand();
return true;
}
}
@@ -146,7 +146,7 @@
protected boolean handleCommand(long elapsedTime) {
IconRecentsView recents = mHelper.getVisibleRecentsView();
if (recents != null) {
- //TODO: Launch next task in icon recents.
+ recents.handleOverviewCommand();
return true;
} else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
// The user tried to launch back into overview too quickly, either after
diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
index a186aaa..447e7e7 100644
--- a/go/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -36,6 +36,7 @@
setContentView(R.layout.fallback_recents_activity);
mRecentsRootView = findViewById(R.id.drag_layer);
mIconRecentsView = findViewById(R.id.overview_panel);
+ mIconRecentsView.setRecentsToActivityHelper(new FallbackRecentsToActivityHelper(this));
}
@Override
@@ -60,7 +61,7 @@
@Override
public ActivityOptions getActivityLaunchOptions(View v) {
- //TODO: Hook into recents launch animation
+ // Stubbed. Recents launch animation will come from the recents view itself.
return null;
}
diff --git a/go/quickstep/src/com/android/quickstep/RecentsToActivityHelper.java b/go/quickstep/src/com/android/quickstep/RecentsToActivityHelper.java
new file mode 100644
index 0000000..8f3b707
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/RecentsToActivityHelper.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Generic interface providing methods to the recents implementation that allow it to callback to
+ * the containing activity.
+ */
+public interface RecentsToActivityHelper {
+
+ /**
+ * The default action to take when leaving/closing recents. In general, this should be used to
+ * go to the appropriate home state.
+ */
+ void leaveRecents();
+}
diff --git a/go/quickstep/src/com/android/quickstep/TaskActionController.java b/go/quickstep/src/com/android/quickstep/TaskActionController.java
new file mode 100644
index 0000000..b2d495b
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskActionController.java
@@ -0,0 +1,80 @@
+/*
+ * 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.ActivityOptions;
+import android.view.View;
+
+import com.android.quickstep.views.TaskItemView;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+/**
+ * Controller that provides logic for task-related commands on recents and updating the model/view
+ * as appropriate.
+ */
+public final class TaskActionController {
+
+ private final TaskListLoader mLoader;
+ private final TaskAdapter mAdapter;
+
+ public TaskActionController(TaskListLoader loader, TaskAdapter adapter) {
+ mLoader = loader;
+ mAdapter = adapter;
+ }
+
+ /**
+ * Launch the task associated with the task holder, animating into the app.
+ *
+ * @param viewHolder the task view holder to launch
+ */
+ public void launchTask(TaskHolder viewHolder) {
+ TaskItemView itemView = (TaskItemView) (viewHolder.itemView);
+ View v = itemView.getThumbnailView();
+ int left = 0;
+ int top = 0;
+ int width = v.getMeasuredWidth();
+ int height = v.getMeasuredHeight();
+
+ ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(viewHolder.getTask().key,
+ opts, null /* resultCallback */, null /* resultCallbackHandler */);
+ }
+
+ /**
+ * Removes the task holder and the task, updating the model and the view.
+ *
+ * @param viewHolder the task view holder to remove
+ */
+ public void removeTask(TaskHolder viewHolder) {
+ int position = viewHolder.getAdapterPosition();
+ Task task = viewHolder.getTask();
+ ActivityManagerWrapper.getInstance().removeTask(task.key.id);
+ mLoader.removeTask(task);
+ mAdapter.notifyItemRemoved(position);
+ }
+
+ /**
+ * Clears all tasks and updates the model and view.
+ */
+ public void clearAllTasks() {
+ // TODO: Play an animation so transition is more natural.
+ int count = mAdapter.getItemCount();
+ ActivityManagerWrapper.getInstance().removeAllRecentTasks();
+ mLoader.clearAllTasks();
+ mAdapter.notifyItemRangeRemoved(0 /* positionStart */, count);
+ }
+}
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
index 57cd60a..e56cc51 100644
--- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java
+++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
@@ -15,15 +15,19 @@
*/
package com.android.quickstep;
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
import android.view.ViewGroup;
-import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView.Adapter;
+import com.android.launcher3.R;
+import com.android.quickstep.views.TaskItemView;
import com.android.systemui.shared.recents.model.Task;
-import java.util.ArrayList;
+import java.util.List;
/**
* Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
@@ -34,26 +38,55 @@
private static final int MAX_TASKS_TO_DISPLAY = 6;
private static final String TAG = "TaskAdapter";
private final TaskListLoader mLoader;
+ private final ArrayMap<Integer, TaskItemView> mTaskIdToViewMap = new ArrayMap<>();
+ private TaskActionController mTaskActionController;
public TaskAdapter(@NonNull TaskListLoader loader) {
mLoader = loader;
}
+ public void setActionController(TaskActionController taskActionController) {
+ mTaskActionController = taskActionController;
+ }
+
+ /**
+ * Get task item view for a given task id if it's attached to the view.
+ *
+ * @param taskId task id to search for
+ * @return corresponding task item view if it's attached, null otherwise
+ */
+ public @Nullable TaskItemView getTaskItemView(int taskId) {
+ return mTaskIdToViewMap.get(taskId);
+ }
+
@Override
public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- // TODO: Swap in an actual task view here (view w/ icon, label, etc.)
- TextView stubView = new TextView(parent.getContext());
- return new TaskHolder(stubView);
+ TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.task_item_view, parent, false);
+ TaskHolder holder = new TaskHolder(itemView);
+ itemView.setOnClickListener(view -> mTaskActionController.launchTask(holder));
+ return holder;
}
@Override
public void onBindViewHolder(TaskHolder holder, int position) {
- ArrayList<Task> tasks = mLoader.getCurrentTaskList();
+ List<Task> tasks = mLoader.getCurrentTaskList();
if (position >= tasks.size()) {
// Task list has updated.
return;
}
holder.bindTask(tasks.get(position));
+
+ }
+
+ @Override
+ public void onViewAttachedToWindow(@NonNull TaskHolder holder) {
+ mTaskIdToViewMap.put(holder.getTask().key.id, (TaskItemView) holder.itemView);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull TaskHolder holder) {
+ mTaskIdToViewMap.remove(holder.getTask().key.id);
}
@Override
diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java
index 1ea6d76..a89229f 100644
--- a/go/quickstep/src/com/android/quickstep/TaskHolder.java
+++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java
@@ -15,34 +15,45 @@
*/
package com.android.quickstep;
-import android.widget.TextView;
-
+import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+import com.android.quickstep.views.TaskItemView;
import com.android.systemui.shared.recents.model.Task;
/**
* A recycler view holder that holds the task view and binds {@link Task} content (app title, icon,
* etc.) to the view.
*/
-final class TaskHolder extends ViewHolder {
+public final class TaskHolder extends ViewHolder {
- // TODO: Implement the actual task view to be held.
- // For now, we just use a simple text view.
- private final TextView mStubView;
+ private final TaskItemView mTaskItemView;
+ private Task mTask;
- public TaskHolder(TextView stubView) {
- super(stubView);
- mStubView = stubView;
+ public TaskHolder(TaskItemView itemView) {
+ super(itemView);
+ mTaskItemView = itemView;
}
/**
* Bind task content to the view. This includes the task icon and title as well as binding
* input handlers such as which task to launch/remove.
*
- * @param task the task to bind to the view this
+ * @param task the task to bind to the view
*/
public void bindTask(Task task) {
- mStubView.setText("Stub task view: " + task.titleDescription);
+ mTask = task;
+ mTaskItemView.setLabel(task.titleDescription);
+ mTaskItemView.setIcon(task.icon);
+ mTaskItemView.setThumbnail(task.thumbnail.thumbnail);
+ }
+
+ /**
+ * Gets the task currently bound to this view
+ *
+ * @return the current task
+ */
+ public @NonNull Task getTask() {
+ return mTask;
}
}
diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
index 9f359b4..c86c24e 100644
--- a/go/quickstep/src/com/android/quickstep/TaskListLoader.java
+++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
@@ -24,6 +24,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -45,13 +46,13 @@
/**
* Returns the current task list as of the last completed load (see
- * {@link #loadTaskList}). This list of tasks is guaranteed to always have all its task
- * content loaded.
+ * {@link #loadTaskList}) as a read-only list. This list of tasks is guaranteed to always have
+ * all its task content loaded.
*
* @return the current list of tasks w/ all content loaded
*/
- public ArrayList<Task> getCurrentTaskList() {
- return mTaskList;
+ public List<Task> getCurrentTaskList() {
+ return Collections.unmodifiableList(mTaskList);
}
/**
@@ -85,34 +86,54 @@
}
/**
- * Loads task content for a list of tasks, including the label and the icon. Uses the list of
- * tasks since the last load as a cache for loaded content.
+ * Removes the task from the current task list.
+ */
+ void removeTask(Task task) {
+ mTaskList.remove(task);
+ }
+
+ /**
+ * Clears the current task list.
+ */
+ void clearAllTasks() {
+ mTaskList.clear();
+ }
+
+ /**
+ * Loads task content for a list of tasks, including the label, icon, and thumbnail. For content
+ * that isn't cached, load the content asynchronously in the background.
*
* @param tasksToLoad list of tasks that need to load their content
- * @param onLoadedCallback runnable to run after all tasks have loaded their content
+ * @param onFullyLoadedCallback runnable to run after all tasks have loaded their content
*/
private void loadTaskContents(ArrayList<Task> tasksToLoad,
- @Nullable Runnable onLoadedCallback) {
- AtomicInteger loadRequestsCount = new AtomicInteger(0);
+ @Nullable Runnable onFullyLoadedCallback) {
+ // Make two load requests per task, one for the icon/title and one for the thumbnail.
+ AtomicInteger loadRequestsCount = new AtomicInteger(tasksToLoad.size() * 2);
+ Runnable itemLoadedRunnable = () -> {
+ if (loadRequestsCount.decrementAndGet() == 0 && onFullyLoadedCallback != null) {
+ onFullyLoadedCallback.run();
+ }
+ };
for (Task task : tasksToLoad) {
+ // Load icon and title.
int index = mTaskList.indexOf(task);
if (index >= 0) {
// If we've already loaded the task and have its content then just copy it over.
Task loadedTask = mTaskList.get(index);
task.titleDescription = loadedTask.titleDescription;
task.icon = loadedTask.icon;
+ itemLoadedRunnable.run();
} else {
// Otherwise, load the content in the background.
- loadRequestsCount.getAndIncrement();
- mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> {
- if (loadRequestsCount.decrementAndGet() == 0 && onLoadedCallback != null) {
- onLoadedCallback.run();
- }
- });
+ mRecentsModel.getIconCache().updateIconInBackground(task,
+ loadedTask -> itemLoadedRunnable.run());
}
- }
- if (loadRequestsCount.get() == 0 && onLoadedCallback != null) {
- onLoadedCallback.run();
+
+ // Load the thumbnail. May return immediately and synchronously if the thumbnail is
+ // cached.
+ mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task,
+ thumbnail -> itemLoadedRunnable.run());
}
}
}
diff --git a/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
new file mode 100644
index 0000000..98407d8
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
@@ -0,0 +1,48 @@
+/*
+ * 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 androidx.recyclerview.widget.ItemTouchHelper.RIGHT;
+
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Callback for swipe input on {@link TaskHolder} views in the recents view.
+ */
+public final class TaskSwipeCallback extends ItemTouchHelper.SimpleCallback {
+
+ private final TaskActionController mTaskActionController;
+
+ public TaskSwipeCallback(TaskActionController taskActionController) {
+ super(0 /* dragDirs */, RIGHT);
+ mTaskActionController = taskActionController;
+ }
+
+ @Override
+ public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder,
+ ViewHolder target) {
+ return false;
+ }
+
+ @Override
+ public void onSwiped(ViewHolder viewHolder, int direction) {
+ if (direction == RIGHT) {
+ mTaskActionController.removeTask((TaskHolder) viewHolder);
+ }
+ }
+}
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index afb0540..a1d62c2 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -17,18 +17,29 @@
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.view.View;
import android.view.ViewDebug;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
import com.android.launcher3.R;
+import com.android.quickstep.RecentsToActivityHelper;
+import com.android.quickstep.TaskActionController;
import com.android.quickstep.TaskAdapter;
+import com.android.quickstep.TaskHolder;
import com.android.quickstep.TaskListLoader;
+import com.android.quickstep.TaskSwipeCallback;
/**
* Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
@@ -36,20 +47,6 @@
*/
public final class IconRecentsView extends FrameLayout {
- public static final FloatProperty<IconRecentsView> TRANSLATION_Y_FACTOR =
- new FloatProperty<IconRecentsView>("translationYFactor") {
-
- @Override
- public void setValue(IconRecentsView view, float v) {
- view.setTranslationYFactor(v);
- }
-
- @Override
- public Float get(IconRecentsView view) {
- return view.mTranslationYFactor;
- }
- };
-
public static final FloatProperty<IconRecentsView> CONTENT_ALPHA =
new FloatProperty<IconRecentsView>("contentAlpha") {
@Override
@@ -67,6 +64,7 @@
return ALPHA.get(view);
}
};
+ private static final long CROSSFADE_DURATION = 300;
/**
* A ratio representing the view's relative placement within its padded space. For example, 0
@@ -75,26 +73,63 @@
@ViewDebug.ExportedProperty(category = "launcher")
private final Context mContext;
+ private final TaskListLoader mTaskLoader;
+ private final TaskAdapter mTaskAdapter;
+ private final TaskActionController mTaskActionController;
- private float mTranslationYFactor;
- private TaskAdapter mTaskAdapter;
+ private RecentsToActivityHelper mActivityHelper;
private RecyclerView mTaskRecyclerView;
- private TaskListLoader mTaskLoader;
+ private View mEmptyView;
+ private View mContentView;
+ private boolean mTransitionedFromApp;
public IconRecentsView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
+ mTaskLoader = new TaskListLoader(mContext);
+ mTaskAdapter = new TaskAdapter(mTaskLoader);
+ mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
+ mTaskAdapter.setActionController(mTaskActionController);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mTaskLoader = new TaskListLoader(mContext);
- mTaskAdapter = new TaskAdapter(mTaskLoader);
- mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
- mTaskRecyclerView.setAdapter(mTaskAdapter);
- mTaskRecyclerView.setLayoutManager(
- new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
+ if (mTaskRecyclerView == null) {
+ mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
+ mTaskRecyclerView.setAdapter(mTaskAdapter);
+ mTaskRecyclerView.setLayoutManager(
+ new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
+ ItemTouchHelper helper = new ItemTouchHelper(
+ new TaskSwipeCallback(mTaskActionController));
+ helper.attachToRecyclerView(mTaskRecyclerView);
+
+ mEmptyView = findViewById(R.id.recent_task_empty_view);
+ mContentView = findViewById(R.id.recent_task_content_view);
+ mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ updateContentViewVisibility();
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ updateContentViewVisibility();
+ }
+ });
+
+ View clearAllView = findViewById(R.id.clear_all_button);
+ clearAllView.setOnClickListener(v -> mTaskActionController.clearAllTasks());
+ }
+ }
+
+ /**
+ * Set activity helper for the view to callback to.
+ *
+ * @param helper the activity helper
+ */
+ public void setRecentsToActivityHelper(@NonNull RecentsToActivityHelper helper) {
+ mActivityHelper = helper;
}
/**
@@ -113,12 +148,89 @@
});
}
- public void setTranslationYFactor(float translationFactor) {
- mTranslationYFactor = translationFactor;
- setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
+ /**
+ * Set whether we transitioned to recents from the most recent app.
+ *
+ * @param transitionedFromApp true if transitioned from the most recent app, false otherwise
+ */
+ public void setTransitionedFromApp(boolean transitionedFromApp) {
+ mTransitionedFromApp = transitionedFromApp;
}
- private float computeTranslationYForFactor(float translationYFactor) {
- return translationYFactor * (getPaddingBottom() - getPaddingTop());
+ /**
+ * Handles input from the overview button. Launch the most recent task unless we just came from
+ * the app. In that case, we launch the next most recent.
+ */
+ public void handleOverviewCommand() {
+ int childCount = mTaskRecyclerView.getChildCount();
+ if (childCount == 0) {
+ // Do nothing
+ return;
+ }
+ TaskHolder taskToLaunch;
+ if (mTransitionedFromApp && childCount > 1) {
+ // Launch the next most recent app
+ TaskItemView itemView = (TaskItemView) mTaskRecyclerView.getChildAt(1);
+ taskToLaunch = (TaskHolder) mTaskRecyclerView.getChildViewHolder(itemView);
+ } else {
+ // Launch the most recent app
+ TaskItemView itemView = (TaskItemView) mTaskRecyclerView.getChildAt(0);
+ taskToLaunch = (TaskHolder) mTaskRecyclerView.getChildViewHolder(itemView);
+ }
+ mTaskActionController.launchTask(taskToLaunch);
+ }
+
+ /**
+ * Get the thumbnail view associated with a task for the purposes of animation.
+ *
+ * @param taskId task id of thumbnail view to get
+ * @return the thumbnail view for the task if attached, null otherwise
+ */
+ public @Nullable View getThumbnailViewForTask(int taskId) {
+ TaskItemView view = mTaskAdapter.getTaskItemView(taskId);
+ if (view == null) {
+ return null;
+ }
+ return view.getThumbnailView();
+ }
+
+ /**
+ * Update the content view so that the appropriate view is shown based off the current list
+ * of tasks.
+ */
+ private void updateContentViewVisibility() {
+ int taskListSize = mTaskLoader.getCurrentTaskList().size();
+ if (mEmptyView.getVisibility() != VISIBLE && taskListSize == 0) {
+ crossfadeViews(mEmptyView, mContentView);
+ mActivityHelper.leaveRecents();
+ }
+ if (mContentView.getVisibility() != VISIBLE && taskListSize > 0) {
+ crossfadeViews(mContentView, mEmptyView);
+ }
+ }
+
+ /**
+ * Animate views so that one view fades in while the other fades out.
+ *
+ * @param fadeInView view that should fade in
+ * @param fadeOutView view that should fade out
+ */
+ private void crossfadeViews(View fadeInView, View fadeOutView) {
+ fadeInView.setVisibility(VISIBLE);
+ fadeInView.setAlpha(0f);
+ fadeInView.animate()
+ .alpha(1f)
+ .setDuration(CROSSFADE_DURATION)
+ .setListener(null);
+
+ fadeOutView.animate()
+ .alpha(0f)
+ .setDuration(CROSSFADE_DURATION)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ fadeOutView.setVisibility(GONE);
+ }
+ });
}
}
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
new file mode 100644
index 0000000..373f107
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
@@ -0,0 +1,84 @@
+/*
+ * 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.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+/**
+ * View representing an individual task item with the icon + thumbnail adjacent to the task label.
+ */
+public final class TaskItemView extends LinearLayout {
+
+ private TextView mLabelView;
+ private ImageView mIconView;
+ private ImageView mThumbnailView;
+
+ public TaskItemView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mLabelView = findViewById(R.id.task_label);
+ mThumbnailView = findViewById(R.id.task_thumbnail);
+ mIconView = findViewById(R.id.task_icon);
+ }
+
+ /**
+ * Set the label for the task item.
+ *
+ * @param label task label
+ */
+ public void setLabel(String label) {
+ mLabelView.setText(label);
+ }
+
+ /**
+ * Set the icon for the task item.
+ *
+ * @param icon task icon
+ */
+ public void setIcon(Drawable icon) {
+ // TODO: Scale the icon up based off the padding on the side
+ // The icon proper is actually smaller than the drawable and has "padding" on the side for
+ // the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the
+ // view if we want the icon to be flush with the bottom of the thumbnail.
+ mIconView.setImageDrawable(icon);
+ }
+
+ /**
+ * Set the task thumbnail for the task.
+ *
+ * @param thumbnail task thumbnail for the task
+ */
+ public void setThumbnail(Bitmap thumbnail) {
+ mThumbnailView.setImageBitmap(thumbnail);
+ }
+
+ public View getThumbnailView() {
+ return mThumbnailView;
+ }
+}
diff --git a/libs/README.txt b/libs/README.txt
deleted file mode 100644
index 9109592..0000000
--- a/libs/README.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-These jar are compiled in the frameworks/base of the platform tree.
-
-launcher_protos.jar is defined as launcherprotosnano in the following file:
-frameworks/base/core/protos/android/stats/launcher/Android.bp
-
-plugin_core.jar is defined as PluginCoreLib in the following file:
-frameworks/base/packages/SystemUI/plugin/Android.bp
-
diff --git a/libs/launcher_protos.jar b/libs/launcher_protos.jar
deleted file mode 100644
index c043936..0000000
--- a/libs/launcher_protos.jar
+++ /dev/null
Binary files differ
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 4129ae8..85f9826 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -122,6 +122,7 @@
SWIPE_UP_TEXT = 2;
QUICK_SCRUB_TEXT = 3;
PREDICTION_TEXT = 4;
+ DWB_TOAST = 5;
}
// Used to define the action component of the LauncherEvent.
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 62d0500..97fc284 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -73,6 +73,14 @@
</intent-filter>
</provider>
+ <provider
+ android:name="com.android.quickstep.TestInformationProvider"
+ android:authorities="${packageName}.TestInfo"
+ android:readPermission="android.permission.WRITE_SECURE_SETTINGS"
+ android:writePermission="android.permission.WRITE_SECURE_SETTINGS"
+ android:exported="true">
+ </provider>
+
<service
android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
tools:node="remove" />
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
deleted file mode 100644
index 8c58e3e..0000000
--- a/quickstep/libs/sysui_shared.jar
+++ /dev/null
Binary files differ
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
index 963f1fa..f712753 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
@@ -20,6 +20,7 @@
import android.os.RemoteException;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -51,7 +52,7 @@
}
@Override
- public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+ public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
// Initialize the recents view scale to what it would be when starting swipe up
RecentsView recentsView = launcher.getOverviewPanel();
recentsView.getTaskSize(sTempRect);
@@ -72,4 +73,13 @@
float scale = (float) appWidth / sTempRect.width();
return new float[] { scale, 0f };
}
+
+ @Override
+ public int getVisibleElements(Launcher launcher) {
+ if (FeatureFlags.SWIPE_HOME.get()) {
+ return super.getVisibleElements(launcher);
+ }
+ // Hide shelf content (e.g. QSB) because we fade it in when swiping up.
+ return ALL_APPS_HEADER_EXTRA;
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
index 3602508..2360eeb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
import android.graphics.Rect;
@@ -25,13 +26,14 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
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.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.RecentsModel;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -67,7 +69,7 @@
}
@Override
- public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+ public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
return new float[] {1f, 0f};
}
@@ -131,7 +133,10 @@
}
public static float getDefaultSwipeHeight(Launcher launcher) {
- DeviceProfile dp = launcher.getDeviceProfile();
+ return getDefaultSwipeHeight(launcher.getDeviceProfile());
+ }
+
+ public static float getDefaultSwipeHeight(DeviceProfile dp) {
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
}
@@ -139,6 +144,8 @@
public void onBackPressed(Launcher launcher) {
TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
if (taskView != null) {
+ launcher.getUserEventDispatcher().logActionCommand(Action.Command.BACK,
+ newContainerTarget(ContainerType.OVERVIEW));
taskView.launchTask(true);
} else {
super.onBackPressed(launcher);
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 0b3bd6c..0d5574f 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,7 +15,6 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import android.animation.ValueAnimator;
@@ -23,8 +22,6 @@
import android.os.Build;
import android.util.FloatProperty;
-import androidx.annotation.NonNull;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -32,6 +29,8 @@
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
+import androidx.annotation.NonNull;
+
/**
* State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing
* the basic view properties, this class also manages changes in the task visuals.
@@ -80,11 +79,6 @@
}
@Override
- FloatProperty<LauncherRecentsView> getTranslationYFactorProperty() {
- return TRANSLATION_Y_FACTOR;
- }
-
- @Override
FloatProperty<RecentsView> getContentAlphaProperty() {
return CONTENT_ALPHA;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
index 109a4c5..c00b4dc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
@@ -22,59 +22,70 @@
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.PointF;
-import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
-import android.view.Display;
import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.ViewConfiguration;
-import android.view.WindowManager;
+import com.android.launcher3.anim.Interpolators;
+import com.android.quickstep.util.MotionPauseDetector;
import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.system.NavigationBarCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.launcher3.R;
+import com.android.systemui.shared.system.NavigationBarCompat;
/**
* Touch consumer for handling events to launch assistant from launcher
*/
public class AssistantTouchConsumer implements InputConsumer {
private static final String TAG = "AssistantTouchConsumer";
+ private static final long RETRACT_ANIMATION_DURATION_MS = 300;
+
+ /* The assistant touch consume competes with quick switch InputConsumer gesture. The delegate
+ * can be chosen to run if the angle passing the slop is lower than the threshold angle. When
+ * this occurs, the state changes to {@link #STATE_DELEGATE_ACTIVE} where the next incoming
+ * motion events are handled by the delegate instead of the assistant touch consumer. If the
+ * angle is higher than the threshold, the state will change to {@link #STATE_ASSISTANT_ACTIVE}.
+ */
+ private static final int STATE_INACTIVE = 0;
+ private static final int STATE_ASSISTANT_ACTIVE = 1;
+ private static final int STATE_DELEGATE_ACTIVE = 2;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
+ private final PointF mStartDragPos = new PointF();
+
private int mActivePointerId = -1;
-
- private final int mDisplayRotation;
- private final Rect mStableInsets = new Rect();
-
- private final float mDragSlop;
- private final float mTouchSlop;
- private final float mThreshold;
-
- private float mStartDisplacement;
- private boolean mPassedDragSlop;
- private boolean mPassedTouchSlop;
- private long mPassedTouchSlopTime;
+ private boolean mPassedSlop;
private boolean mLaunchedAssistant;
+ private float mDistance;
+ private float mTimeFraction;
+ private long mDragTime;
private float mLastProgress;
+ private int mState;
+ private final float mDistThreshold;
+ private final long mTimeThreshold;
+ private final int mAngleThreshold;
+ private final float mSlop;
+ private final MotionPauseDetector mMotionPauseDetector;
private final ISystemUiProxy mSysUiProxy;
+ private final InputConsumer mConsumerDelegate;
- public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy) {
+ public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
+ InputConsumer delegate) {
+ final Resources res = context.getResources();
mSysUiProxy = systemUiProxy;
-
- mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
- mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
- mThreshold = context.getResources().getDimension(R.dimen.gestures_assistant_threshold);
-
- Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
- mDisplayRotation = display.getRotation();
- WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+ mConsumerDelegate = delegate;
+ mMotionPauseDetector = new MotionPauseDetector(context);
+ mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
+ mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
+ mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
+ mSlop = NavigationBarCompat.getQuickScrubTouchSlopPx();
+ mState = STATE_INACTIVE;
}
@Override
@@ -83,14 +94,28 @@
}
@Override
+ public boolean isActive() {
+ return mState != STATE_INACTIVE;
+ }
+
+ @Override
public void onMotionEvent(MotionEvent ev) {
// TODO add logging
+
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
- mLastProgress = -1;
+ mTimeFraction = 0;
+
+ // Detect when the gesture decelerates to start the assistant
+ mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
+ if (isPaused && mState == STATE_ASSISTANT_ACTIVE) {
+ mTimeFraction = 1;
+ updateAssistantProgress();
+ }
+ });
break;
}
case ACTION_POINTER_UP: {
@@ -107,94 +132,101 @@
break;
}
case ACTION_MOVE: {
+ if (mState == STATE_DELEGATE_ACTIVE) {
+ break;
+ }
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
break;
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
- float displacement = getDisplacement(ev);
- if (!mPassedDragSlop) {
- // Normal gesture, ensure we pass the drag slop before we start tracking
- // the gesture
- if (Math.abs(displacement) > mDragSlop) {
- mPassedDragSlop = true;
- mStartDisplacement = displacement;
- mPassedTouchSlopTime = SystemClock.uptimeMillis();
- }
- }
+ if (!mPassedSlop) {
+ // Normal gesture, ensure we pass the slop before we start tracking the gesture
+ if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) > mSlop) {
+ mPassedSlop = true;
+ mStartDragPos.set(mLastPos.x, mLastPos.y);
+ mDragTime = SystemClock.uptimeMillis();
- if (!mPassedTouchSlop) {
- if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) >=
- mTouchSlop) {
- mPassedTouchSlop = true;
- if (!mPassedDragSlop) {
- mPassedDragSlop = true;
- mStartDisplacement = displacement;
- mPassedTouchSlopTime = SystemClock.uptimeMillis();
+ // Determine if angle is larger than threshold for assistant detection
+ float angle = (float) Math.toDegrees(
+ Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x));
+ angle = angle > 90 ? 180 - angle : angle;
+ if (angle > mAngleThreshold) {
+ mState = STATE_ASSISTANT_ACTIVE;
+
+ if (mConsumerDelegate != null) {
+ // Send cancel event
+ MotionEvent event = MotionEvent.obtain(ev);
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ mConsumerDelegate.onMotionEvent(event);
+ event.recycle();
+ }
+ } else {
+ mState = STATE_DELEGATE_ACTIVE;
}
}
- }
-
- if (mPassedDragSlop) {
- // Move
- float distance = mStartDisplacement - displacement;
- if (distance >= 0) {
- onAssistantProgress(distance / mThreshold);
+ } else {
+ // Movement
+ mDistance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
+ mLastPos.y - mStartDragPos.y);
+ mMotionPauseDetector.addPosition(mDistance, 0);
+ if (mDistance >= 0) {
+ final long diff = SystemClock.uptimeMillis() - mDragTime;
+ mTimeFraction = Math.min(diff * 1f / mTimeThreshold, 1);
+ updateAssistantProgress();
}
}
break;
}
case ACTION_CANCEL:
- break;
- case ACTION_UP: {
- if (ev.getEventTime() - mPassedTouchSlopTime < ViewConfiguration.getTapTimeout()) {
- onAssistantProgress(1);
+ case ACTION_UP:
+ if (mState != STATE_DELEGATE_ACTIVE && !mLaunchedAssistant) {
+ ValueAnimator animator = ValueAnimator.ofFloat(mLastProgress, 0)
+ .setDuration(RETRACT_ANIMATION_DURATION_MS);
+ animator.addUpdateListener(valueAnimator -> {
+ float progress = (float) valueAnimator.getAnimatedValue();
+ try {
+ mSysUiProxy.onAssistantProgress(progress);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to send SysUI start/send assistant progress: "
+ + progress, e);
+ }
+ });
+ animator.setInterpolator(Interpolators.DEACCEL_2);
+ animator.start();
}
-
+ mMotionPauseDetector.clear();
break;
- }
+ }
+
+ if (mState != STATE_ASSISTANT_ACTIVE && mConsumerDelegate != null) {
+ mConsumerDelegate.onMotionEvent(ev);
}
}
- private void onAssistantProgress(float progress) {
- if (mLastProgress == progress) {
- return;
- }
- try {
- mSysUiProxy.onAssistantProgress(Math.max(0, Math.min(1, progress)));
- if (progress >= 1 && !mLaunchedAssistant) {
- mSysUiProxy.startAssistant(new Bundle());
- mLaunchedAssistant = true;
- }
+ private void updateAssistantProgress() {
+ if (!mLaunchedAssistant) {
+ float progress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction;
mLastProgress = progress;
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to notify SysUI to start/send assistant progress: " + progress, e);
+ try {
+ mSysUiProxy.onAssistantProgress(progress);
+
+ if (mDistance >= mDistThreshold && mTimeFraction >= 1) {
+ mSysUiProxy.startAssistant(new Bundle());
+ mLaunchedAssistant = true;
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + progress, e);
+ }
}
}
- private boolean isNavBarOnRight() {
- return mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0;
- }
-
- private boolean isNavBarOnLeft() {
- return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
- }
-
- private float getDisplacement(MotionEvent ev) {
- float eventX = ev.getX();
- float eventY = ev.getY();
- float displacement = eventY - mDownPos.y;
- if (isNavBarOnRight()) {
- displacement = eventX - mDownPos.x;
- } else if (isNavBarOnLeft()) {
- displacement = mDownPos.x - eventX;
- }
- return displacement;
- }
-
- static boolean withinTouchRegion(Context context, float x) {
- return x > context.getResources().getDisplayMetrics().widthPixels
- - context.getResources().getDimension(R.dimen.gestures_assistant_width);
+ static boolean withinTouchRegion(Context context, MotionEvent ev) {
+ final Resources res = context.getResources();
+ final int width = res.getDisplayMetrics().widthPixels;
+ final int height = res.getDisplayMetrics().heightPixels;
+ final int size = res.getDimensionPixelSize(R.dimen.gestures_assistant_size);
+ return (ev.getX() > width - size || ev.getX() < size) && ev.getY() > height - size;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
index 1ed1353..ef46b3b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -21,7 +21,6 @@
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -90,7 +89,7 @@
@NonNull
@Override
- public Animator createActivityAnimationToHome() {
+ public AnimatorPlaybackController createActivityAnimationToHome() {
Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0);
anim.addListener(new AnimationSuccessListener() {
@Override
@@ -98,7 +97,10 @@
recentsView.startHome();
}
});
- return anim;
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.play(anim);
+ long accuracy = 2 * Math.max(recentsView.getWidth(), recentsView.getHeight());
+ return AnimatorPlaybackController.wrap(animatorSet, accuracy);
}
};
}
@@ -182,11 +184,6 @@
}
@Override
- public AlphaProperty getAlphaProperty(RecentsActivity activity) {
- return activity.getDragLayer().getAlphaProperty(0);
- }
-
- @Override
public int getContainerType() {
return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 971987a..279b83c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.Animator;
@@ -38,19 +39,24 @@
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.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.LayoutUtils;
@@ -61,10 +67,6 @@
import java.util.function.BiPredicate;
import java.util.function.Consumer;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
/**
* {@link ActivityControlHelper} for the in-launcher recents.
*/
@@ -140,10 +142,9 @@
@NonNull
@Override
- public Animator createActivityAnimationToHome() {
+ public AnimatorPlaybackController createActivityAnimationToHome() {
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
- return activity.getStateManager().createAnimationToNewWorkspace(
- NORMAL, accuracy).getTarget();
+ return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy);
}
};
}
@@ -212,7 +213,7 @@
: mShelfState == ShelfAnimState.PEEK
? shelfPeekingProgress
: shelfOverviewProgress;
- mShelfAnim = createShelfAnim(activity, toProgress);
+ mShelfAnim = createShelfProgressAnim(activity, toProgress);
mShelfAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -230,10 +231,10 @@
LauncherState fromState, long transitionLength,
Consumer<AnimatorPlaybackController> callback) {
LauncherState endState = OVERVIEW;
+ DeviceProfile dp = activity.getDeviceProfile();
+ long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
if (wasVisible && fromState != BACKGROUND_APP) {
// If a translucent app was launched fom launcher, animate launcher states.
- DeviceProfile dp = activity.getDeviceProfile();
- long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
callback.accept(activity.getStateManager()
.createAnimationToNewWorkspace(fromState, endState, accuracy));
return;
@@ -246,10 +247,11 @@
if (!activity.getDeviceProfile().isVerticalBarLayout()
&& !FeatureFlags.SWIPE_HOME.get()) {
// Don't animate the shelf when SWIPE_HOME is true, because we update it atomically.
- Animator shiftAnim = createShelfAnim(activity,
+ Animator shiftAnim = createShelfProgressAnim(activity,
fromState.getVerticalProgress(activity),
endState.getVerticalProgress(activity));
anim.play(shiftAnim);
+ anim.play(createShelfAlphaAnim(activity, endState, accuracy));
}
playScaleDownAnim(anim, activity, endState);
@@ -266,7 +268,7 @@
callback.accept(controller);
}
- private Animator createShelfAnim(Launcher activity, float ... progressValues) {
+ private Animator createShelfProgressAnim(Launcher activity, float ... progressValues) {
Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
"allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(),
SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
@@ -275,6 +277,19 @@
}
/**
+ * Very quickly fade the alpha of shelf content.
+ */
+ private Animator createShelfAlphaAnim(Launcher activity, LauncherState toState, long accuracy) {
+ AllAppsTransitionController allAppsController = activity.getAllAppsController();
+ AnimatorSetBuilder animBuilder = new AnimatorSetBuilder();
+ animBuilder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, DEACCEL_3);
+ LauncherStateManager.AnimationConfig config = new LauncherStateManager.AnimationConfig();
+ config.duration = accuracy;
+ allAppsController.setAlphas(toState.getVisibleElements(activity), config, animBuilder);
+ return animBuilder.build();
+ }
+
+ /**
* Scale down recents from the center task being full screen to being in overview.
*/
private void playScaleDownAnim(AnimatorSet anim, Launcher launcher,
@@ -290,7 +305,7 @@
// starting to line up the side pages during swipe up)
float prevRvScale = recentsView.getScaleX();
float prevRvTransY = recentsView.getTranslationY();
- float targetRvScale = endState.getOverviewScaleAndTranslationYFactor(launcher)[0];
+ float targetRvScale = endState.getOverviewScaleAndTranslationY(launcher)[0];
SCALE_PROPERTY.set(recentsView, targetRvScale);
recentsView.setTranslationY(0);
ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
@@ -376,11 +391,6 @@
}
@Override
- public AlphaProperty getAlphaProperty(Launcher activity) {
- return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
- }
-
- @Override
public int getContainerType() {
final Launcher launcher = getVisibleLauncher();
return launcher != null ? launcher.getStateManager().getState().containerType
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
index 9fceab4..357c9fc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
@@ -18,6 +18,8 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.launcher3.config.FeatureFlags;
+
import java.util.StringJoiner;
import java.util.function.Consumer;
@@ -101,6 +103,9 @@
* The callback is only run once.
*/
public void addCallback(int stateMask, Runnable callback) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && mCallbacks.get(stateMask) != null) {
+ throw new IllegalStateException("Multiple callbacks on same state");
+ }
mCallbacks.put(stateMask, callback);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index e3afb92..c8dcf80 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -21,10 +21,9 @@
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
-
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
-import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -45,8 +44,6 @@
import android.view.ViewConfiguration;
import android.view.WindowManager;
-import androidx.annotation.UiThread;
-
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RaceConditionTracker;
@@ -63,6 +60,8 @@
import java.util.function.Consumer;
+import androidx.annotation.UiThread;
+
/**
* Input consumer for handling events originating from an activity other than Launcher
*/
@@ -131,7 +130,8 @@
mVelocityTracker = VelocityTracker.obtain();
mActivityControlHelper = activityControl;
- mIsDeferredDownTarget = isDeferredDownTarget;
+ boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
+ mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
mOverviewCallbacks = overviewCallbacks;
mTaskOverlayFactory = taskOverlayFactory;
mInputConsumer = inputConsumer;
@@ -143,8 +143,7 @@
mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
- // If active listener isn't null, we are continuing the previous gesture.
- mPassedTouchSlop = mPassedDragSlop = mSwipeSharedState.getActiveListener() != null;
+ mPassedTouchSlop = mPassedDragSlop = continuingPreviousGesture;
}
@Override
@@ -230,7 +229,6 @@
mTouchSlop) {
mPassedTouchSlop = true;
- TOUCH_INTERACTION_LOG.addLog("startQuickstep");
if (mIsDeferredDownTarget) {
// Deferred gesture, start the animation and gesture tracking once
// we pass the actual touch slop
@@ -272,6 +270,7 @@
}
private void notifyGestureStarted() {
+ TOUCH_INTERACTION_LOG.addLog("startQuickstep");
if (mInteractionHandler == null) {
return;
}
@@ -310,6 +309,7 @@
if (listenerSet != null) {
listenerSet.addListener(handler);
mSwipeSharedState.applyActiveRecentsAnimationState(handler);
+ notifyGestureStarted();
} else {
RecentsAnimationListenerSet newListenerSet =
mSwipeSharedState.newRecentsAnimationListenerSet();
@@ -330,12 +330,13 @@
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
+ float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
float velocity = isNavBarOnRight() ? velocityX
: isNavBarOnLeft() ? -velocityX
- : mVelocityTracker.getYVelocity(mActivePointerId);
+ : velocityY;
mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
- mInteractionHandler.onGestureEnded(velocity, velocityX);
+ mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY));
} else {
// Since we start touch tracking on DOWN, we may reach this state without actually
// starting the gesture. In that case, just cleanup immediately.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
index d7898b4..cda2a1a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
@@ -30,6 +30,7 @@
import android.view.ViewConfiguration;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Utilities;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.util.CachedEventDispatcher;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -139,7 +140,7 @@
return;
}
int flags = ev.getEdgeFlags();
- ev.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
+ ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
if (ev.getAction() == ACTION_DOWN) {
mTarget.onInterceptTouchEvent(ev);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index f0bc223..0924f38 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -173,6 +173,12 @@
return true;
}
+ public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) {
+ if (targetSet != null) {
+ targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot);
+ }
+ }
+
public SwipeAnimationTargetSet getController() {
return targetSet;
}
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 6d608ee..8fe0461 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -74,8 +74,6 @@
public static final EventLogArray TOUCH_INTERACTION_LOG =
new EventLogArray("touch_interaction_log", 40);
- public static final int EDGE_NAV_BAR = 1 << 8;
-
private static final String TAG = "TouchInteractionService";
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
@@ -313,31 +311,36 @@
mSwipeSharedState.clearAllState();
}
+ final ActivityControlHelper activityControl =
+ mOverviewComponentObserver.getActivityControlHelper();
if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
return InputConsumer.NO_OP;
} else if (mAssistantAvailable && mOverviewInteractionState.isSwipeUpGestureEnabled()
&& FeatureFlags.ENABLE_ASSISTANT_GESTURE.get()
- && AssistantTouchConsumer.withinTouchRegion(this, event.getX())) {
- return new AssistantTouchConsumer(this, mRecentsModel.getSystemUiProxy());
- } else if (mSwipeSharedState.goingToLauncher ||
- mOverviewComponentObserver.getActivityControlHelper().isResumed()) {
- return OverviewInputConsumer.newInstance(
- mOverviewComponentObserver.getActivityControlHelper(), false);
+ && AssistantTouchConsumer.withinTouchRegion(this, event)) {
+ return new AssistantTouchConsumer(this, mISystemUiProxy, !activityControl.isResumed()
+ ? createOtherActivityInputConsumer(event, runningTaskInfo) : null);
+ } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
+ return OverviewInputConsumer.newInstance(activityControl, false);
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
- mOverviewComponentObserver.getActivityControlHelper().isInLiveTileMode()) {
- return OverviewInputConsumer.newInstance(
- mOverviewComponentObserver.getActivityControlHelper(), false);
+ activityControl.isInLiveTileMode()) {
+ return OverviewInputConsumer.newInstance(activityControl, false);
} else {
- ActivityControlHelper activityControl =
- mOverviewComponentObserver.getActivityControlHelper();
- boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
- return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
- mOverviewComponentObserver.getOverviewIntent(), activityControl,
- shouldDefer, mOverviewCallbacks, mTaskOverlayFactory, mInputConsumer,
- this::onConsumerInactive, mSwipeSharedState);
+ return createOtherActivityInputConsumer(event, runningTaskInfo);
}
}
+ private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event,
+ RunningTaskInfo runningTaskInfo) {
+ final ActivityControlHelper activityControl =
+ mOverviewComponentObserver.getActivityControlHelper();
+ boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
+ return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
+ mOverviewComponentObserver.getOverviewIntent(), activityControl,
+ shouldDefer, mOverviewCallbacks, mTaskOverlayFactory, mInputConsumer,
+ this::onConsumerInactive, mSwipeSharedState);
+ }
+
/**
* To be called by the consumer when it's no longer active.
*/
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 7dc58a5..e8d4c19 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -40,16 +40,15 @@
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
@@ -78,8 +77,6 @@
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.MultiValueAlpha;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.views.FloatingIconView;
@@ -88,6 +85,7 @@
import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
@@ -95,7 +93,6 @@
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.recents.utilities.RectFEvaluator;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -113,7 +110,7 @@
implements SwipeAnimationListener, OnApplyWindowInsetsListener {
private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
- private static final String[] STATE_NAMES = DEBUG_STATES ? new String[19] : null;
+ private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
private static int getFlagForIndex(int index, String name) {
if (DEBUG_STATES) {
@@ -126,49 +123,42 @@
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");
- private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE =
- getFlagForIndex(3, "STATE_ACTIVITY_MULTIPLIER_COMPLETE");
// Internal initialization states
private static final int STATE_APP_CONTROLLER_RECEIVED =
- getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
+ getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
// Interaction finish states
private static final int STATE_SCALED_CONTROLLER_HOME =
- getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME");
+ getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
private static final int STATE_SCALED_CONTROLLER_RECENTS =
- getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS");
- private static final int STATE_SCALED_CONTROLLER_LAST_TASK =
- getFlagForIndex(7, "STATE_SCALED_CONTROLLER_LAST_TASK");
+ getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
private static final int STATE_HANDLER_INVALIDATED =
- getFlagForIndex(8, "STATE_HANDLER_INVALIDATED");
+ getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
private static final int STATE_GESTURE_STARTED =
- getFlagForIndex(9, "STATE_GESTURE_STARTED");
+ getFlagForIndex(7, "STATE_GESTURE_STARTED");
private static final int STATE_GESTURE_CANCELLED =
- getFlagForIndex(10, "STATE_GESTURE_CANCELLED");
+ getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
private static final int STATE_GESTURE_COMPLETED =
- getFlagForIndex(11, "STATE_GESTURE_COMPLETED");
+ getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
private static final int STATE_CAPTURE_SCREENSHOT =
- getFlagForIndex(12, "STATE_CAPTURE_SCREENSHOT");
+ getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
private static final int STATE_SCREENSHOT_CAPTURED =
- getFlagForIndex(13, "STATE_SCREENSHOT_CAPTURED");
+ getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
private static final int STATE_SCREENSHOT_VIEW_SHOWN =
- getFlagForIndex(14, "STATE_SCREENSHOT_VIEW_SHOWN");
+ getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
private static final int STATE_RESUME_LAST_TASK =
- getFlagForIndex(15, "STATE_RESUME_LAST_TASK");
+ getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
private static final int STATE_START_NEW_TASK =
- getFlagForIndex(16, "STATE_START_NEW_TASK");
+ getFlagForIndex(14, "STATE_START_NEW_TASK");
private static final int STATE_CURRENT_TASK_FINISHED =
- getFlagForIndex(17, "STATE_CURRENT_TASK_FINISHED");
+ getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
private static final int LAUNCHER_UI_STATES =
- STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE
- | STATE_LAUNCHER_STARTED;
-
- // For debugging, keep in sync with above states
+ STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
enum GestureEndTarget {
HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE),
@@ -178,7 +168,7 @@
NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP),
- LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, false, ContainerType.APP);
+ LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP);
GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
int containerType) {
@@ -226,7 +216,6 @@
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
// visible.
private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
- private boolean mDispatchedDownEvent;
private boolean mContinuingLastGesture;
// To avoid UI jump when gesture is started, we offset the animation by the threshold.
private float mShiftAtGestureStart = 0;
@@ -238,10 +227,10 @@
private final ActivityInitListener mActivityInitListener;
private final int mRunningTaskId;
- private final RunningTaskInfo mRunningTaskInfo;
private ThumbnailData mTaskSnapshot;
private MultiStateCallback mStateCallback;
+ // Used to control launcher components throughout the swipe gesture.
private AnimatorPlaybackController mLauncherTransitionController;
private T mActivity;
@@ -265,7 +254,6 @@
long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
InputConsumerController inputConsumer) {
mContext = context;
- mRunningTaskInfo = runningTaskInfo;
mRunningTaskId = runningTaskInfo.id;
mTouchTimeMs = touchTimeMs;
mActivityControlHelper = controller;
@@ -283,10 +271,8 @@
private void initStateCallbacks() {
mStateCallback = new MultiStateCallback(STATE_NAMES);
- // Re-setup the recents UI when gesture starts, as the state could have been changed during
- // that time by a previous window transition.
- mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_GESTURE_STARTED,
- this::setupRecentsViewUi);
+ mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
+ this::onLauncherPresentAndGestureStarted);
mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
this::initializeLauncherAnimationController);
@@ -294,9 +280,6 @@
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
this::launcherFrameDrawn);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
- this::notifyGestureStartedAsync);
-
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
| STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
@@ -304,16 +287,13 @@
mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
this::sendRemoteAnimationsToAnimationFactory);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_LAST_TASK,
- this::resumeLastTaskForQuickstep);
mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::resumeLastTask);
mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::startNewTask);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
- | STATE_ACTIVITY_MULTIPLIER_COMPLETE
- | STATE_CAPTURE_SCREENSHOT,
+ | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
this::switchToScreenshot);
mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
@@ -322,13 +302,13 @@
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_COMPLETED
| STATE_SCALED_CONTROLLER_HOME | STATE_APP_CONTROLLER_RECEIVED
- | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+ | STATE_LAUNCHER_DRAWN,
this::finishCurrentTransitionToHome);
mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
this::reset);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
- | STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS
+ | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
| STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
| STATE_GESTURE_STARTED,
this::setupLauncherUiAfterSwipeUpAnimation);
@@ -336,8 +316,7 @@
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED
- | STATE_SCALED_CONTROLLER_LAST_TASK,
+ mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
this::notifyTransitionCancelled);
mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
@@ -415,6 +394,7 @@
});
mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
+ mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
mStateCallback.setState(STATE_LAUNCHER_PRESENT);
@@ -423,6 +403,8 @@
} else {
activity.setOnStartCallback(this::onLauncherStart);
}
+
+ setupRecentsViewUi();
return true;
}
@@ -434,16 +416,19 @@
return;
}
- mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
- mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
+ // 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 (mGestureEndTarget != HOME) {
+ mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
+ mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
+ }
AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
if (mWasLauncherAlreadyVisible) {
- mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_DRAWN);
+ mStateCallback.setState(STATE_LAUNCHER_DRAWN);
} else {
TraceHelper.beginSection("WTS-init");
View dragLayer = activity.getDragLayer();
- mActivityControlHelper.getAlphaProperty(activity).setValue(0);
dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
@Override
@@ -460,11 +445,18 @@
});
}
- setupRecentsViewUi();
activity.getRootView().setOnApplyWindowInsetsListener(this);
mStateCallback.setState(STATE_LAUNCHER_STARTED);
}
+ private void onLauncherPresentAndGestureStarted() {
+ // Re-setup the recents UI when gesture starts, as the state could have been changed during
+ // that time by a previous window transition.
+ setupRecentsViewUi();
+
+ notifyGestureStartedAsync();
+ }
+
private void setupRecentsViewUi() {
if (mContinuingLastGesture) {
return;
@@ -476,25 +468,6 @@
}
private void launcherFrameDrawn() {
- AlphaProperty property = mActivityControlHelper.getAlphaProperty(mActivity);
- if (property.getValue() < 1) {
- if (mGestureStarted) {
- final MultiStateCallback callback = mStateCallback;
- ObjectAnimator animator = ObjectAnimator.ofFloat(
- property, MultiValueAlpha.VALUE, 1);
- animator.setDuration(getFadeInDuration()).addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- callback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
- }
- });
- animator.start();
- } else {
- property.setValue(1);
- mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
- }
- }
mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
}
@@ -706,15 +679,19 @@
}
}
+ /**
+ * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
+ * @param velocity The x and y components of the velocity when the gesture ends.
+ */
@UiThread
- public void onGestureEnded(float endVelocity, float velocityX) {
+ public void onGestureEnded(float endVelocity, PointF velocity) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
- handleNormalGestureEnd(endVelocity, isFling, velocityX);
+ handleNormalGestureEnd(endVelocity, isFling, velocity);
}
@UiThread
@@ -732,9 +709,8 @@
}
@UiThread
- private void handleNormalGestureEnd(float endVelocity, boolean isFling, float velocityX) {
- float velocityPxPerMs = endVelocity / 1000;
- float velocityXPxPerMs = velocityX / 1000;
+ private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity) {
+ PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
long duration = MAX_SWIPE_DURATION;
float currentShift = mCurrentShift.value;
final GestureEndTarget endTarget;
@@ -749,7 +725,7 @@
final int lastTaskIndex = mRecentsView.getTaskViewCount() - 1;
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
taskToLaunch = nextPage <= lastTaskIndex ? nextPage : lastTaskIndex;
- goingToNewTask = mRecentsView != null && taskToLaunch != runningTaskIndex;
+ goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
} else {
goingToNewTask = false;
}
@@ -779,7 +755,7 @@
} else {
if (SWIPE_HOME.get() && endVelocity < 0 && !mIsShelfPeeking) {
// If swiping at a diagonal, base end target on the faster velocity.
- endTarget = goingToNewTask && Math.abs(velocityX) > Math.abs(endVelocity)
+ endTarget = goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity)
? NEW_TASK : HOME;
} else if (endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold)) {
// If user scrolled to a new task, only go to recents if they already passed
@@ -789,14 +765,15 @@
endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
}
endShift = endTarget.endShift;
- startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
+ startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
* SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
float minFlingVelocity = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
if (endTarget == RECENTS) {
Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
- startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength);
+ startShift, endShift, endShift, velocityPxPerMs.y,
+ mTransitionDragLength);
endShift = overshoot.end;
interpolator = overshoot.interpolator;
duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
@@ -807,7 +784,7 @@
// 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));
+ long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
}
}
@@ -822,6 +799,7 @@
setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
} else if (endTarget == RECENTS) {
+ mLiveTileOverlay.startIconAnimation();
mRecentsAnimationWrapper.enableInputProxy();
if (mRecentsView != null) {
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
@@ -831,7 +809,7 @@
}
} else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
// Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
- // or resumeLastTaskForQuickstep().
+ // or resumeLastTask().
if (mRecentsView != null) {
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
}
@@ -863,14 +841,14 @@
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
@UiThread
private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
- GestureEndTarget target, float velocityPxPerMs) {
+ GestureEndTarget target, PointF velocityPxPerMs) {
mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
interpolator, target, velocityPxPerMs));
}
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
- Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) {
+ Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
mGestureEndTarget = target;
if (mGestureEndTarget.canBeContinued) {
@@ -883,9 +861,8 @@
RecentsModel.INSTANCE.get(mContext).endStabilizationSession();
}
- HomeAnimationFactory homeAnimFactory;
- Animator windowAnim;
if (mGestureEndTarget == HOME) {
+ HomeAnimationFactory homeAnimFactory;
if (mActivity != null) {
homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
} else {
@@ -900,27 +877,33 @@
@NonNull
@Override
- public Animator createActivityAnimationToHome() {
- return new AnimatorSet();
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
};
mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
}
- windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+ RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+ windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ setStateOnUiThread(target.endState);
+ }
+ });
+ windowAnim.start(velocityPxPerMs);
mLauncherTransitionController = null;
} else {
- windowAnim = mCurrentShift.animateToValue(start, end);
- homeAnimFactory = null;
+ Animator windowAnim = mCurrentShift.animateToValue(start, end);
+ windowAnim.setDuration(duration).setInterpolator(interpolator);
+ windowAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ setStateOnUiThread(target.endState);
+ }
+ });
+ windowAnim.start();
}
- windowAnim.setDuration(duration).setInterpolator(interpolator);
- windowAnim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- setStateOnUiThread(target.endState);
- }
- });
- windowAnim.start();
// Always play the entire launcher animation when going home, since it is separate from
// the animation that has been controlled thus far.
if (mGestureEndTarget == HOME) {
@@ -931,12 +914,6 @@
// interpolate over the remaining progress (end - start).
TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
interpolator, start, end);
- if (homeAnimFactory != null) {
- Animator homeAnim = homeAnimFactory.createActivityAnimationToHome();
- homeAnim.setDuration(duration).setInterpolator(adjustedInterpolator);
- homeAnim.start();
- mLauncherTransitionController = null;
- }
if (mLauncherTransitionController == null) {
return;
}
@@ -948,50 +925,41 @@
mLauncherTransitionController.getAnimationPlayer().setDuration(duration);
if (QUICKSTEP_SPRINGS.get()) {
- mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs);
+ mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
}
mLauncherTransitionController.getAnimationPlayer().start();
}
}
/**
- * Creates an Animator that transforms the current app window into the home app.
+ * 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.
*/
- private Animator createWindowAnimationToHome(float startProgress,
+ private RectFSpringAnim createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
- RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
+ final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
mTransformParams.setProgress(startProgress)));
- RectF originalTarget = new RectF(mClipAnimationHelper.getTargetRect());
- final RectF finalTarget = homeAnimationFactory.getWindowTargetRect();
-
- final RectFEvaluator rectFEvaluator = new RectFEvaluator();
- final RectF targetRect = new RectF();
- final RectF currentRect = new RectF();
+ final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
final View floatingView = homeAnimationFactory.getFloatingView();
final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect);
if (isFloatingIconView) {
- anim.addListener((FloatingIconView) floatingView);
+ anim.addAnimatorListener((FloatingIconView) floatingView);
}
+ AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
+
// 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 ? 0.75f : 1f;
- anim.addUpdateListener(animation -> {
- float progress = animation.getAnimatedFraction();
+ anim.addOnUpdateListener((currentRect, progress) -> {
float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress);
- // Initially go towards original target (task view in recents),
- // but accelerate towards the final target.
- // TODO: This is technically not correct. Instead, motion should continue at
- // the released velocity but accelerate towards the target.
- targetRect.set(rectFEvaluator.evaluate(interpolatedProgress,
- originalTarget, finalTarget));
- currentRect.set(rectFEvaluator.evaluate(interpolatedProgress, startRect, targetRect));
+
+ homeAnim.setPlayFraction(progress);
float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0,
windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR);
@@ -1003,10 +971,17 @@
((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress,
windowAlphaThreshold);
}
+
});
- anim.addListener(new AnimationSuccessListener() {
+ anim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ homeAnim.dispatchOnStart();
+ }
+
@Override
public void onAnimationSuccess(Animator animator) {
+ homeAnim.getAnimationPlayer().end();
if (mRecentsView != null) {
mRecentsView.post(mRecentsView::resetTaskVisuals);
}
@@ -1016,16 +991,11 @@
}
@UiThread
- private void resumeLastTaskForQuickstep() {
- setStateOnUiThread(STATE_RESUME_LAST_TASK);
- doLogGesture(LAST_TASK);
- reset();
- }
-
- @UiThread
private void resumeLastTask() {
mRecentsAnimationWrapper.finish(false /* toRecents */, null);
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
+ doLogGesture(LAST_TASK);
+ reset();
}
@UiThread
@@ -1033,18 +1003,15 @@
// 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.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
- result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
- mMainThreadHandler);
+ mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false);
} else {
mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
- mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
- result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
- mMainThreadHandler);
+ mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false);
});
}
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
doLogGesture(NEW_TASK);
+ reset();
}
public void reset() {
@@ -1072,7 +1039,6 @@
private void invalidateHandlerWithLauncher() {
mLauncherTransitionController = null;
- mActivityControlHelper.getAlphaProperty(mActivity).setValue(1);
mRecentsView.setEnableFreeScroll(true);
mRecentsView.setRunningTaskIconScaledDown(false);
@@ -1170,9 +1136,10 @@
mLauncherTransitionController = null;
}
mActivityControlHelper.onSwipeUpComplete(mActivity);
+ mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
// Animate the first icon.
- mRecentsView.animateUpRunningTaskIconScale();
+ mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation());
mRecentsView.setSwipeDownShouldLaunchApp(true);
RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
index 62f2183..94e704a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
@@ -38,9 +38,16 @@
*/
public class RecentsAnimationListenerSet implements RecentsAnimationListener {
+ // The actual app surface is replaced by a screenshot upon recents animation cancelation when
+ // deferredWithScreenshot is true. Launcher takes the responsibility to clean up this screenshot
+ // after app transition is finished. This delay is introduced to cover the app transition
+ // period of time.
+ private final int TRANSITION_DELAY = 100;
+
private final Set<SwipeAnimationListener> mListeners = new ArraySet<>();
private final boolean mShouldMinimizeSplitScreen;
private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
+ private RecentsAnimationControllerCompat mController;
public RecentsAnimationListenerSet(boolean shouldMinimizeSplitScreen,
Consumer<SwipeAnimationTargetSet> onFinishListener) {
@@ -64,6 +71,7 @@
public final void onAnimationStart(RecentsAnimationControllerCompat controller,
RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
Rect minimizedHomeBounds) {
+ mController = controller;
SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets,
homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
mOnFinishListener);
@@ -75,12 +83,17 @@
}
@Override
- public final void onAnimationCanceled() {
+ public final void onAnimationCanceled(boolean deferredWithScreenshot) {
Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
for (SwipeAnimationListener listener : getListeners()) {
listener.onRecentsAnimationCanceled();
}
});
+ // TODO: handle the transition better instead of simply using a transition delay.
+ if (deferredWithScreenshot) {
+ MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
+ TRANSITION_DELAY);
+ }
}
private SwipeAnimationListener[] getListeners() {
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
new file mode 100644
index 0000000..2edeb3a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.FlingSpringAnim;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+
+/**
+ * Applies spring forces to animate from a starting rect to a target rect,
+ * while providing update callbacks to the caller.
+ */
+public class RectFSpringAnim {
+
+ /**
+ * Although the rect position animation takes an indefinite amount of time since it depends on
+ * the initial velocity and applied forces, scaling from the starting rect to the target rect
+ * can be done in parallel at a fixed duration. Update callbacks are sent based on the progress
+ * of this animation, while the end callback is sent after all animations finish.
+ */
+ private static final long RECT_SCALE_DURATION = 180;
+
+ private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X =
+ new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") {
+ @Override
+ public float getValue(RectFSpringAnim anim) {
+ return anim.mCurrentCenterX;
+ }
+
+ @Override
+ public void setValue(RectFSpringAnim anim, float currentCenterX) {
+ anim.mCurrentCenterX = currentCenterX;
+ anim.onUpdate();
+ }
+ };
+
+ private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_Y =
+ new FloatPropertyCompat<RectFSpringAnim>("rectCenterYSpring") {
+ @Override
+ public float getValue(RectFSpringAnim anim) {
+ return anim.mCurrentCenterY;
+ }
+
+ @Override
+ public void setValue(RectFSpringAnim anim, float currentCenterY) {
+ anim.mCurrentCenterY = currentCenterY;
+ anim.onUpdate();
+ }
+ };
+
+ private static final FloatProperty<RectFSpringAnim> RECT_SCALE_PROGRESS =
+ new FloatProperty<RectFSpringAnim>("rectScaleProgress") {
+ @Override
+ public Float get(RectFSpringAnim anim) {
+ return anim.mCurrentScaleProgress;
+ }
+
+ @Override
+ public void setValue(RectFSpringAnim anim, float currentScaleProgress) {
+ anim.mCurrentScaleProgress = currentScaleProgress;
+ anim.onUpdate();
+ }
+ };
+
+ private final RectF mStartRect;
+ private final RectF mTargetRect;
+ private final RectF mCurrentRect = new RectF();
+ private final List<OnUpdateListener> mOnUpdateListeners = new ArrayList<>();
+ private final List<Animator.AnimatorListener> mAnimatorListeners = new ArrayList<>();
+
+ private float mCurrentCenterX;
+ private float mCurrentCenterY;
+ private float mCurrentScaleProgress;
+ private boolean mRectXAnimEnded;
+ private boolean mRectYAnimEnded;
+ private boolean mRectScaleAnimEnded;
+
+ public RectFSpringAnim(RectF startRect, RectF targetRect) {
+ mStartRect = startRect;
+ mTargetRect = targetRect;
+ mCurrentCenterX = mStartRect.centerX();
+ mCurrentCenterY = mStartRect.centerY();
+ }
+
+ public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
+ mOnUpdateListeners.add(onUpdateListener);
+ }
+
+ public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
+ mAnimatorListeners.add(animatorListener);
+ }
+
+ public void start(PointF velocityPxPerMs) {
+ // Only tell caller that we ended if both x and y animations have ended.
+ OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> {
+ mRectXAnimEnded = true;
+ maybeOnEnd();
+ });
+ OnAnimationEndListener onYEndListener = ((animation, canceled, centerY, velocityY) -> {
+ mRectYAnimEnded = true;
+ maybeOnEnd();
+ });
+ FlingSpringAnim rectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, mCurrentCenterX,
+ mTargetRect.centerX(), velocityPxPerMs.x * 1000, onXEndListener);
+ FlingSpringAnim rectYAnim = new FlingSpringAnim(this, RECT_CENTER_Y, mCurrentCenterY,
+ mTargetRect.centerY(), velocityPxPerMs.y * 1000, onYEndListener);
+
+ ValueAnimator rectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+ PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1))
+ .setDuration(RECT_SCALE_DURATION);
+ rectScaleAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mRectScaleAnimEnded = true;
+ maybeOnEnd();
+ }
+ });
+
+ rectXAnim.start();
+ rectYAnim.start();
+ rectScaleAnim.start();
+ for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
+ animatorListener.onAnimationStart(null);
+ }
+ }
+
+ private void onUpdate() {
+ if (!mOnUpdateListeners.isEmpty()) {
+ float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(),
+ mTargetRect.width());
+ float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(),
+ mTargetRect.height());
+ mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentCenterY - currentHeight / 2,
+ mCurrentCenterX + currentWidth / 2, mCurrentCenterY + currentHeight / 2);
+ for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
+ onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress);
+ }
+ }
+ }
+
+ private void maybeOnEnd() {
+ if (mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) {
+ for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
+ animatorListener.onAnimationEnd(null);
+ }
+ }
+ }
+
+ public interface OnUpdateListener {
+ void onUpdate(RectF currentRect, float progress);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 0aa1beb..446fb39 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -23,7 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
-import android.content.res.Resources;
+import android.content.pm.LauncherApps.AppUsageLimit;
import android.icu.text.MeasureFormat;
import android.icu.text.MeasureFormat.FormatWidth;
import android.icu.util.Measure;
@@ -44,16 +44,16 @@
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.systemui.shared.recents.model.Task;
-import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Locale;
public final class DigitalWellBeingToast extends LinearLayout {
static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
static final int MINUTE_MS = 60000;
+ private final LauncherApps mLauncherApps;
public interface InitializeCallback {
- void call(float saturation, String contentDescription);
+ void call(String contentDescription);
}
private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
@@ -67,6 +67,7 @@
setLayoutDirection(Utilities.isRtl(getResources()) ?
View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
setOnClickListener((view) -> openAppUsageSettings());
+ mLauncherApps = context.getSystemService(LauncherApps.class);
}
@Override
@@ -82,52 +83,32 @@
if (task.key.userId != UserHandle.myUserId()) {
setVisibility(GONE);
- callback.call(1, task.titleDescription);
+ callback.call(task.titleDescription);
return;
}
Utilities.THREAD_POOL_EXECUTOR.execute(() -> {
- long appUsageLimitTimeMs = -1;
- long appRemainingTimeMs = -1;
+ final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit(
+ task.getTopComponent().getPackageName(),
+ UserHandle.of(task.key.userId));
- try {
- final Method getAppUsageLimit = LauncherApps.class.getMethod(
- "getAppUsageLimit",
- String.class,
- UserHandle.class);
- final Object usageLimit = getAppUsageLimit.invoke(
- getContext().getSystemService(LauncherApps.class),
- task.getTopComponent().getPackageName(),
- UserHandle.of(task.key.userId));
-
- if (usageLimit != null) {
- final Class appUsageLimitClass = usageLimit.getClass();
- appUsageLimitTimeMs = (long) appUsageLimitClass.getMethod("getTotalUsageLimit").
- invoke(usageLimit);
- appRemainingTimeMs = (long) appUsageLimitClass.getMethod("getUsageRemaining").
- invoke(usageLimit);
- }
- } catch (Exception e) {
- // Do nothing
- }
-
- final long appUsageLimitTimeMsFinal = appUsageLimitTimeMs;
- final long appRemainingTimeMsFinal = appRemainingTimeMs;
+ final long appUsageLimitTimeMs =
+ usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
+ final long appRemainingTimeMs =
+ usageLimit != null ? usageLimit.getUsageRemaining() : -1;
post(() -> {
- if (appUsageLimitTimeMsFinal < 0) {
+ if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
setVisibility(GONE);
} else {
setVisibility(VISIBLE);
- mText.setText(getText(appRemainingTimeMsFinal));
- mImage.setImageResource(appRemainingTimeMsFinal > 0 ?
+ mText.setText(getText(appRemainingTimeMs));
+ mImage.setImageResource(appRemainingTimeMs > 0 ?
R.drawable.hourglass_top : R.drawable.hourglass_bottom);
}
- callback.call(
- appUsageLimitTimeMsFinal >= 0 && appRemainingTimeMsFinal <= 0 ? 0 : 1,
- getContentDescriptionForTask(
- task, appUsageLimitTimeMsFinal, appRemainingTimeMsFinal));
+ callback.call(getContentDescriptionForTask(
+ task, appUsageLimitTimeMs, appRemainingTimeMs));
});
});
}
@@ -196,12 +177,9 @@
}
private String getText(long remainingTime) {
- final Resources resources = getResources();
- return (remainingTime <= 0) ?
- resources.getString(R.string.app_in_grayscale) :
- resources.getString(
- R.string.time_left_for_app,
- getRoundedUpToMinuteReadableDuration(remainingTime));
+ return getResources().getString(
+ R.string.time_left_for_app,
+ getRoundedUpToMinuteReadableDuration(remainingTime));
}
public void openAppUsageSettings() {
@@ -225,7 +203,7 @@
private String getContentDescriptionForTask(
Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) {
- return appUsageLimitTimeMs >= 0 ?
+ return appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0 ?
getResources().getString(
R.string.task_contents_description_with_remaining_time,
task.titleDescription,
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 97bce5e..8da1d2b 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
@@ -56,27 +56,6 @@
@TargetApi(Build.VERSION_CODES.O)
public class LauncherRecentsView extends RecentsView<Launcher> {
- public static final FloatProperty<LauncherRecentsView> TRANSLATION_Y_FACTOR =
- new FloatProperty<LauncherRecentsView>("translationYFactor") {
-
- @Override
- public void setValue(LauncherRecentsView view, float v) {
- view.setTranslationYFactor(v);
- }
-
- @Override
- public Float get(LauncherRecentsView view) {
- return view.mTranslationYFactor;
- }
- };
-
- /**
- * A ratio representing the view's relative placement within its padded space. For example, 0
- * is top aligned and 0.5 is centered vertically.
- */
- @ViewDebug.ExportedProperty(category = "launcher")
- private float mTranslationYFactor;
-
private final TransformParams mTransformParams = new TransformParams();
private ChipsContainer mChipsContainer;
@@ -95,18 +74,7 @@
@Override
public void startHome() {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- takeScreenshotAndFinishRecentsAnimation(true,
- () -> mActivity.getStateManager().goToState(NORMAL));
- } else {
- mActivity.getStateManager().goToState(NORMAL);
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- setTranslationYFactor(mTranslationYFactor);
+ mActivity.getStateManager().goToState(NORMAL);
}
@Override
@@ -117,9 +85,9 @@
params.bottomMargin = mActivity.getDeviceProfile().chipHintBottomMarginPx;
}
- public void setTranslationYFactor(float translationFactor) {
- mTranslationYFactor = translationFactor;
- setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
+ @Override
+ public void setTranslationY(float translationY) {
+ super.setTranslationY(translationY);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
LauncherState state = mActivity.getStateManager().getState();
if (state == OVERVIEW || state == ALL_APPS) {
@@ -128,10 +96,6 @@
}
}
- public float computeTranslationYForFactor(float translationYFactor) {
- return translationYFactor * (getPaddingBottom() - getPaddingTop());
- }
-
public void setHintVisibility(float v) {
if (mChipsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) {
mChipsContainer.setHintVisibility(v);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
index ab2b90f..a838797 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -1,5 +1,11 @@
package com.android.quickstep.views;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
@@ -9,16 +15,37 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+
+import com.android.launcher3.anim.Interpolators;
public class LiveTileOverlay extends Drawable {
+ private static final long ICON_ANIM_DURATION = 120;
+
+ private static final FloatProperty<LiveTileOverlay> PROGRESS =
+ new FloatProperty<LiveTileOverlay>("progress") {
+ @Override
+ public void setValue(LiveTileOverlay liveTileOverlay, float progress) {
+ liveTileOverlay.setIconAnimationProgress(progress);
+ }
+
+ @Override
+ public Float get(LiveTileOverlay liveTileOverlay) {
+ return liveTileOverlay.mIconAnimationProgress;
+ }
+ };
+
private final Paint mPaint = new Paint();
private Rect mBoundsRect = new Rect();
private RectF mCurrentRect;
private float mCornerRadius;
+ private Drawable mIcon;
+ private Animator mIconAnimator;
private boolean mDrawEnabled = true;
+ private float mIconAnimationProgress = 0f;
public LiveTileOverlay() {
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
@@ -35,6 +62,33 @@
invalidateSelf();
}
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
+ }
+
+ public void startIconAnimation() {
+ if (mIconAnimator != null) {
+ mIconAnimator.cancel();
+ }
+ // This animator must match the icon part of {@link TaskView#FOCUS_TRANSITION} animation.
+ mIconAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 1);
+ mIconAnimator.setDuration(ICON_ANIM_DURATION).setInterpolator(LINEAR);
+ mIconAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIconAnimator = null;
+ }
+ });
+ mIconAnimator.start();
+ }
+
+ public float cancelIconAnimation() {
+ if (mIconAnimator != null) {
+ mIconAnimator.cancel();
+ }
+ return mIconAnimationProgress;
+ }
+
public void setDrawEnabled(boolean drawEnabled) {
if (mDrawEnabled != drawEnabled) {
mDrawEnabled = drawEnabled;
@@ -46,6 +100,16 @@
public void draw(Canvas canvas) {
if (mCurrentRect != null && mDrawEnabled) {
canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+ if (mIcon != null && mIconAnimationProgress > 0f) {
+ canvas.save();
+ float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
+ 1f).getInterpolation(mIconAnimationProgress);
+ canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
+ mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
+ canvas.scale(scale, scale);
+ mIcon.draw(canvas);
+ canvas.restore();
+ }
}
}
@@ -59,4 +123,9 @@
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
+
+ private void setIconAnimationProgress(float progress) {
+ mIconAnimationProgress = progress;
+ invalidateSelf();
+ }
}
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 3e0e8ae..a02df62 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
@@ -22,13 +22,14 @@
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.QUICKSTEP_SPRINGS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.uioverrides.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
-import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
-import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
@@ -81,6 +82,7 @@
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.config.FeatureFlags;
+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.OverScroller;
@@ -94,16 +96,15 @@
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.util.TaskViewDrawable;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.BackgroundExecutor;
+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 com.android.systemui.shared.system.WindowCallbacksCompat;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -167,6 +168,8 @@
private final ViewPool<TaskView> mTaskViewPool;
+ private boolean mDwbToastShown;
+
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
@@ -278,6 +281,7 @@
private final int mEmptyMessagePadding;
private boolean mShowEmptyMessage;
private Layout mEmptyTextLayout;
+ private LiveTileOverlay mLiveTileOverlay;
private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
(inMultiWindowMode) -> {
@@ -426,6 +430,16 @@
public void setOverviewStateEnabled(boolean enabled) {
mOverviewStateEnabled = enabled;
updateTaskStackListenerState();
+ if (!enabled) mDwbToastShown = false;
+ }
+
+ public void onDigitalWellbeingToastShown() {
+ if (!mDwbToastShown) {
+ mDwbToastShown = true;
+ mActivity.getUserEventDispatcher().logActionTip(
+ LauncherEventUtil.VISIBLE,
+ LauncherLogProto.TipType.DWB_TOAST);
+ }
}
@Override
@@ -855,10 +869,15 @@
}
public void animateUpRunningTaskIconScale() {
+ animateUpRunningTaskIconScale(0);
+ }
+
+ public void animateUpRunningTaskIconScale(float startProgress) {
mRunningTaskIconScaledDown = false;
TaskView firstTask = getRunningTaskView();
if (firstTask != null) {
firstTask.animateIconScaleAndDimIntoView();
+ firstTask.setIconScaleAnimStartProgress(startProgress);
}
}
@@ -1567,6 +1586,16 @@
mClipAnimationHelper = clipAnimationHelper;
}
+ public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) {
+ mLiveTileOverlay = liveTileOverlay;
+ }
+
+ public void updateLiveTileIcon(Drawable icon) {
+ if (mLiveTileOverlay != null) {
+ mLiveTileOverlay.setIcon(icon);
+ }
+ }
+
public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
if (mRecentsAnimationWrapper == null) {
if (onFinishComplete != null) {
@@ -1577,50 +1606,4 @@
mRecentsAnimationWrapper.finish(toRecents, onFinishComplete);
}
-
- public void takeScreenshotAndFinishRecentsAnimation(boolean toRecents,
- Runnable onFinishComplete) {
- if (mRecentsAnimationWrapper == null || getRunningTaskView() == null) {
- if (onFinishComplete != null) {
- onFinishComplete.run();
- }
- return;
- }
-
- SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
- if (controller != null) {
- // Update the screenshot of the task
- ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId);
- TaskView taskView = updateThumbnail(mRunningTaskId, taskSnapshot);
- if (taskView != null) {
- taskView.setShowScreenshot(true);
- // Defer finishing the animation until the next launcher frame with the
- // new thumbnail
- new WindowCallbacksCompat(taskView) {
-
- // The number of frames to defer until we actually finish the animation
- private int mDeferFrameCount = 2;
-
- @Override
- public void onPostDraw(Canvas canvas) {
- if (mDeferFrameCount > 0) {
- mDeferFrameCount--;
- // Workaround, detach and reattach to invalidate the root node for
- // another draw
- detach();
- attach();
- taskView.invalidate();
- return;
- }
-
- detach();
- mRecentsAnimationWrapper.finish(toRecents, () -> {
- onFinishComplete.run();
- mRunningTaskId = -1;
- });
- }
- }.attach();
- }
- }
- }
}
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 682152e..d15a392 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
@@ -208,13 +208,7 @@
R.layout.task_view_menu_option, this, false);
menuOption.setIconAndLabelFor(
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- menuOptionView.setOnClickListener(
- view -> mTaskView.getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
- () -> onClickListener.onClick(view)));
- } else {
- menuOptionView.setOnClickListener(onClickListener);
- }
+ menuOptionView.setOnClickListener(onClickListener);
mOptionLayout.addView(menuOptionView);
}
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 e5b1a79..3889fd3 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
@@ -155,7 +155,8 @@
private float mZoomScale;
private float mFullscreenProgress;
- private Animator mIconAndDimAnimator;
+ private ObjectAnimator mIconAndDimAnimator;
+ private float mIconScaleAnimStartProgress = 0;
private float mFocusTransitionProgress = 1;
private boolean mShowScreenshot;
@@ -259,11 +260,10 @@
Handler resultCallbackHandler) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (isRunningTask()) {
- getRecentsView().finishRecentsAnimation(false,
+ getRecentsView().finishRecentsAnimation(false /* toRecents */,
() -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
} else {
- getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
- () -> launchTaskInternal(animate, resultCallback, resultCallbackHandler));
+ launchTaskInternal(animate, resultCallback, resultCallbackHandler);
}
} else {
launchTaskInternal(animate, resultCallback, resultCallbackHandler);
@@ -316,11 +316,16 @@
mIconLoadRequest = iconCache.updateIconInBackground(mTask,
(task) -> {
setIcon(task.icon);
+ if (isRunningTask()) {
+ getRecentsView().updateLiveTileIcon(task.icon);
+ }
mDigitalWellBeingToast.initialize(
mTask,
- (saturation, contentDescription) -> {
+ contentDescription -> {
setContentDescription(contentDescription);
- mSnapshotView.setSaturation(saturation);
+ if (mDigitalWellBeingToast.getVisibility() == VISIBLE) {
+ getRecentsView().onDigitalWellbeingToastShown();
+ }
});
});
} else {
@@ -379,11 +384,16 @@
mIconView.setScaleY(scale);
}
+ public void setIconScaleAnimStartProgress(float startProgress) {
+ mIconScaleAnimStartProgress = startProgress;
+ }
+
public void animateIconScaleAndDimIntoView() {
if (mIconAndDimAnimator != null) {
mIconAndDimAnimator.cancel();
}
mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1);
+ mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress);
mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR);
mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() {
@Override
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 3fbfcdd..a966698 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -26,4 +26,8 @@
determines how many thumbnails will be fetched in the background. -->
<integer name="recentsThumbnailCacheSize">3</integer>
<integer name="recentsIconCacheSize">12</integer>
+
+ <!-- Assistant Gesture -->
+ <integer name="assistant_gesture_min_time_threshold">200</integer>
+ <integer name="assistant_gesture_corner_deg_threshold">30</integer>
</resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index f5e5dd3..9c97c8c 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -66,6 +66,6 @@
<dimen name="shelf_surface_offset">24dp</dimen>
<!-- Assistant Gestures -->
- <dimen name="gestures_assistant_width">70dp</dimen>
- <dimen name="gestures_assistant_threshold">200dp</dimen>
+ <dimen name="gestures_assistant_size">28dp</dimen>
+ <dimen name="gestures_assistant_drag_threshold">70dp</dimen>
</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index f5e8fa8..81565a5 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -55,10 +55,6 @@
escaped form of '<'). [CHAR LIMIT=15] -->
<string name="shorter_duration_less_than_one_minute">< 1 minute</string>
- <!-- Annotation shown on an app card in Recents, telling that the app was switched to a
- grayscale because it ran over its time limit [CHAR LIMIT=25] -->
- <string name="app_in_grayscale">App in grayscale</string>
-
<!-- Annotation shown on an app card in Recents, telling that the app has a usage limit set by
the user, and a given time is left for it today [CHAR LIMIT=22] -->
<string name="time_left_for_app"><xliff:g id="time" example="7 minutes">%1$s</xliff:g> left today</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index f8b167b..c5475d6 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -559,7 +559,8 @@
float transY0 = floatingViewBounds[1] - offsetY;
float windowRadius = 0;
- if (RecentsModel.INSTANCE.get(mLauncher).supportsRoundedCornersOnWindows()) {
+ if (!mDeviceProfile.isMultiWindowMode &&
+ RecentsModel.INSTANCE.get(mLauncher).supportsRoundedCornersOnWindows()) {
windowRadius = RecentsModel.INSTANCE.get(mLauncher)
.getWindowCornerRadius();
}
@@ -700,7 +701,8 @@
new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
- float cornerRadius = RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
+ float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
+ RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
unlockAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -726,7 +728,8 @@
Matrix matrix = new Matrix();
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
int duration = CLOSING_TRANSITION_DURATION_MS;
- float windowCornerRadius = RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
+ float windowCornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
+ RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
closingAnimator.setDuration(duration);
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 1eaa8bc..5ae562e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -78,8 +78,9 @@
}
@Override
- public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
- return new float[] {0.9f, -0.2f};
+ public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
+ float slightParallax = -launcher.getDeviceProfile().allAppsCellHeightPx * 0.3f;
+ return new float[] {0.9f, slightParallax};
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index df9dbe4..e74d84d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -26,8 +26,6 @@
import android.view.View;
import android.view.animation.Interpolator;
-import androidx.annotation.NonNull;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -35,6 +33,8 @@
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
+import androidx.annotation.NonNull;
+
/**
* State handler for recents view. Manages UI changes and animations for recents view based off the
* current {@link LauncherState}.
@@ -53,9 +53,9 @@
@Override
public void setState(@NonNull LauncherState state) {
- float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
- SCALE_PROPERTY.set(mRecentsView, scaleTranslationYFactor[0]);
- getTranslationYFactorProperty().set(mRecentsView, scaleTranslationYFactor[1]);
+ float[] scaleTranslationY = state.getOverviewScaleAndTranslationY(mLauncher);
+ SCALE_PROPERTY.set(mRecentsView, scaleTranslationY[0]);
+ mRecentsView.setTranslationY(scaleTranslationY[1]);
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
}
@@ -79,11 +79,11 @@
void setStateWithAnimationInternal(@NonNull final LauncherState toState,
@NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
PropertySetter setter = config.getPropertySetter(builder);
- float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
+ float[] scaleTranslationY = toState.getOverviewScaleAndTranslationY(mLauncher);
Interpolator scaleAndTransYInterpolator = getScaleAndTransYInterpolator(toState, builder);
- setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationYFactor[0],
+ setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationY[0],
scaleAndTransYInterpolator);
- setter.setFloat(mRecentsView, getTranslationYFactorProperty(), scaleTranslationYFactor[1],
+ setter.setFloat(mRecentsView, View.TRANSLATION_Y, scaleTranslationY[1],
scaleAndTransYInterpolator);
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
@@ -102,13 +102,6 @@
}
/**
- * Get property for translation Y factor for the recents view.
- *
- * @return the float property for the recents view
- */
- abstract FloatProperty getTranslationYFactorProperty();
-
- /**
* Get property for content alpha for the recents view.
*
* @return the float property for the view's content alpha
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
index fd4bf9b..086cbdb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
@@ -2,7 +2,7 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import android.view.MotionEvent;
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index eccef04..418f7f4 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep;
-import android.animation.Animator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
@@ -31,7 +30,6 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -85,8 +83,6 @@
return true;
}
- AlphaProperty getAlphaProperty(T activity);
-
/**
* Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
*/
@@ -135,6 +131,6 @@
@NonNull RectF getWindowTargetRect();
- @NonNull Animator createActivityAnimationToHome();
+ @NonNull AnimatorPlaybackController createActivityAnimationToHome();
}
}
diff --git a/quickstep/src/com/android/quickstep/NavBarModeOverlayResourceObserver.java b/quickstep/src/com/android/quickstep/NavBarModeOverlayResourceObserver.java
new file mode 100644
index 0000000..d44c253
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/NavBarModeOverlayResourceObserver.java
@@ -0,0 +1,85 @@
+/*
+ * 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.Intent.ACTION_OVERLAY_CHANGED;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.util.Log;
+
+import com.android.systemui.shared.system.QuickStepContract;
+
+/**
+ * Observer for the resource config that specifies the navigation bar mode.
+ */
+public class NavBarModeOverlayResourceObserver extends BroadcastReceiver {
+
+ private static final String TAG = "NavBarModeOverlayResourceObserver";
+
+ private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
+ "config_navBarInteractionMode";
+
+ private final Context mContext;
+ private final OnChangeListener mOnChangeListener;
+
+ public NavBarModeOverlayResourceObserver(Context context, OnChangeListener listener) {
+ mContext = context;
+ mOnChangeListener = listener;
+ }
+
+ public void register() {
+ IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(this, filter);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mOnChangeListener.onNavBarModeChanged(getSystemIntegerRes(context,
+ NAV_BAR_INTERACTION_MODE_RES_NAME));
+ }
+
+ public interface OnChangeListener {
+ void onNavBarModeChanged(int mode);
+ }
+
+ public static boolean isSwipeUpModeEnabled(Context context) {
+ return QuickStepContract.isSwipeUpMode(getSystemIntegerRes(context,
+ NAV_BAR_INTERACTION_MODE_RES_NAME));
+ }
+
+ public static boolean isEdgeToEdgeModeEnabled(Context context) {
+ return QuickStepContract.isGesturalMode(getSystemIntegerRes(context,
+ NAV_BAR_INTERACTION_MODE_RES_NAME));
+ }
+
+ private static int getSystemIntegerRes(Context context, String resName) {
+ Resources res = context.getResources();
+ int resId = res.getIdentifier(resName, "integer", "android");
+
+ if (resId != 0) {
+ return res.getInteger(resId);
+ } else {
+ Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+ return -1;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index a0ab301..903701d 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep;
-import static com.android.quickstep.SwipeUpSetting.newSwipeUpSettingsObserver;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
@@ -28,10 +27,11 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SecureSettingsObserver;
import com.android.launcher3.util.UiThreadHelper;
import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.system.QuickStepContract;
import androidx.annotation.WorkerThread;
@@ -58,7 +58,8 @@
private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
private static final int MSG_SET_SWIPE_UP_ENABLED = 202;
- private final SecureSettingsObserver mSwipeUpSettingObserver;
+ // TODO: Discriminate between swipe up and edge to edge
+ private final NavBarModeOverlayResourceObserver mSwipeUpSettingObserver;
private final Context mContext;
private final Handler mUiHandler;
@@ -66,7 +67,7 @@
// These are updated on the background thread
private ISystemUiProxy mISystemUiProxy;
- private boolean mSwipeUpEnabled = true;
+ private boolean mSwipeUpEnabled;
private float mBackButtonAlpha = 1;
private Runnable mOnSwipeUpSettingChangedListener;
@@ -80,15 +81,15 @@
mUiHandler = new Handler(this::handleUiMessage);
mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
- if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
- mSwipeUpSettingObserver =
- newSwipeUpSettingsObserver(context, this::notifySwipeUpSettingChanged);
+ mSwipeUpEnabled = NavBarModeOverlayResourceObserver.isSwipeUpModeEnabled(mContext)
+ || NavBarModeOverlayResourceObserver.isEdgeToEdgeModeEnabled(mContext);
+ if (SwipeUpSetting.isSystemNavigationSettingAvailable()) {
+ mSwipeUpSettingObserver = new NavBarModeOverlayResourceObserver(context,
+ this::notifySwipeUpSettingChanged);
mSwipeUpSettingObserver.register();
- mSwipeUpEnabled = mSwipeUpSettingObserver.getValue();
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
} else {
mSwipeUpSettingObserver = null;
- mSwipeUpEnabled = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
}
}
@@ -175,7 +176,13 @@
}
}
- private void notifySwipeUpSettingChanged(boolean swipeUpEnabled) {
+ private void notifySwipeUpSettingChanged(int mode) {
+ boolean swipeUpEnabled = !QuickStepContract.isLegacyMode(mode);
+ boolean gesturalEnabled = QuickStepContract.isGesturalMode(mode);
+
+ FeatureFlags.SWIPE_HOME.updateStorage(mContext, gesturalEnabled);
+ FeatureFlags.ENABLE_ASSISTANT_GESTURE.updateStorage(mContext, gesturalEnabled);
+
mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, swipeUpEnabled ? 1 : 0, 0).
sendToTarget();
diff --git a/quickstep/src/com/android/quickstep/SwipeUpSetting.java b/quickstep/src/com/android/quickstep/SwipeUpSetting.java
index 381ab9f..7f830f9 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpSetting.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpSetting.java
@@ -16,23 +16,18 @@
package com.android.quickstep;
-import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
-
-import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
-import com.android.launcher3.util.SecureSettingsObserver;
-import com.android.launcher3.util.SecureSettingsObserver.OnChangeListener;
-
public final class SwipeUpSetting {
private static final String TAG = "SwipeUpSetting";
private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
"config_swipe_up_gesture_setting_available";
- private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
- "config_swipe_up_gesture_default";
+ public static boolean isSystemNavigationSettingAvailable() {
+ return getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME);
+ }
private static boolean getSystemBooleanRes(String resName) {
Resources res = Resources.getSystem();
@@ -45,18 +40,4 @@
return false;
}
}
-
- public static boolean isSwipeUpSettingAvailable() {
- return getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME);
- }
-
- public static boolean isSwipeUpEnabledDefaultValue() {
- return getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
- }
-
- public static SecureSettingsObserver newSwipeUpSettingsObserver(Context context,
- OnChangeListener listener) {
- return new SecureSettingsObserver(context.getContentResolver(), listener,
- SWIPE_UP_SETTING_NAME, isSwipeUpEnabledDefaultValue() ? 1 : 0);
- }
}
diff --git a/quickstep/src/com/android/quickstep/TestInformationProvider.java b/quickstep/src/com/android/quickstep/TestInformationProvider.java
new file mode 100644
index 0000000..e57d3ec
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TestInformationProvider.java
@@ -0,0 +1,119 @@
+/*
+ * 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.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.TestProtocol;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.uioverrides.OverviewState;
+import com.android.quickstep.util.LayoutUtils;
+
+public class TestInformationProvider extends ContentProvider {
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
+ return 0;
+ }
+
+ @Override
+ public int delete(Uri uri, String s, String[] strings) {
+ return 0;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues contentValues) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
+ return null;
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ final Bundle response = new Bundle();
+ final Context context = getContext();
+ final DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.
+ get(context).getDeviceProfile(context);
+ final LauncherAppState launcherAppState = LauncherAppState.getInstanceNoCreate();
+ final Launcher launcher = launcherAppState != null ?
+ (Launcher) launcherAppState.getModel().getCallback() : null;
+
+ switch (method) {
+ case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
+ final float swipeHeight =
+ OverviewState.getDefaultSwipeHeight(deviceProfile);
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
+ break;
+ }
+
+ case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
+ final float swipeHeight =
+ LayoutUtils.getShelfTrackingDistance(context, deviceProfile);
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
+ break;
+ }
+
+ case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
+ if (launcher == null) return null;
+
+ final float progress = LauncherState.OVERVIEW.getVerticalProgress(launcher)
+ - LauncherState.ALL_APPS.getVerticalProgress(launcher);
+ final float distance =
+ launcher.getAllAppsController().getShiftRange() * progress;
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
+ break;
+ }
+
+ case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
+ if (launcher == null) return null;
+
+ final float progress = LauncherState.NORMAL.getVerticalProgress(launcher)
+ - LauncherState.ALL_APPS.getVerticalProgress(launcher);
+ final float distance =
+ launcher.getAllAppsController().getShiftRange() * progress;
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
+ break;
+ }
+ }
+ return response;
+ }
+ return null;
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java b/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
index f89842a..12bd0ca 100644
--- a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
+++ b/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
@@ -16,25 +16,29 @@
package com.android.quickstep;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
import static com.android.quickstep.QuickStepOnOffRule.Mode.BOTH;
import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF;
import static com.android.quickstep.QuickStepOnOffRule.Mode.ON;
-import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
+import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_2BUTTON_OVERLAY;
+import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_3BUTTON_OVERLAY;
+import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
-import static org.junit.Assert.assertTrue;
-
-import android.provider.Settings;
+import android.content.Context;
import android.util.Log;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.TestHelpers;
+import com.android.systemui.shared.system.QuickStepContract;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
+import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -76,53 +80,59 @@
return new Statement() {
@Override
public void evaluate() throws Throwable {
- if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
- try {
- if (mode == ON || mode == BOTH) {
- evaluateWithQuickstepOn();
- }
- if (mode == OFF || mode == BOTH) {
- evaluateWithQuickstepOff();
- }
- } finally {
- setSwipeUpSetting(null);
+ final Context context = getInstrumentation().getContext();
+ final String prevOverlayPkg = QuickStepContract.isGesturalMode(context)
+ ? NAV_BAR_MODE_GESTURAL_OVERLAY
+ : QuickStepContract.isSwipeUpMode(context)
+ ? NAV_BAR_MODE_2BUTTON_OVERLAY
+ : NAV_BAR_MODE_3BUTTON_OVERLAY;
+ try {
+ if (mode == ON || mode == BOTH) {
+ evaluateWithQuickstepOn();
}
- } else {
- // Execute without changing the setting, if the requested mode is
- // compatible.
- final boolean swipeUpEnabledDefaultValue =
- SwipeUpSetting.isSwipeUpEnabledDefaultValue();
- if (mode == BOTH ||
- mode == ON && swipeUpEnabledDefaultValue ||
- mode == OFF && !swipeUpEnabledDefaultValue) {
- evaluateWithoutChangingSetting(base);
+ if (mode == OFF || mode == BOTH) {
+ evaluateWithQuickstepOff();
}
+ } finally {
+ setActiveOverlay(prevOverlayPkg);
}
}
- public void setSwipeUpSetting(String value) {
- Log.d(TAG, "setSwipeUpSetting: " + value);
- assertTrue("Couldn't change Quickstep mode",
- Settings.Secure.putString(
- InstrumentationRegistry.getInstrumentation().getTargetContext().
- getContentResolver(),
- SWIPE_UP_SETTING_NAME,
- value));
- }
-
public void evaluateWithoutChangingSetting(Statement base) throws Throwable {
base.evaluate();
}
private void evaluateWithQuickstepOff() throws Throwable {
- setSwipeUpSetting("0");
+ setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY);
evaluateWithoutChangingSetting(base);
}
private void evaluateWithQuickstepOn() throws Throwable {
- setSwipeUpSetting("1");
+ setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY);
base.evaluate();
}
+
+ private void setActiveOverlay(String overlayPackage) {
+ setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
+ overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
+ setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
+ overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
+ setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
+ overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
+
+ // TODO: Wait until nav bar mode has applied
+ }
+
+ private void setOverlayPackageEnabled(String overlayPackage, boolean enable) {
+ Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
+ final String action = enable ? "enable" : "disable";
+ try {
+ UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+ "cmd overlay " + action + " " + overlayPackage);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
};
} else {
return base;
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index fe789aa..6031dcd 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -37,6 +37,9 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
+
+ static final int STRESS_REPEAT_COUNT = 10;
+
@Override
@Before
public void setUp() throws Exception {
@@ -79,4 +82,28 @@
closeLauncherActivity();
mLauncher.getBackground().switchToOverview();
}
+
+ @Test
+ @QuickstepOnOff
+ public void testStressPressHome() {
+ for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
+ // Destroy Launcher activity.
+ closeLauncherActivity();
+
+ // The test action.
+ mLauncher.pressHome();
+ }
+ }
+
+ @Test
+ @QuickstepOnOff
+ public void testStressSwipeToOverview() {
+ for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
+ // Destroy Launcher activity.
+ closeLauncherActivity();
+
+ // The test action.
+ mLauncher.getBackground().switchToOverview();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index d96855e..d75006e 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -276,14 +276,6 @@
}
@Override
- public void setTag(Object tag) {
- if (tag != null) {
- LauncherModel.checkItemInfo((ItemInfo) tag);
- }
- super.setTag(tag);
- }
-
- @Override
public void refreshDrawableState() {
if (!mIgnorePressedStateChange) {
super.refreshDrawableState();
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 3b054c2..7919d29 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -133,6 +133,7 @@
private final Rect mInsets = new Rect();
public final Rect workspacePadding = new Rect();
private final Rect mHotseatPadding = new Rect();
+ // When true, nav bar is on the left side of the screen.
private boolean mIsSeascape;
// Notification dots
@@ -587,28 +588,40 @@
/**
* Gets an item's location on the home screen. This is useful if the home screen
* is animating, otherwise use {@link View#getLocationOnScreen(int[])}.
- *
- * TODO(b/123900446): Handle landscape mode
* @param pageDiff The page difference relative to the current page.
*/
public void getItemLocation(int cellX, int cellY, int spanX, int spanY, int container,
int pageDiff, Rect outBounds) {
outBounds.setEmpty();
- outBounds.left = mInsets.left
- + workspacePadding.left + cellLayoutPaddingLeftRightPx + (cellX * getCellSize().x);
- outBounds.top = mInsets.top;
if (container == CONTAINER_HOTSEAT) {
- outBounds.top += workspacePadding.top
- + (inv.numRows * getCellSize().y)
- + verticalDragHandleSizePx
- - verticalDragHandleOverlapWorkspace;
- outBounds.bottom = outBounds.top + hotseatBarSizePx - hotseatBarBottomPaddingPx;
+ final int actualHotseatCellHeight;
+ if (isVerticalBarLayout()) {
+ actualHotseatCellHeight = availableHeightPx / inv.numRows;
+ if (mIsSeascape) {
+ outBounds.left = mHotseatPadding.left;
+ } else {
+ outBounds.left = availableWidthPx - hotseatBarSizePx + mHotseatPadding.left;
+ }
+ outBounds.right = outBounds.left + iconSizePx;
+ outBounds.top = mHotseatPadding.top
+ + actualHotseatCellHeight * (inv.numRows - cellX - 1);
+ outBounds.bottom = outBounds.top + actualHotseatCellHeight;
+ } else {
+ actualHotseatCellHeight = hotseatBarSizePx - hotseatBarBottomPaddingPx
+ - hotseatBarTopPaddingPx;
+ outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
+ + (cellX * getCellSize().x);
+ outBounds.right = outBounds.left + getCellSize().x;
+ outBounds.top = mInsets.top + availableHeightPx - hotseatBarSizePx;
+ outBounds.bottom = outBounds.top + actualHotseatCellHeight;
+ }
} else {
- outBounds.top += workspacePadding.top + (cellY * getCellSize().y);
+ outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
+ + (cellX * getCellSize().x) + (pageDiff * availableWidthPx);
+ outBounds.right = outBounds.left + (getCellSize().x * spanX);
+ outBounds.top = mInsets.top + workspacePadding.top + (cellY * getCellSize().y);
outBounds.bottom = outBounds.top + (getCellSize().y * spanY);
- outBounds.left += (pageDiff) * availableWidthPx;
}
- outBounds.right = outBounds.left + (getCellSize().x * spanX);
}
public float getAspectRatioWithInsets() {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 6582df2..f081303 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -20,7 +20,10 @@
import static com.android.launcher3.Utilities.getDevicePrefs;
import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -114,6 +117,7 @@
private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
private ConfigMonitor mConfigMonitor;
+ private OverlayMonitor mOverlayMonitor;
@VisibleForTesting
public InvariantDeviceProfile() {}
@@ -131,6 +135,7 @@
defaultLayoutId = p.defaultLayoutId;
demoModeLayoutId = p.demoModeLayoutId;
mExtraAttrs = p.mExtraAttrs;
+ mOverlayMonitor = p.mOverlayMonitor;
}
@TargetApi(23)
@@ -138,8 +143,12 @@
initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null));
mConfigMonitor = new ConfigMonitor(context,
APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
+ mOverlayMonitor = new OverlayMonitor(context);
}
+ /**
+ * This constructor should NOT have any monitors by design.
+ */
public InvariantDeviceProfile(Context context, String gridName) {
String newName = initGrid(context, gridName);
if (newName == null || !newName.equals(gridName)) {
@@ -555,4 +564,20 @@
return this;
}
}
+
+ private class OverlayMonitor extends BroadcastReceiver {
+
+ private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+
+ OverlayMonitor(Context context) {
+ IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+ filter.addDataScheme("package");
+ context.registerReceiver(this, filter);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onConfigChanged(context);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index a130604..134e116 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -114,8 +114,6 @@
ItemInfo(ItemInfo info) {
copyFrom(info);
- // tempdebug:
- LauncherModel.checkItemInfo(this);
}
public void copyFrom(ItemInfo info) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d820448..f283a6c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -378,14 +378,7 @@
}
if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
- mUserEventDispatcher = null;
- initDeviceProfile(mDeviceProfile.inv);
- dispatchDeviceProfileChanged();
- reapplyUi();
- mDragLayer.recreateControllers();
-
- // TODO: We can probably avoid rebind when only screen size changed.
- rebindModel();
+ onIdpChanged(mDeviceProfile.inv);
}
mOldConfig.setTo(newConfig);
@@ -410,8 +403,19 @@
@Override
public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
+ onIdpChanged(idp);
+ }
+
+ private void onIdpChanged(InvariantDeviceProfile idp) {
+ mUserEventDispatcher = null;
+
initDeviceProfile(idp);
- getRootView().dispatchInsets();
+ dispatchDeviceProfileChanged();
+ reapplyUi();
+ mDragLayer.recreateControllers();
+
+ // TODO: We can probably avoid rebind when only screen size changed.
+ rebindModel();
}
private void initDeviceProfile(InvariantDeviceProfile idp) {
@@ -488,11 +492,15 @@
@Override
public void invalidateParent(ItemInfo info) {
- FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(getDeviceProfile().inv);
- if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) {
+ if (info.container >= 0) {
View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
- if (folderIcon != null) {
- folderIcon.invalidate();
+ if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) {
+ FolderIconPreviewVerifier verifier =
+ new FolderIconPreviewVerifier(getDeviceProfile().inv);
+ verifier.setFolderInfo((FolderInfo) folderIcon.getTag());
+ if (verifier.isItemInPreview(info.rank)) {
+ folderIcon.invalidate();
+ }
}
}
}
@@ -1763,12 +1771,11 @@
@Override
public void bindScreens(IntArray orderedScreenIds) {
// Make sure the first screen is always at the start.
- if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
- } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()
- && orderedScreenIds.isEmpty()) {
+ } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
// If there are no screens, we need to have an empty screen
mWorkspace.addExtraEmptyScreen();
}
@@ -1784,7 +1791,7 @@
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
int screenId = orderedScreenIds.get(i);
- if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenId != Workspace.FIRST_SCREEN_ID) {
+ if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
// No need to bind the first screen, as its always bound.
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b1664f1..c559f2b 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -34,7 +34,6 @@
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.AddWorkspaceItemsTask;
@@ -207,57 +206,6 @@
hasVerticalHotseat, verifyChanges);
}
- static void checkItemInfoLocked(
- final int itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
- ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
- if (modelItem != null && item != modelItem) {
- // If it is a release build on a release device, check all the data is consistent as
- // we don't want to crash non-dev users.
- if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
- modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
- if (modelItem.title.toString().equals(item.title.toString()) &&
- modelItem.getIntent().filterEquals(item.getIntent()) &&
- modelItem.id == item.id &&
- modelItem.itemType == item.itemType &&
- modelItem.container == item.container &&
- modelItem.screenId == item.screenId &&
- modelItem.cellX == item.cellX &&
- modelItem.cellY == item.cellY &&
- modelItem.spanX == item.spanX &&
- modelItem.spanY == item.spanY) {
- // For all intents and purposes, this is the same object
- return;
- }
- }
-
- // the modelItem needs to match up perfectly with item if our model is
- // to be consistent with the database-- for now, just require
- // modelItem == item or the equality check above
- String msg = "item: " + ((item != null) ? item.toString() : "null") +
- "modelItem: " +
- ((modelItem != null) ? modelItem.toString() : "null") +
- "Error: ItemInfo passed to checkItemInfo doesn't match original";
- RuntimeException e = new RuntimeException(msg);
- if (stackTrace != null) {
- e.setStackTrace(stackTrace);
- }
- throw e;
- }
- }
-
- static void checkItemInfo(final ItemInfo item) {
- final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
- final int itemId = item.id;
- Runnable r = new Runnable() {
- public void run() {
- synchronized (sBgDataModel) {
- checkItemInfoLocked(itemId, item, stackTrace);
- }
- }
- };
- runOnWorkerThread(r);
- }
-
/**
* Set this as the current Launcher activity object for the loader.
*/
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index fb33694..39d93c8 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
import android.annotation.TargetApi;
+import android.app.backup.BackupManager;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
@@ -150,7 +151,7 @@
mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler);
if (RestoreDbTask.isPending(getContext())) {
- if (!RestoreDbTask.performRestore(mOpenHelper)) {
+ if (!RestoreDbTask.performRestore(mOpenHelper, new BackupManager(getContext()))) {
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
}
// Set is pending to false irrespective of the result, so that it doesn't get
@@ -542,6 +543,7 @@
* The class is subclassed in tests to create an in-memory db.
*/
public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
+ private final BackupManager mBackupManager;
private final Handler mWidgetHostResetHandler;
private final Context mContext;
private int mMaxItemId = -1;
@@ -571,6 +573,7 @@
super(context, tableName, SCHEMA_VERSION);
mContext = context;
mWidgetHostResetHandler = widgetHostResetHandler;
+ mBackupManager = new BackupManager(mContext);
}
protected void initIds() {
@@ -620,9 +623,12 @@
Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
}
+ public long getSerialNumberForUser(UserHandle user) {
+ return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user);
+ }
+
public long getDefaultUserSerial() {
- return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
- Process.myUserHandle());
+ return getSerialNumberForUser(Process.myUserHandle());
}
private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
@@ -723,7 +729,7 @@
convertShortcutsToLauncherActivities(db);
case 26:
// QSB was moved to the grid. Clear the first row on screen 0.
- if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
!LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
break;
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index cee1c26..875288a 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -192,13 +192,7 @@
return getWorkspaceScaleAndTranslation(launcher);
}
- /**
- * Returns 2 floats designating how to transition overview:
- * scale for the current and adjacent pages
- * translationY factor where 0 is top aligned and 0.5 is centered vertically
- */
- public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
- // TODO: Simplify to use a constant value instead of a factor.
+ public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
return new float[] {1.1f, 0f};
}
diff --git a/src/com/android/launcher3/TestProtocol.java b/src/com/android/launcher3/TestProtocol.java
index 23df79e..4eb3627 100644
--- a/src/com/android/launcher3/TestProtocol.java
+++ b/src/com/android/launcher3/TestProtocol.java
@@ -30,4 +30,14 @@
public static final int OVERVIEW_STATE_ORDINAL = 2;
public static final int ALL_APPS_STATE_ORDINAL = 3;
public static final int BACKGROUND_APP_STATE_ORDINAL = 4;
+
+ public static final String TEST_INFO_RESPONSE_FIELD = "response";
+ public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT =
+ "home-to-overview-swipe-height";
+ public static final String REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT =
+ "background-to-overview-swipe-height";
+ public static final String REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT =
+ "all-apps-to-overview-swipe-height";
+ public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
+ "home-to-all-apps-swipe-height";
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index e699500..dd755cb 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -54,6 +54,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
+import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -115,6 +116,11 @@
public static final int SINGLE_FRAME_MS = 16;
/**
+ * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
+ */
+ public static final int EDGE_NAV_BAR = 1 << 8;
+
+ /**
* Indicates if the device has a debug build. Should only be used to store additional info or
* add extra logging and not for changing the app behavior.
*/
@@ -571,9 +577,11 @@
final boolean isFolderIcon = v instanceof FolderIcon;
final Rect rect = new Rect();
+ // Deep shortcut views have their icon drawn in a separate view.
final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
- if (fromDeepShortcutView) {
- // Deep shortcut views have their icon drawn in a separate view.
+ if (v instanceof DeepShortcutView) {
+ dragLayer.getDescendantRectRelativeToSelf(((DeepShortcutView) v).getIconView(), rect);
+ } else if (fromDeepShortcutView) {
DeepShortcutView view = (DeepShortcutView) v.getParent();
dragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
} else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9427675..720a692 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -487,7 +487,7 @@
* @param qsb an existing qsb to recycle or null.
*/
public void bindAndInitFirstWorkspaceScreen(View qsb) {
- if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
+ if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
return;
}
// Add the first page
@@ -794,7 +794,7 @@
int id = mWorkspaceScreens.keyAt(i);
CellLayout cl = mWorkspaceScreens.valueAt(i);
// FIRST_SCREEN_ID can never be removed.
- if ((!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || id > FIRST_SCREEN_ID)
+ if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
&& cl.getShortcutsAndWidgets().getChildCount() == 0) {
removeScreens.add(id);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 93bf69d..a4ecec7 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -188,9 +188,12 @@
private void setAlphas(LauncherState toState, AnimationConfig config,
AnimatorSetBuilder builder) {
+ setAlphas(toState.getVisibleElements(mLauncher), config, builder);
+ }
+
+ public void setAlphas(int visibleElements, AnimationConfig config, AnimatorSetBuilder builder) {
PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER
: config.getPropertySetter(builder);
- int visibleElements = toState.getVisibleElements(mLauncher);
boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0;
diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java
new file mode 100644
index 0000000..3d21d82
--- /dev/null
+++ b/src/com/android/launcher3/anim/FlingSpringAnim.java
@@ -0,0 +1,60 @@
+/*
+ * 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.anim;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.FlingAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+/**
+ * Given a property to animate and a target value and starting velocity, first apply friction to
+ * the fling until we pass the target, then apply a spring force to pull towards the target.
+ */
+public class FlingSpringAnim {
+
+ private static final float FLING_FRICTION = 1.5f;
+ // Have the spring pull towards the target if we've slowed down too much before reaching it.
+ private static final float FLING_END_THRESHOLD_PX = 50f;
+ private static final float SPRING_STIFFNESS = 350f;
+ private static final float SPRING_DAMPING = SpringForce.DAMPING_RATIO_LOW_BOUNCY;
+
+ private final FlingAnimation mFlingAnim;
+
+ public <K> FlingSpringAnim(K object, FloatPropertyCompat<K> property, float startPosition,
+ float targetPosition, float startVelocity, OnAnimationEndListener onEndListener) {
+ mFlingAnim = new FlingAnimation(object, property)
+ .setFriction(FLING_FRICTION)
+ .setMinimumVisibleChange(FLING_END_THRESHOLD_PX)
+ .setStartVelocity(startVelocity)
+ .setMinValue(Math.min(startPosition, targetPosition))
+ .setMaxValue(Math.max(startPosition, targetPosition));
+ mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
+ SpringAnimation springAnim = new SpringAnimation(object, property)
+ .setStartVelocity(velocity)
+ .setSpring(new SpringForce(targetPosition)
+ .setStiffness(SPRING_STIFFNESS)
+ .setDampingRatio(SPRING_DAMPING));
+ springAnim.addEndListener(onEndListener);
+ springAnim.start();
+ }));
+ }
+
+ public void start() {
+ mFlingAnim.start();
+ }
+}
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
index cc70e32..f74590b 100644
--- a/src/com/android/launcher3/anim/SpringObjectAnimator.java
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.anim;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -32,8 +34,6 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
-
/**
* This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
* a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
@@ -51,9 +51,9 @@
private SpringProperty<T> mProperty;
private ArrayList<AnimatorListener> mListeners;
- private boolean mSpringEnded = false;
- private boolean mAnimatorEnded = false;
- private boolean mEnded = false;
+ private boolean mSpringEnded = true;
+ private boolean mAnimatorEnded = true;
+ private boolean mEnded = true;
private static final FloatPropertyCompat<ProgressInterface> sFloatProperty =
new FloatPropertyCompat<ProgressInterface>("springObjectAnimator") {
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index d6e450a..106d901 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -21,6 +21,9 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
import android.provider.Settings;
import androidx.annotation.GuardedBy;
@@ -65,9 +68,8 @@
// When enabled the promise icon is visible in all apps while installation an app.
public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
- public static final TogglableFlag QSB_ON_FIRST_SCREEN = new TogglableFlag("QSB_ON_FIRST_SCREEN",
- true,
- "Enable moving the QSB on the 0th screen of the workspace");
+ // Enable moving the QSB on the 0th screen of the workspace
+ public static final boolean QSB_ON_FIRST_SCREEN = true;
public static final TogglableFlag EXAMPLE_FLAG = new TogglableFlag("EXAMPLE_FLAG", true,
"An example flag that doesn't do anything. Useful for testing");
@@ -178,7 +180,7 @@
currentValue = getFromStorage(context, defaultValue);
}
- void updateStorage(Context context, boolean value) {
+ public void updateStorage(Context context, boolean value) {
SharedPreferences.Editor editor = context.getSharedPreferences(FLAGS_PREF_NAME,
Context.MODE_PRIVATE).edit();
if (value == defaultValue) {
@@ -256,11 +258,21 @@
@Override
public void initialize(Context context) {
contentResolver = context.getContentResolver();
+ contentResolver.registerContentObserver(Settings.Global.getUriFor(getKey()), true,
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ superInitialize(context);
+ }});
+ superInitialize(context);
+ }
+
+ private void superInitialize(Context context) {
super.initialize(context);
}
@Override
- void updateStorage(Context context, boolean value) {
+ public void updateStorage(Context context, boolean value) {
if (contentResolver == null) {
return;
}
@@ -274,10 +286,5 @@
}
return Settings.Global.getInt(contentResolver, getKey(), defaultValue ? 1 : 0) == 1;
}
-
- @Override
- public boolean get() {
- return getFromStorage(null, getDefaultValue());
- }
}
}
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index f005ce7..6950a1f 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -63,8 +63,7 @@
public static final int ALPHA_INDEX_OVERLAY = 0;
public static final int ALPHA_INDEX_LAUNCHER_LOAD = 1;
public static final int ALPHA_INDEX_TRANSITIONS = 2;
- public static final int ALPHA_INDEX_SWIPE_UP = 3;
- private static final int ALPHA_CHANNEL_COUNT = 4;
+ private static final int ALPHA_CHANNEL_COUNT = 3;
public static final int ANIMATION_END_DISAPPEAR = 0;
public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 67495ea..71c9148 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -200,6 +200,7 @@
private void setFolder(Folder folder) {
mFolder = folder;
mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
+ mPreviewVerifier.setFolderInfo(mFolder.getInfo());
updatePreviewItems(false);
}
diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
index 5a27cd4..4c84e35 100644
--- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
+++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
@@ -16,6 +16,8 @@
package com.android.launcher3.folder;
+import android.util.Log;
+
import com.android.launcher3.FolderInfo;
import com.android.launcher3.InvariantDeviceProfile;
@@ -26,14 +28,20 @@
*/
public class FolderIconPreviewVerifier {
+ private static final String TAG = "FolderPreviewVerifier";
+
private final int mMaxGridCountX;
private final int mMaxGridCountY;
private final int mMaxItemsPerPage;
- private final int[] mGridSize = new int[2];
+ private final int[] mGridSize = new int[] { 1, 1 };
+ private int mNumItemsInFolder;
private int mGridCountX;
private boolean mDisplayingUpperLeftQuadrant = false;
+ /**
+ * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
+ */
public FolderIconPreviewVerifier(InvariantDeviceProfile profile) {
mMaxGridCountX = profile.numFolderColumns;
mMaxGridCountY = profile.numFolderRows;
@@ -42,11 +50,14 @@
public void setFolderInfo(FolderInfo info) {
int numItemsInFolder = info.contents.size();
- FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
- mMaxGridCountY, mMaxItemsPerPage, mGridSize);
- mGridCountX = mGridSize[0];
+ if (numItemsInFolder != mNumItemsInFolder) {
+ FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
+ mMaxGridCountY, mMaxItemsPerPage, mGridSize);
+ mGridCountX = mGridSize[0];
- mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
+ mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
+ mNumItemsInFolder = numItemsInFolder;
+ }
}
/**
@@ -62,6 +73,10 @@
* @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
*/
public boolean isItemInPreview(int page, int rank) {
+ if (mGridSize[0] == 1) {
+ Log.w(TAG, "setFolderInfo not called before checking if item is in preview.");
+ }
+
// First page items are laid out such that the first 4 items are always in the upper
// left quadrant. For all other pages, we need to check the row and col.
if (page > 0 || mDisplayingUpperLeftQuadrant) {
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 06eaf38..d4ce6dd 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -321,6 +321,7 @@
FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
Launcher.getLauncher(getContext()).getDeviceProfile().inv);
+ verifier.setFolderInfo(mFolder.getInfo());
rank = 0;
for (int i = 0; i < itemCount; i++) {
View v = list.size() > i ? list.get(i) : null;
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 837749d..eb45be1 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -262,7 +262,7 @@
}
// Add first page QSB
- if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
View qsb = mHomeElementInflater.inflate(
R.layout.search_container_workspace, mWorkspace, false);
CellLayout.LayoutParams lp =
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 210f744..97cf267 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -61,6 +61,8 @@
protected final WeakReference<Callbacks> mCallbacks;
+ private int mMyBindingId;
+
public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
mUiExecutor = new MainThreadExecutor();
@@ -94,6 +96,7 @@
appWidgets.addAll(mBgDataModel.appWidgets);
orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
mBgDataModel.lastBindId++;
+ mMyBindingId = mBgDataModel.lastBindId;
}
final int currentScreen;
@@ -285,6 +288,10 @@
protected void executeCallbacksTask(CallbackTask task, Executor executor) {
executor.execute(() -> {
+ if (mMyBindingId != mBgDataModel.lastBindId) {
+ Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
+ return;
+ }
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
task.execute(callbacks);
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index b338fff..e8cc8f9 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -129,7 +129,7 @@
screenSet.add(item.screenId);
}
}
- if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenSet.isEmpty()) {
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) {
screenSet.add(Workspace.FIRST_SCREEN_ID);
}
return screenSet.getArray();
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index 243b286..faecc06 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -253,8 +253,7 @@
*/
protected void migrateScreen(int screenId) {
// If we are migrating the first screen, do not touch the first row.
- int startY =
- (FeatureFlags.QSB_ON_FIRST_SCREEN.get() && screenId == Workspace.FIRST_SCREEN_ID)
+ int startY = (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID)
? 1 : 0;
ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index d104a8b..88193d0 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -442,8 +442,7 @@
if (item.screenId == Workspace.FIRST_SCREEN_ID) {
// Mark the first row as occupied (if the feature is enabled)
// in order to account for the QSB.
- screen.markCells(0, 0, countX + 1, 1,
- FeatureFlags.QSB_ON_FIRST_SCREEN.get());
+ screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
}
occupied.put(item.screenId, screen);
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 7275576..2b20b08 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -340,8 +340,6 @@
Intent intent;
String targetPkg;
- FolderIconPreviewVerifier verifier =
- new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
while (!mStopped && c.moveToNext()) {
try {
if (c.user == null) {
@@ -366,13 +364,13 @@
ComponentName cn = intent.getComponent();
targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
- if (!Process.myUserHandle().equals(c.user)) {
+ if (allUsers.indexOfValue(c.user) < 0) {
if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
- c.markDeleted("Legacy shortcuts are only allowed for default user");
+ c.markDeleted("Legacy shortcuts are only allowed for current users");
continue;
} else if (c.restoreFlag != 0) {
// Don't restore items for other profiles.
- c.markDeleted("Restore from managed profile not supported");
+ c.markDeleted("Restore from other profiles not supported");
continue;
}
}
@@ -461,8 +459,7 @@
c.markRestored();
}
- boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
- !verifier.isItemInPreview(c.getInt(rankIndex));
+ boolean useLowResIcon = !c.isOnWorkspaceOrHotseat();
if (c.restoreFlag != 0) {
// Already verified above that user is same as default user
@@ -745,24 +742,25 @@
}
}
+ // Sort the folder items, update ranks, and make sure all preview items are high res.
FolderIconPreviewVerifier verifier =
new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
- // Sort the folder items and make sure all items in the preview are high resolution.
for (FolderInfo folder : mBgDataModel.folders) {
Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
verifier.setFolderInfo(folder);
+ int size = folder.contents.size();
- int numItemsInPreview = 0;
- for (ShortcutInfo info : folder.contents) {
+ // Update ranks here to ensure there are no gaps caused by removed folder items.
+ // Ranks are the source of truth for folder items, so cellX and cellY can be ignored
+ // for now. Database will be updated once user manually modifies folder.
+ for (int rank = 0; rank < size; ++rank) {
+ ShortcutInfo info = folder.contents.get(rank);
+ info.rank = rank;
+
if (info.usingLowResIcon()
&& info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
&& verifier.isItemInPreview(info.rank)) {
mIconCache.getTitleAndIcon(info, false);
- numItemsInPreview++;
- }
-
- if (numItemsInPreview >= MAX_NUM_ITEMS_IN_PREVIEW) {
- break;
}
}
}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index ac5076c..daf99e9 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -37,6 +37,7 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -112,19 +113,18 @@
ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
if (modelItem != null && item != modelItem) {
// check all the data is consistent
- if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
- ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
- ShortcutInfo shortcut = (ShortcutInfo) item;
- if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
- modelShortcut.intent.filterEquals(shortcut.intent) &&
- modelShortcut.id == shortcut.id &&
- modelShortcut.itemType == shortcut.itemType &&
- modelShortcut.container == shortcut.container &&
- modelShortcut.screenId == shortcut.screenId &&
- modelShortcut.cellX == shortcut.cellX &&
- modelShortcut.cellY == shortcut.cellY &&
- modelShortcut.spanX == shortcut.spanX &&
- modelShortcut.spanY == shortcut.spanY) {
+ if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
+ modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
+ if (modelItem.title.toString().equals(item.title.toString()) &&
+ modelItem.getIntent().filterEquals(item.getIntent()) &&
+ modelItem.id == item.id &&
+ modelItem.itemType == item.itemType &&
+ modelItem.container == item.container &&
+ modelItem.screenId == item.screenId &&
+ modelItem.cellX == item.cellX &&
+ modelItem.cellY == item.cellY &&
+ modelItem.spanX == item.spanX &&
+ modelItem.spanY == item.spanY) {
// For all intents and purposes, this is the same object
return;
}
@@ -310,7 +310,7 @@
/**
* Delete operations tracked using {@link #enqueueDeleteRunnable} will only be called
* if {@link #commitDelete} is called. Note that one of {@link #commitDelete()} or
- * {@link #abortDelete()} MUST be called after this method, or else all delete
+ * {@link #abortDelete} MUST be called after this method, or else all delete
* operations will remain uncommitted indefinitely.
*/
public void prepareToUndoDelete() {
@@ -325,7 +325,7 @@
/**
* If {@link #prepareToUndoDelete} has been called, we store the Runnable to be run when
- * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete()} is called).
+ * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete} is called).
* Otherwise, we run the Runnable immediately.
*/
private void enqueueDeleteRunnable(Runnable r) {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index b0af4c6..10ecc4f 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
@@ -167,7 +168,8 @@
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
BaseDragLayer dl = getPopupContainer();
- if (!dl.isEventOverView(this, ev)) {
+ final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+ if (!cameFromNavBar && !dl.isEventOverView(this, ev)) {
mLauncher.getUserEventDispatcher().logActionTapOutside(
LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
close(true);
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index 86fcc06..7b62f53 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -104,7 +104,7 @@
.getSerialNumberForUser(Process.myUserHandle()));
boolean createEmptyRowOnFirstScreen;
- if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null,
// get items on the first row of the first screen (min screen id)
"profileId = ? AND container = -100 AND cellY = 0 AND screen = " +
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index bcca4d8..040b5e5 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -18,10 +18,15 @@
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import android.app.backup.BackupManager;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.LongSparseArray;
+import android.util.SparseLongArray;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
@@ -48,10 +53,10 @@
private static final String INFO_COLUMN_NAME = "name";
private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value";
- public static boolean performRestore(DatabaseHelper helper) {
+ public static boolean performRestore(DatabaseHelper helper, BackupManager backupManager) {
SQLiteDatabase db = helper.getWritableDatabase();
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
- new RestoreDbTask().sanitizeDB(helper, db);
+ new RestoreDbTask().sanitizeDB(helper, db, backupManager);
t.commit();
return true;
} catch (Exception e) {
@@ -62,20 +67,44 @@
/**
* Makes the following changes in the provider DB.
- * 1. Removes all entries belonging to a managed profile as managed profiles
- * cannot be restored.
+ * 1. Removes all entries belonging to any profiles that were not restored.
* 2. Marks all entries as restored. The flags are updated during first load or as
* the restored apps get installed.
- * 3. If the user serial for primary profile is different than that of the previous device,
- * update the entries to the new profile id.
+ * 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.
*/
- private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db) throws Exception {
+ private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db, BackupManager backupManager)
+ throws Exception {
+ // Primary user ids
+ long myProfileId = helper.getDefaultUserSerial();
long oldProfileId = getDefaultProfileId(db);
- // Delete all entries which do not belong to the main user
- int itemsDeleted = db.delete(
- Favorites.TABLE_NAME, "profileId != ?", new String[]{Long.toString(oldProfileId)});
+ LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
+ LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
+ + 1);
+
+ // Build mapping of restored profile ids to their new profile ids.
+ profileMapping.put(oldProfileId, myProfileId);
+ for (int i = oldManagedProfileIds.size() - 1; i >= 0; --i) {
+ long oldManagedProfileId = oldManagedProfileIds.keyAt(i);
+ UserHandle user = getUserForAncestralSerialNumber(backupManager, oldManagedProfileId);
+ if (user != null) {
+ long newManagedProfileId = helper.getSerialNumberForUser(user);
+ profileMapping.put(oldManagedProfileId, newManagedProfileId);
+ }
+ }
+
+ // Delete all entries which do not belong to any restored profile(s).
+ int numProfiles = profileMapping.size();
+ String[] profileIds = new String[numProfiles];
+ profileIds[0] = Long.toString(oldProfileId);
+ StringBuilder whereClause = new StringBuilder("profileId != ?");
+ for (int i = profileMapping.size() - 1; i >= 1; --i) {
+ whereClause.append(" AND profileId != ?");
+ profileIds[i] = Long.toString(profileMapping.keyAt(i));
+ }
+ int itemsDeleted = db.delete(Favorites.TABLE_NAME, whereClause.toString(), profileIds);
if (itemsDeleted > 0) {
- FileLog.d(TAG, itemsDeleted + " items belonging to a managed profile, were deleted");
+ FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted");
}
// Mark all items as restored.
@@ -85,7 +114,7 @@
| (keepAllIcons ? ShortcutInfo.FLAG_RESTORE_STARTED : 0));
db.update(Favorites.TABLE_NAME, values, null, null);
- // Mark widgets with appropriate restore flag
+ // Mark widgets with appropriate restore flag.
values.put(Favorites.RESTORED, LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
LauncherAppWidgetInfo.FLAG_UI_NOT_READY |
@@ -93,21 +122,46 @@
db.update(Favorites.TABLE_NAME, values, "itemType = ?",
new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)});
- long myProfileId = helper.getDefaultUserSerial();
+ // Migrate ids. To avoid any overlap, we initially move conflicting ids to a temp location.
+ // Using Long.MIN_VALUE since profile ids can not be negative, so there will be no overlap.
+ final long tempLocationOffset = Long.MIN_VALUE;
+ SparseLongArray tempMigratedIds = new SparseLongArray(profileMapping.size());
+ int numTempMigrations = 0;
+ for (int i = profileMapping.size() - 1; i >= 0; --i) {
+ long oldId = profileMapping.keyAt(i);
+ long newId = profileMapping.valueAt(i);
+
+ if (oldId != newId) {
+ if (profileMapping.indexOfKey(newId) >= 0) {
+ tempMigratedIds.put(numTempMigrations, newId);
+ numTempMigrations++;
+ newId = tempLocationOffset + newId;
+ }
+ migrateProfileId(db, oldId, newId);
+ }
+ }
+
+ // Migrate ids from their temporary id to their actual final id.
+ for (int i = tempMigratedIds.size() - 1; i >= 0; --i) {
+ long newId = tempMigratedIds.valueAt(i);
+ migrateProfileId(db, tempLocationOffset + newId, newId);
+ }
+
if (myProfileId != oldProfileId) {
- FileLog.d(TAG, "Changing primary user id from " + oldProfileId + " to " + myProfileId);
- migrateProfileId(db, myProfileId);
+ changeDefaultColumn(db, myProfileId);
}
}
/**
- * Updates profile id of all entries and changes the default value for the column.
+ * Updates profile id of all entries from {@param oldProfileId} to {@param newProfileId}.
*/
- protected void migrateProfileId(SQLiteDatabase db, long newProfileId) {
+ protected void migrateProfileId(SQLiteDatabase db, long oldProfileId, long newProfileId) {
+ FileLog.d(TAG, "Changing profile user id from " + oldProfileId + " to " + newProfileId);
// Update existing entries.
ContentValues values = new ContentValues();
values.put(Favorites.PROFILE_ID, newProfileId);
- db.update(Favorites.TABLE_NAME, values, null, null);
+ db.update(Favorites.TABLE_NAME, values, "profileId = ?",
+ new String[]{Long.toString(oldProfileId)});
// Change default value of the column.
db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
@@ -116,6 +170,43 @@
dropTable(db, "favorites_old");
}
+
+ /**
+ * Changes the default value for the column.
+ */
+ protected void changeDefaultColumn(SQLiteDatabase db, long newProfileId) {
+ db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
+ Favorites.addTableToDb(db, newProfileId, false);
+ db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;");
+ dropTable(db, "favorites_old");
+ }
+
+ /**
+ * Returns a list of the managed profile id(s) used in the favorites table of the provided db.
+ */
+ private LongSparseArray<Long> getManagedProfileIds(SQLiteDatabase db, long defaultProfileId) {
+ LongSparseArray<Long> ids = new LongSparseArray<>();
+ try (Cursor c = db.rawQuery("SELECT profileId from favorites WHERE profileId != ? "
+ + "GROUP BY profileId", new String[] {Long.toString(defaultProfileId)})){
+ while (c.moveToNext()) {
+ ids.put(c.getLong(c.getColumnIndex(Favorites.PROFILE_ID)), null);
+ }
+ }
+ return ids;
+ }
+
+ /**
+ * Returns a UserHandle of a restored managed profile with the given serial number, or null
+ * if none found.
+ */
+ private UserHandle getUserForAncestralSerialNumber(BackupManager backupManager,
+ long ancestralSerialNumber) {
+ if (Build.VERSION.SDK_INT < 29) {
+ return null;
+ }
+ return backupManager.getUserForAncestralSerialNumber(ancestralSerialNumber);
+ }
+
/**
* Returns the profile id used in the favorites table of the provided db.
*/
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index c2bae6d..857ea05 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -215,7 +215,7 @@
}
public boolean isQsbEnabled() {
- return FeatureFlags.QSB_ON_FIRST_SCREEN.get();
+ return FeatureFlags.QSB_ON_FIRST_SCREEN;
}
protected Bundle createBindOptions() {
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 12280f8..12d35e9 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -41,8 +41,6 @@
private static final String TAG = "ConfigMonitor";
- private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
-
private final Point mTmpPoint1 = new Point();
private final Point mTmpPoint2 = new Point();
@@ -78,11 +76,6 @@
// Listen for configuration change
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
- // Listen for {@link OverlayManager} change
- IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
- filter.addDataScheme("package");
- mContext.registerReceiver(this, filter);
-
// Listen for display manager change
mContext.getSystemService(DisplayManager.class)
.registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper()));
@@ -91,12 +84,6 @@
@Override
public void onReceive(Context context, Intent intent) {
Configuration config = context.getResources().getConfiguration();
- // TODO: when overlay manager service encodes more information to the Uri such as category
- // of the overlay, only listen to the ones that are of interest to launcher.
- if (intent != null && ACTION_OVERLAY_CHANGED.equals(intent.getAction())) {
- Log.d(TAG, "Overlay changed.");
- notifyChange();
- }
if (mFontScale != config.fontScale || mDensity != config.densityDpi) {
Log.d(TAG, "Configuration changed.");
notifyChange();
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 7af2bd8..2a5418d 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.views;
+import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -50,12 +52,11 @@
import com.android.launcher3.graphics.ShiftedBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.shortcuts.DeepShortcutView;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
-
/**
* A view that is created to look like another view with the purpose of creating fluid animations.
*/
@@ -142,9 +143,6 @@
setBackgroundDrawableBounds(bgScale);
mRevealAnimator.setCurrentFraction(shapeRevealProgress);
- if (Float.compare(shapeRevealProgress, 1f) >= 0f) {
- mRevealAnimator.end();
- }
}
invalidate();
invalidateOutline();
@@ -159,6 +157,9 @@
@Override
public void onAnimationEnd(Animator animator) {
+ if (mRevealAnimator != null) {
+ mRevealAnimator.end();
+ }
if (mEndRunnable != null) {
mEndRunnable.run();
}
@@ -197,8 +198,14 @@
// Similar to DragView, we simply use the BubbleTextView icon here.
drawable = ((BubbleTextView) v).getIcon();
}
- if (v instanceof ImageView && info instanceof SystemShortcut) {
- drawable = ((ImageView) v).getDrawable();
+ if (info instanceof SystemShortcut) {
+ if (v instanceof ImageView) {
+ drawable = ((ImageView) v).getDrawable();
+ } else if (v instanceof DeepShortcutView) {
+ drawable = ((DeepShortcutView) v).getIconView().getBackground();
+ } else {
+ drawable = v.getBackground();
+ }
}
if (drawable == null) {
drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 48cf9e8..e259cfe 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -18,8 +18,10 @@
import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.TextUtils;
@@ -150,7 +152,7 @@
RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
ArrayList<OptionItem> options = new ArrayList<>();
- int res = FeatureFlags.STYLE_WALLPAPER.get() ?
+ int res = FeatureFlags.STYLE_WALLPAPER.get() && existsStyleWallpapers(launcher) ?
R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
options.add(new OptionItem(res, R.drawable.ic_wallpaper,
ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker));
@@ -164,6 +166,14 @@
show(launcher, target, options);
}
+ private static boolean existsStyleWallpapers(Launcher launcher) {
+ Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ intent.setComponent(new ComponentName(launcher.getString(R.string.wallpaper_picker_package),
+ "com.android.customization.picker.CustomizationPickerActivity"));
+ ResolveInfo ri = launcher.getPackageManager().resolveActivity(intent, 0);
+ return ri != null;
+ }
+
public static boolean onWidgetsClicked(View view) {
return openWidgets(Launcher.getLauncher(view.getContext()));
}
diff --git a/tests/Android.mk b/tests/Android.mk
index a787537..ca7395a 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -23,16 +23,20 @@
androidx.annotation_annotation \
androidx.test.runner \
androidx.test.rules \
- androidx.test.uiautomator_uiautomator \
- libSharedSystemUI
+ androidx.test.uiautomator_uiautomator
-LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
- ../quickstep/src/com/android/quickstep/SwipeUpSetting.java \
- ../src/com/android/launcher3/util/SecureSettingsObserver.java \
- ../src/com/android/launcher3/TestProtocol.java \
+ifneq (,$(wildcard frameworks/base))
+else
+ LOCAL_STATIC_JAVA_LIBRARIES += libSharedSystemUI
-LOCAL_SDK_VERSION := current
+ LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
+ ../quickstep/src/com/android/quickstep/SwipeUpSetting.java \
+ ../src/com/android/launcher3/util/SecureSettingsObserver.java \
+ ../src/com/android/launcher3/TestProtocol.java
+endif
+
LOCAL_MODULE := ub-launcher-aosp-tapl
+LOCAL_SDK_VERSION := current
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -43,18 +47,18 @@
LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
- androidx.test.runner \
- androidx.test.rules \
- androidx.test.uiautomator_uiautomator \
- mockito-target-minus-junit4
+ androidx.test.runner \
+ androidx.test.rules \
+ androidx.test.uiautomator_uiautomator \
+ mockito-target-minus-junit4
ifneq (,$(wildcard frameworks/base))
- LOCAL_PRIVATE_PLATFORM_APIS := true
- LOCAL_STATIC_JAVA_LIBRARIES += launcher-aosp-tapl
+ LOCAL_PRIVATE_PLATFORM_APIS := true
+ LOCAL_STATIC_JAVA_LIBRARIES += launcher-aosp-tapl
else
- LOCAL_SDK_VERSION := 28
- LOCAL_MIN_SDK_VERSION := 21
- LOCAL_STATIC_JAVA_LIBRARIES += ub-launcher-aosp-tapl
+ LOCAL_SDK_VERSION := 28
+ LOCAL_MIN_SDK_VERSION := 21
+ LOCAL_STATIC_JAVA_LIBRARIES += ub-launcher-aosp-tapl
endif
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index babb731..6fa8d62 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -41,18 +41,34 @@
// Verify item add
assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
- new RestoreDbTask().migrateProfileId(db, 33);
+ new RestoreDbTask().migrateProfileId(db, 42, 33);
// verify data migrated
assertEquals(0, getCount(db, "select * from favorites where profileId = 42"));
assertEquals(5, getCount(db, "select * from favorites where profileId = 33"));
+ }
+
+ @Test
+ public void testChangeDefaultColumn() throws Exception {
+ SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
+ // Add some dummy data
+ for (int i = 0; i < 5; i++) {
+ ContentValues values = new ContentValues();
+ values.put(Favorites._ID, i);
+ values.put(Favorites.TITLE, "item " + i);
+ db.insert(Favorites.TABLE_NAME, null, values);
+ }
+ // Verify default column is 42
+ assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
+
+ new RestoreDbTask().changeDefaultColumn(db, 33);
// Verify default value changed
ContentValues values = new ContentValues();
values.put(Favorites._ID, 100);
values.put(Favorites.TITLE, "item 100");
db.insert(Favorites.TABLE_NAME, null, values);
- assertEquals(6, getCount(db, "select * from favorites where profileId = 33"));
+ assertEquals(1, getCount(db, "select * from favorites where profileId = 33"));
}
private int getCount(SQLiteDatabase db, String sql) {
diff --git a/tests/tapl/AndroidManifest.xml b/tests/tapl/AndroidManifest.xml
new file mode 100644
index 0000000..0207e2b
--- /dev/null
+++ b/tests/tapl/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.launcher3.tapl"
+>
+
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+</manifest>
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 1353a23..122151e 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -44,6 +44,13 @@
return LauncherInstrumentation.ContainerType.ALL_APPS;
}
+ private boolean hasClickableIcon(UiObject2 allAppsContainer, BySelector appIconSelector) {
+ final UiObject2 icon = allAppsContainer.findObject(appIconSelector);
+ if (icon == null) return false;
+ final UiObject2 navBar = mLauncher.waitForSystemUiObject("navigation_bar_frame");
+ return icon.getVisibleBounds().bottom < navBar.getVisibleBounds().top;
+ }
+
/**
* Finds an icon. Fails if the icon doesn't exist. Scrolls the app list when needed to make
* sure the icon is visible.
@@ -55,10 +62,10 @@
public AppIcon getAppIcon(String appName) {
final UiObject2 allAppsContainer = verifyActiveContainer();
final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
- if (!allAppsContainer.hasObject(appIconSelector)) {
+ if (!hasClickableIcon(allAppsContainer, appIconSelector)) {
scrollBackToBeginning();
int attempts = 0;
- while (!allAppsContainer.hasObject(appIconSelector) &&
+ while (!hasClickableIcon(allAppsContainer, appIconSelector) &&
allAppsContainer.scroll(Direction.DOWN, 0.8f)) {
LauncherInstrumentation.assertTrue(
"Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
index 2642815..dcc51b5 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
@@ -23,6 +23,8 @@
import androidx.annotation.NonNull;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.TestProtocol;
+
/**
* Operations on AllApps opened from Overview.
*/
@@ -45,7 +47,11 @@
final UiObject2 qsb = mLauncher.waitForObjectInContainer(
allAppsContainer, "search_container_all_apps");
final Point start = qsb.getVisibleCenter();
- final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.6);
+ final int swipeHeight = mLauncher.getTestInfo(
+ TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT).
+ getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+
+ final int endY = start.y + swipeHeight + mLauncher.getTouchSlop();
LauncherInstrumentation.log("AllAppsFromOverview.switchBackToOverview before swipe");
mLauncher.swipe(start.x, start.y, start.x, endY, OVERVIEW_STATE_ORDINAL);
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index d39a38e..fbeb3a2 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -17,6 +17,7 @@
package com.android.launcher3.tapl;
import android.graphics.Point;
+import android.os.SystemClock;
import android.view.MotionEvent;
import android.widget.TextView;
@@ -41,10 +42,12 @@
*/
public AppIconMenu openMenu() {
final Point iconCenter = mObject.getVisibleCenter();
- mLauncher.sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
+ final long downTime = SystemClock.uptimeMillis();
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, iconCenter);
final UiObject2 deepShortcutsContainer = mLauncher.waitForLauncherObject(
"deep_shortcuts_container");
- mLauncher.sendPointer(MotionEvent.ACTION_UP, iconCenter);
+ mLauncher.sendPointer(
+ downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, iconCenter);
return new AppIconMenu(mLauncher, deepShortcutsContainer);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 606cf37..ef509ed 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -22,16 +22,23 @@
import static org.junit.Assert.assertTrue;
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+
import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
+import com.android.launcher3.TestProtocol;
+
/**
* Indicates the base state with a UI other than Overview running as foreground. It can also
* indicate Launcher as long as Launcher is not in Overview state.
*/
public class Background extends LauncherInstrumentation.VisibleContainer {
+ private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
+ private static final int ZERO_BUTTON_SWIPE_UP_HOLD_DURATION = 400;
Background(LauncherInstrumentation launcher) {
super(launcher);
@@ -58,21 +65,49 @@
}
protected void goToOverviewUnchecked(int expectedState) {
- if (mLauncher.isSwipeUpEnabled()) {
- final int height = mLauncher.getDevice().getDisplayHeight();
- final UiObject2 navBar = mLauncher.getSystemUiObject("navigation_bar_frame");
+ switch (mLauncher.getNavigationModel()) {
+ case ZERO_BUTTON: {
+ final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+ final int startY = getSwipeStartY();
+ final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).
+ getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ final Point start = new Point(centerX, startY);
+ final Point end =
+ new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
- int swipeLength = Math.round(getSwipeLength() * mLauncher.getDisplayDensity());
- mLauncher.swipe(
- navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
- navBar.getVisibleBounds().centerX(), height - swipeLength,
- expectedState);
- } else {
- mLauncher.getSystemUiObject("recent_apps").click();
+ final long downTime = SystemClock.uptimeMillis();
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
+ mLauncher.movePointer(downTime, ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION, start, end);
+ LauncherInstrumentation.sleep(ZERO_BUTTON_SWIPE_UP_HOLD_DURATION);
+ mLauncher.sendPointer(
+ downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end);
+ break;
+ }
+
+ case TWO_BUTTON: {
+ final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+ final int startY = getSwipeStartY();
+ final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).
+ getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+
+ mLauncher.swipe(
+ centerX, startY, centerX,
+ startY - swipeHeight - mLauncher.getTouchSlop(),
+ expectedState);
+ break;
+ }
+
+ case THREE_BUTTON:
+ mLauncher.waitForSystemUiObject("recent_apps").click();
+ break;
}
}
- protected int getSwipeLength() {
- return 200;
+ protected String getSwipeHeightRequestName() {
+ return TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT;
+ }
+
+ protected int getSwipeStartY() {
+ return mLauncher.waitForSystemUiObject("navigation_bar_frame").getVisibleBounds().centerY();
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 6e92dad..234c3bf 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -29,7 +29,7 @@
*/
public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
private static final int FLING_SPEED = 1500;
- private static final int FLINGS_FOR_DISMISS_LIMIT = 5;
+ private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
BaseOverview(LauncherInstrumentation launcher) {
super(launcher);
@@ -44,8 +44,10 @@
* Flings forward (left) and waits the fling's end.
*/
public void flingForward() {
- final UiObject2 overview = verifyActiveContainer();
LauncherInstrumentation.log("Overview.flingForward before fling");
+ final UiObject2 overview = verifyActiveContainer();
+ final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+ overview.setGestureMargins(margin, 0, 0, 0);
overview.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
mLauncher.waitForIdle();
verifyActiveContainer();
@@ -58,7 +60,7 @@
final BySelector clearAllSelector = mLauncher.getLauncherObjectSelector("clear_all");
for (int i = 0;
i < FLINGS_FOR_DISMISS_LIMIT
- && verifyActiveContainer().findObject(clearAllSelector) == null;
+ && !verifyActiveContainer().hasObject(clearAllSelector);
++i) {
flingForward();
}
@@ -71,8 +73,10 @@
* Flings backward (right) and waits the fling's end.
*/
public void flingBackward() {
- final UiObject2 overview = verifyActiveContainer();
LauncherInstrumentation.log("Overview.flingBackward before fling");
+ final UiObject2 overview = verifyActiveContainer();
+ final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+ overview.setGestureMargins(0, 0, margin, 0);
overview.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
mLauncher.waitForIdle();
verifyActiveContainer();
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 93b4cc6..7e8c544 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -16,33 +16,41 @@
package com.android.launcher3.tapl;
-import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
+import static com.android.launcher3.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.UiAutomation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.Point;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock;
-import android.provider.Settings;
import android.util.Log;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.Configurator;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
import com.android.launcher3.TestProtocol;
-import com.android.quickstep.SwipeUpSetting;
+import com.android.systemui.shared.system.QuickStepContract;
import org.junit.Assert;
+import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.TimeoutException;
@@ -54,6 +62,7 @@
public final class LauncherInstrumentation {
private static final String TAG = "Tapl";
+ private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
// Types for launcher containers that the user is interacting with. "Background" is a
// pseudo-container corresponding to inactive launcher covered by another app.
@@ -61,6 +70,8 @@
WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, BASE_OVERVIEW
}
+ public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
+
// Base class for launcher containers.
static abstract class VisibleContainer {
protected final LauncherInstrumentation mLauncher;
@@ -95,6 +106,7 @@
private final UiDevice mDevice;
private final Instrumentation mInstrumentation;
private int mExpectedRotation = Surface.ROTATION_0;
+ private final Uri mTestProviderUri;
/**
* Constructs the root of TAPL hierarchy. You get all other objects from it.
@@ -108,20 +120,56 @@
// into Launcher.
assertTrue("Device must run in a test harness",
TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness());
+
+ final String testPackage = getContext().getPackageName();
+ final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
+
+ // Launcher package. As during inproc tests the tested launcher may not be selected as the
+ // current launcher, choosing target package for inproc. For out-of-proc, use the installed
+ // launcher package.
+ final String authorityPackage = testPackage.equals(targetPackage) ?
+ getLauncherPackageName() :
+ targetPackage;
+
+ mTestProviderUri = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authorityPackage + ".TestInfo")
+ .build();
+
+ try {
+ mDevice.executeShellCommand("pm grant " + testPackage +
+ " android.permission.WRITE_SECURE_SETTINGS");
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ }
+
+ Context getContext() {
+ return mInstrumentation.getContext();
+ }
+
+ Bundle getTestInfo(String request) {
+ return getContext().getContentResolver().call(mTestProviderUri, request, null, null);
}
void setActiveContainer(VisibleContainer container) {
sActiveContainer = new WeakReference<>(container);
}
- public boolean isSwipeUpEnabled() {
- final boolean swipeUpEnabledDefaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
- return SwipeUpSetting.isSwipeUpSettingAvailable() ?
- Settings.Secure.getInt(
- mInstrumentation.getTargetContext().getContentResolver(),
- SWIPE_UP_SETTING_NAME,
- swipeUpEnabledDefaultValue ? 1 : 0) == 1 :
- swipeUpEnabledDefaultValue;
+ public NavigationModel getNavigationModel() {
+ return isSwipeUpEnabled() ? NavigationModel.TWO_BUTTON : NavigationModel.THREE_BUTTON;
+ }
+
+ private boolean isSwipeUpEnabled() {
+ final Context baseContext = mInstrumentation.getTargetContext();
+ try {
+ // Workaround, use constructed context because both the instrumentation context and the
+ // app context are not constructed with resources that take overlays into account
+ Context ctx = baseContext.createPackageContext(getLauncherPackageName(), 0);
+ return !QuickStepContract.isLegacyMode(ctx);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
}
static void log(String message) {
@@ -250,24 +298,49 @@
// We need waiting for any accessibility event generated after pressing Home because
// otherwise waitForIdle may return immediately in case when there was a big enough pause in
// accessibility events prior to pressing Home.
- executeAndWaitForEvent(
- () -> {
- log("LauncherInstrumentation.pressHome before clicking");
- getSystemUiObject("home").click();
- },
- event -> true,
- "Pressing Home didn't produce any events");
- mDevice.waitForIdle();
+ if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+ if (hasLauncherObject(WORKSPACE_RES_ID)) {
+ log("0-button pressHome: already in workspace");
+ } else if (hasLauncherObject(OVERVIEW_RES_ID)) {
+ log("0-button pressHome: overview");
+ mDevice.pressHome();
+ } else if (hasLauncherObject(WIDGETS_RES_ID)) {
+ log("0-button pressHome: widgets");
+ mDevice.pressHome();
+ } else if (hasLauncherObject(APPS_RES_ID)) {
+ log("0-button pressHome: all apps");
+ mDevice.pressHome();
+ } else {
+ log("0-button pressHome: another app");
+ assertTrue("Launcher is visible, don't know how to go home",
+ !mDevice.hasObject(By.pkg(getLauncherPackageName())));
+ final UiObject2 navBar = waitForSystemUiObject("navigation_bar_frame");
- // Temporarily press home twice as the first click sometimes gets ignored (b/124239413)
- executeAndWaitForEvent(
- () -> {
- log("LauncherInstrumentation.pressHome before clicking");
- getSystemUiObject("home").click();
- },
- event -> true,
- "Pressing Home didn't produce any events");
- mDevice.waitForIdle();
+ swipe(
+ navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
+ navBar.getVisibleBounds().centerX(), 0,
+ BACKGROUND_APP_STATE_ORDINAL, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
+ }
+ } else {
+ executeAndWaitForEvent(
+ () -> {
+ log("LauncherInstrumentation.pressHome before clicking");
+ waitForSystemUiObject("home").click();
+ },
+ event -> true,
+ "Pressing Home didn't produce any events");
+ mDevice.waitForIdle();
+
+ // Temporarily press home twice as the first click sometimes gets ignored (b/124239413)
+ executeAndWaitForEvent(
+ () -> {
+ log("LauncherInstrumentation.pressHome before clicking");
+ waitForSystemUiObject("home").click();
+ },
+ event -> true,
+ "Pressing Home didn't produce any events");
+ mDevice.waitForIdle();
+ }
return getWorkspace();
}
@@ -359,8 +432,9 @@
}
@NonNull
- UiObject2 getSystemUiObject(String resId) {
- final UiObject2 object = mDevice.findObject(By.res(SYSTEMUI_PACKAGE, resId));
+ UiObject2 waitForSystemUiObject(String resId) {
+ final UiObject2 object = mDevice.wait(
+ Until.findObject(By.res(SYSTEMUI_PACKAGE, resId)), WAIT_TIME_MS);
assertNotNull("Can't find a systemui object with id: " + resId, object);
return object;
}
@@ -387,6 +461,11 @@
return object;
}
+ @Nullable
+ private boolean hasLauncherObject(String resId) {
+ return mDevice.hasObject(getLauncherObjectSelector(resId));
+ }
+
@NonNull
UiObject2 waitForLauncherObject(String resName) {
final BySelector selector = getLauncherObjectSelector(resName);
@@ -409,8 +488,12 @@
}
void swipe(int startX, int startY, int endX, int endY, int expectedState) {
+ swipe(startX, startY, endX, endY, expectedState, 60);
+ }
+
+ void swipe(int startX, int startY, int endX, int endY, int expectedState, int steps) {
final Bundle parcel = (Bundle) executeAndWaitForEvent(
- () -> mDevice.swipe(startX, startY, endX, endY, 60),
+ () -> mDevice.swipe(startX, startY, endX, endY, steps),
event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()),
"Swipe failed to receive an event for the swipe end: " + startX + ", " + startY
+ ", " + endX + ", " + endY);
@@ -422,14 +505,58 @@
mDevice.waitForIdle();
}
- void sendPointer(int action, Point point) {
- final MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
- SystemClock.uptimeMillis(), action, point.x, point.y, 0);
- mInstrumentation.sendPointerSync(event);
+ float getDisplayDensity() {
+ return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
+ }
+
+ int getTouchSlop() {
+ return ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
+ float x, float y) {
+ MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+ properties.id = 0;
+ properties.toolType = Configurator.getInstance().getToolType();
+
+ MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+ coords.pressure = 1;
+ coords.size = 1;
+ coords.x = x;
+ coords.y = y;
+
+ return MotionEvent.obtain(downTime, eventTime, action, 1,
+ new MotionEvent.PointerProperties[]{properties},
+ new MotionEvent.PointerCoords[]{coords},
+ 0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+ }
+
+ void sendPointer(long downTime, long currentTime, int action, Point point) {
+ final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
+ mInstrumentation.getUiAutomation().injectInputEvent(event, true);
event.recycle();
}
- float getDisplayDensity() {
- return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
+ void movePointer(long downTime, long duration, Point from, Point to) {
+ final Point point = new Point();
+ for (; ; ) {
+ sleep(16);
+
+ final long currentTime = SystemClock.uptimeMillis();
+ final float progress = (currentTime - downTime) / (float) duration;
+ if (progress > 1) return;
+
+ point.x = from.x + (int) (progress * (to.x - from.x));
+ point.y = from.y + (int) (progress * (to.y - from.y));
+
+ sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point);
+ }
+ }
+
+ static void sleep(int duration) {
+ try {
+ Thread.sleep(duration);
+ } catch (InterruptedException e) {
+ }
}
}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
index 0208144..5c8d5eb 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -50,7 +50,7 @@
verifyActiveContainer();
// Swipe from navbar to the top.
- final UiObject2 navBar = mLauncher.getSystemUiObject("navigation_bar_frame");
+ final UiObject2 navBar = mLauncher.waitForSystemUiObject("navigation_bar_frame");
final Point start = navBar.getVisibleCenter();
LauncherInstrumentation.log("Overview.switchToAllApps before swipe");
mLauncher.swipe(start.x, start.y, start.x, 0, ALL_APPS_STATE_ORDINAL);
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 5cd41f9..7d97acd 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -28,6 +28,8 @@
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.TestProtocol;
+
/**
* Operations on the workspace screen.
*/
@@ -49,15 +51,16 @@
@NonNull
public AllApps switchToAllApps() {
verifyActiveContainer();
- // Swipe from the hotseat to near the top, e.g. 10% of the screen.
final UiObject2 hotseat = mHotseat;
final Point start = hotseat.getVisibleCenter();
- final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f);
+ final int swipeHeight = mLauncher.getTestInfo(
+ TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT).
+ getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
mLauncher.swipe(
start.x,
start.y,
start.x,
- endY,
+ start.y - swipeHeight - mLauncher.getTouchSlop(),
ALL_APPS_STATE_ORDINAL
);
@@ -133,6 +136,8 @@
*/
public void flingForward() {
final UiObject2 workspace = verifyActiveContainer();
+ final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+ workspace.setGestureMargins(0, 0, margin, 0);
workspace.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
mLauncher.waitForIdle();
verifyActiveContainer();
@@ -144,6 +149,8 @@
*/
public void flingBackward() {
final UiObject2 workspace = verifyActiveContainer();
+ final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+ workspace.setGestureMargins(margin, 0, 0, 0);
workspace.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
mLauncher.waitForIdle();
verifyActiveContainer();
@@ -162,7 +169,12 @@
}
@Override
- protected int getSwipeLength() {
- return 100;
+ protected String getSwipeHeightRequestName() {
+ return TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT;
+ }
+
+ @Override
+ protected int getSwipeStartY() {
+ return mLauncher.waitForLauncherObject("hotseat").getVisibleBounds().top;
}
}
\ No newline at end of file