Merge "[Toast] Change toast qpr1 feature flags to all apps device config." into tm-qpr-dev
diff --git a/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
index 0c1f05f..0b17a7b 100644
--- a/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
+++ b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
@@ -15,24 +15,13 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
import android.content.Context;
-import android.content.res.Resources;
import android.os.Bundle;
import androidx.annotation.Nullable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
import com.android.launcher3.testing.DebugTestInformationHandler;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.quickstep.TouchInteractionService.TISBinder;
-import com.android.quickstep.util.TISBindHelper;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
/**
* Class to handle requests from tests, including debug ones, to Quickstep Launcher builds.
@@ -49,78 +38,14 @@
@Override
public Bundle call(String method, String arg, @Nullable Bundle extras) {
Bundle response = new Bundle();
- switch (method) {
- case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING:
- runOnTISBinder(tisBinder -> {
- enableManualTaskbarStashing(tisBinder, true);
- });
- return response;
-
- case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING:
- runOnTISBinder(tisBinder -> {
- enableManualTaskbarStashing(tisBinder, false);
- });
- return response;
-
- case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED:
- runOnTISBinder(tisBinder -> {
- enableManualTaskbarStashing(tisBinder, true);
-
- // Allow null-pointer to catch illegal states.
- tisBinder.getTaskbarManager().getCurrentActivityContext()
- .unstashTaskbarIfStashed();
-
- enableManualTaskbarStashing(tisBinder, false);
- });
- return response;
-
- case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: {
- final Resources resources = mContext.getResources();
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size));
- return response;
- }
-
- case TestProtocol.REQUEST_RECREATE_TASKBAR:
- // Allow null-pointer to catch illegal states.
- runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
- return response;
-
- default:
- response = super.call(method, arg, extras);
- if (response != null) return response;
- return mDebugTestInformationHandler.call(method, arg, extras);
+ if (TestProtocol.REQUEST_RECREATE_TASKBAR.equals(method)) {
+ // Allow null-pointer to catch illegal states.
+ runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
+ return response;
}
- }
-
- private void enableManualTaskbarStashing(TISBinder tisBinder, boolean enable) {
- // Allow null-pointer to catch illegal states.
- tisBinder.getTaskbarManager().getCurrentActivityContext().enableManualStashingForTests(
- enable);
- }
-
- /**
- * Runs the given command on the UI thread, after ensuring we are connected to
- * TouchInteractionService.
- */
- private void runOnTISBinder(Consumer<TISBinder> connectionCallback) {
- try {
- CountDownLatch countDownLatch = new CountDownLatch(1);
- TISBindHelper helper = MAIN_EXECUTOR.submit(() ->
- new TISBindHelper(mContext, tisBinder -> {
- connectionCallback.accept(tisBinder);
- countDownLatch.countDown();
- })).get();
- countDownLatch.await();
- MAIN_EXECUTOR.submit(helper::onDestroy);
- } catch (ExecutionException | InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
-
- private interface UIThreadCommand {
-
- void execute(Launcher launcher);
+ response = super.call(method, arg, extras);
+ if (response != null) return response;
+ return mDebugTestInformationHandler.call(method, arg, extras);
}
}
diff --git a/quickstep/res/drawable/ic_floating_task_button.xml b/quickstep/res/drawable/ic_floating_task_button.xml
new file mode 100644
index 0000000..e50f65c
--- /dev/null
+++ b/quickstep/res/drawable/ic_floating_task_button.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z"
+ android:fillColor="#636C6F"/>
+ <path
+ android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z"
+ android:fillColor="#636C6F"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 351a3bc..c54d119 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -33,6 +33,7 @@
import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
@@ -117,9 +118,14 @@
@Override
public int getExpectedHeight() {
- return getVisibility() == GONE ? 0
- : mActivityContext.getDeviceProfile().allAppsCellHeightPx + getPaddingTop()
- + getPaddingBottom();
+ DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+ int iconHeight = deviceProfile.allAppsIconSizePx;
+ int iconPadding = deviceProfile.allAppsIconDrawablePaddingPx;
+ int textHeight = Utilities.calculateTextHeight(deviceProfile.allAppsIconTextSizePx);
+ int verticalPadding = getResources().getDimensionPixelSize(
+ R.dimen.all_apps_predicted_icon_vertical_padding);
+ int totalHeight = iconHeight + iconPadding + textHeight + verticalPadding * 2;
+ return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
index 9a682c6..7c3281a 100644
--- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
+++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
@@ -55,6 +55,7 @@
@Override
public void onClick(View view) {
+ // Initiate splitscreen from the Home screen or Home All Apps
Bitmap bitmap;
Intent intent;
if (mItemInfo instanceof WorkspaceItemInfo) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java
new file mode 100644
index 0000000..5f4d239
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar;
+
+import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.R;
+
+// TODO: This would be replaced by the thing that has the role and provides the intent.
+/**
+ * Helper to determine what intent should be used to display in a floating window, if one
+ * exists.
+ */
+public class FloatingTaskIntentResolver {
+ private static final String TAG = FloatingTaskIntentResolver.class.getSimpleName();
+
+ @Nullable
+ /** Gets an intent for a floating task, if one exists. */
+ public static Intent getIntent(Context context) {
+ PackageManager pm = context.getPackageManager();
+ String pkg = context.getString(R.string.floating_task_package);
+ String action = context.getString(R.string.floating_task_action);
+ if (TextUtils.isEmpty(pkg) || TextUtils.isEmpty(action)) {
+ Log.d(TAG, "intent could not be found, pkg= " + pkg + " action= " + action);
+ return null;
+ }
+ Intent intent = createIntent(pm, null, pkg, action);
+ if (intent != null) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+ Log.d(TAG, "No valid intent found!");
+ return null;
+ }
+
+ @Nullable
+ private static Intent createIntent(PackageManager pm, @Nullable String activityName,
+ String packageName, String action) {
+ if (TextUtils.isEmpty(activityName)) {
+ activityName = queryActivityForAction(pm, packageName, action);
+ }
+ if (TextUtils.isEmpty(activityName)) {
+ Log.d(TAG, "Activity name is empty even after action search: " + action);
+ return null;
+ }
+ ComponentName component = new ComponentName(packageName, activityName);
+ Intent intent = new Intent(action).setComponent(component).setPackage(packageName);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Log.d(TAG, "createIntent returning: " + intent);
+ return intent;
+ }
+
+ @Nullable
+ private static String queryActivityForAction(PackageManager pm, String packageName,
+ String action) {
+ Intent intent = new Intent(action).setPackage(packageName);
+ ResolveInfo resolveInfo = pm.resolveActivity(intent, MATCH_DEFAULT_ONLY);
+ if (resolveInfo == null || resolveInfo.activityInfo == null) {
+ Log.d(TAG, "queryActivityForAction: + " + resolveInfo);
+ return null;
+ }
+ ActivityInfo info = resolveInfo.activityInfo;
+ if (!info.exported) {
+ Log.d(TAG, "queryActivityForAction: + " + info + " not exported");
+ return null;
+ }
+ if (!info.enabled) {
+ Log.d(TAG, "queryActivityForAction: + " + info + " not enabled");
+ return null;
+ }
+ return resolveInfo.activityInfo.name;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java b/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java
new file mode 100644
index 0000000..b15669b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.icons.FastBitmapDrawable;
+
+/**
+ * Button in Taskbar that opens something in a floating task.
+ */
+public class LaunchFloatingTaskButton extends BubbleTextView {
+
+ public LaunchFloatingTaskButton(Context context) {
+ this(context, null);
+ }
+
+ public LaunchFloatingTaskButton(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LaunchFloatingTaskButton(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ Context theme = new ContextThemeWrapper(context, R.style.AllAppsButtonTheme);
+ Bitmap bitmap = LauncherAppState.getInstance(context).getIconCache().getIconFactory()
+ .createScaledBitmapWithShadow(
+ theme.getDrawable(R.drawable.ic_floating_task_button));
+ setIcon(new FastBitmapDrawable(bitmap));
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8697b69..9d15ea8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -778,8 +778,8 @@
* stash/unstash the taskbar.
*/
@VisibleForTesting
- public void enableManualStashingForTests(boolean enableManualStashing) {
- mControllers.taskbarStashController.enableManualStashingForTests(enableManualStashing);
+ public void enableManualStashingDuringTests(boolean enableManualStashing) {
+ mControllers.taskbarStashController.enableManualStashingDuringTests(enableManualStashing);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 49d0873..da6dab1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -270,6 +270,7 @@
@Override
public void onClick(View view) {
+ // Initiate splitscreen from the in-app Taskbar or Taskbar All Apps
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
LogUtils.getShellShareableInstanceId();
mTarget.getStatsLogManager().logger()
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index d60bf8c..4b0adb1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -49,8 +49,6 @@
import com.android.quickstep.SystemUiProxy;
import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.Optional;
import java.util.StringJoiner;
import java.util.function.IntPredicate;
@@ -162,7 +160,7 @@
private boolean mIsImeShowing;
private boolean mIsImeSwitcherShowing;
- private boolean mEnableManualStashingForTests = false;
+ private boolean mEnableManualStashingDuringTests = false;
// Evaluate whether the handle should be stashed
private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
@@ -242,15 +240,15 @@
*/
protected boolean supportsManualStashing() {
return supportsVisualStashing()
- && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingForTests);
+ && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingDuringTests);
}
/**
* Enables support for manual stashing. This should only be used to add this functionality
* to Launcher specific tests.
*/
- public void enableManualStashingForTests(boolean enableManualStashing) {
- mEnableManualStashingForTests = enableManualStashing;
+ public void enableManualStashingDuringTests(boolean enableManualStashing) {
+ mEnableManualStashingDuringTests = enableManualStashing;
}
/**
@@ -546,13 +544,7 @@
}
private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
- Optional<View> optionalView =
- Arrays.stream(mControllers.taskbarViewController.getIconViews()).findFirst();
- if (optionalView.isEmpty()) {
- Log.wtf(TAG, "No views to start Interaction jank monitor with.", new Exception());
- return;
- }
- View v = optionalView.get();
+ View v = mControllers.taskbarActivityContext.getDragLayer();
int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
animator.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index dbf9759..bb82d19 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -16,10 +16,13 @@
package com.android.launcher3.taskbar;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.os.SystemProperties;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -53,6 +56,7 @@
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
*/
public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable {
+ private static final String TAG = TaskbarView.class.getSimpleName();
private static final float TASKBAR_BACKGROUND_LUMINANCE = 0.30f;
public int mThemeIconsBackground;
@@ -81,6 +85,12 @@
private View mQsb;
+ // Only non-null when device supports having a floating task.
+ private @Nullable BubbleTextView mFloatingTaskButton;
+ private @Nullable Intent mFloatingTaskIntent;
+ private static final boolean FLOATING_TASKS_ENABLED =
+ SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
+
public TaskbarView(@NonNull Context context) {
this(context, null);
}
@@ -123,6 +133,19 @@
// TODO: Disable touch events on QSB otherwise it can crash.
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+
+ if (FLOATING_TASKS_ENABLED) {
+ mFloatingTaskIntent = FloatingTaskIntentResolver.getIntent(context);
+ if (mFloatingTaskIntent != null) {
+ mFloatingTaskButton = new LaunchFloatingTaskButton(context);
+ mFloatingTaskButton.setLayoutParams(
+ new ViewGroup.LayoutParams(mIconTouchSize, mIconTouchSize));
+ mFloatingTaskButton.setPadding(mItemPadding, mItemPadding, mItemPadding,
+ mItemPadding);
+ } else {
+ Log.d(TAG, "Floating tasks is enabled but no intent was found!");
+ }
+ }
}
private int getColorWithGivenLuminance(int color, float luminance) {
@@ -150,6 +173,10 @@
if (mAllAppsButton != null) {
mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener());
}
+ if (mFloatingTaskButton != null) {
+ mFloatingTaskButton.setOnClickListener(
+ mControllerCallbacks.getFloatingTaskButtonListener(mFloatingTaskIntent));
+ }
}
private void removeAndRecycle(View view) {
@@ -174,6 +201,10 @@
}
removeView(mQsb);
+ if (mFloatingTaskButton != null) {
+ removeView(mFloatingTaskButton);
+ }
+
for (int i = 0; i < hotseatItemInfos.length; i++) {
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
if (hotseatItemInfo == null) {
@@ -255,6 +286,11 @@
mQsb.setVisibility(View.INVISIBLE);
}
+ if (mFloatingTaskButton != null) {
+ int index = Utilities.isRtl(getResources()) ? 0 : getChildCount();
+ addView(mFloatingTaskButton, index);
+ }
+
mThemeIconsBackground = calculateThemeIconsBackground();
setThemedIconsBackgroundColor(mThemeIconsBackground);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 992aa4b..00d5083 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -24,6 +24,7 @@
import static com.android.quickstep.AnimatedFloat.VALUE;
import android.annotation.NonNull;
+import android.content.Intent;
import android.graphics.Rect;
import android.util.FloatProperty;
import android.util.Log;
@@ -51,6 +52,7 @@
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SystemUiProxy;
import java.io.PrintWriter;
import java.util.function.Predicate;
@@ -427,6 +429,13 @@
};
}
+ public View.OnClickListener getFloatingTaskButtonListener(@NonNull Intent intent) {
+ return v -> {
+ SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(v.getContext());
+ proxy.showFloatingTask(intent);
+ };
+ }
+
public View.OnLongClickListener getIconOnLongClickListener() {
return mControllers.taskbarDragController::startDragOnLongClick;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 2e4e739..8fb7030 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -88,6 +88,11 @@
return;
}
setStateWithAnimationInternal(toState, config, builder);
+ builder.addEndListener(success -> {
+ if (!success) {
+ mRecentsView.reset();
+ }
+ });
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 1316ddd..192ac62 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -895,7 +895,7 @@
// load in, and then proceed to OverviewSplitSelect.
if (isInState(OVERVIEW_SPLIT_SELECT)) {
SplitSelectStateController splitSelectStateController =
- ((RecentsView) getOverviewPanel()).getSplitPlaceholder();
+ ((RecentsView) getOverviewPanel()).getSplitSelectController();
// Launcher will restart in Overview and then transition to OverviewSplitSelect.
outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap(
new PendingSplitSelectInfo(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index f5161aa..f75a404 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -34,6 +34,7 @@
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_75;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
@@ -62,6 +63,7 @@
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
+import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.RecentsView;
/**
@@ -79,14 +81,6 @@
private static final int PER_PAGE_SCROLL_DURATION = 150;
private static final int MAX_PAGE_SCROLL_DURATION = 750;
- private static final int OVERVIEW_TO_SPLIT_ACTIONS_FADE_START = 0;
- private static final int OVERVIEW_TO_SPLIT_ACTIONS_FADE_END = 83;
-
- private static final float OVERVIEW_TO_SPLIT_ACTIONS_FADE_START_OFFSET =
- (float) OVERVIEW_TO_SPLIT_ACTIONS_FADE_START / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_ACTIONS_FADE_END_OFFSET =
- (float) OVERVIEW_TO_SPLIT_ACTIONS_FADE_END / SplitScreenSelectState.ENTER_DURATION;
-
// Due to use of physics, duration may differ between devices so we need to calculate and
// cache the value.
private int mHintToNormalDuration = -1;
@@ -197,9 +191,17 @@
} else if (fromState == NORMAL && toState == ALL_APPS) {
AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mActivity, config);
} else if (fromState == OVERVIEW && toState == OVERVIEW_SPLIT_SELECT) {
+ SplitAnimationTimings timings = SplitAnimationTimings.OVERVIEW_TO_SPLIT;
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR,
- OVERVIEW_TO_SPLIT_ACTIONS_FADE_START_OFFSET,
- OVERVIEW_TO_SPLIT_ACTIONS_FADE_END_OFFSET));
+ timings.getActionsFadeStartOffset(),
+ timings.getActionsFadeEndOffset()));
+ } else if (fromState == NORMAL && toState == OVERVIEW_SPLIT_SELECT) {
+ SplitAnimationTimings timings = SplitAnimationTimings.NORMAL_TO_SPLIT;
+ config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR,
+ timings.getScrimFadeInStartOffset(),
+ timings.getScrimFadeInEndOffset()));
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_0_75);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_0_75);
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 2bc3c3e..430053d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -19,6 +19,7 @@
import android.content.Context;
import com.android.launcher3.Launcher;
+import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.RecentsView;
/**
@@ -26,10 +27,6 @@
* pinned and user is selecting the second one
*/
public class SplitScreenSelectState extends OverviewState {
- public static final int ENTER_DURATION = 866;
- public static final int EXIT_DURATION = 500;
- // TODO: Add ability to differentiate between Split > Home and Split > Confirmed timings
-
public SplitScreenSelectState(int id) {
super(id);
}
@@ -47,6 +44,8 @@
@Override
public int getTransitionDuration(Context context, boolean isToState) {
- return isToState ? ENTER_DURATION : EXIT_DURATION;
+ return isToState
+ ? SplitAnimationTimings.ENTER_DURATION
+ : SplitAnimationTimings.ABORT_DURATION;
}
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 57a26ee..875b72c 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -240,8 +240,11 @@
interactionHandler.onGestureCancelled();
cmd.removeListener(this);
- RecentsView createdRecents =
- activityInterface.getCreatedActivity().getOverviewPanel();
+ T createdActivity = activityInterface.getCreatedActivity();
+ if (createdActivity == null) {
+ return;
+ }
+ RecentsView createdRecents = createdActivity.getOverviewPanel();
if (createdRecents != null) {
createdRecents.onRecentsAnimationComplete();
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 9bd0cb9..b7cdecd 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,16 +1,25 @@
package com.android.quickstep;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.app.Activity;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Bundle;
import androidx.annotation.Nullable;
+import com.android.launcher3.R;
import com.android.launcher3.testing.TestInformationHandler;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.TISBindHelper;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
public class QuickstepTestInformationHandler extends TestInformationHandler {
@@ -72,6 +81,37 @@
TestProtocol.REQUEST_HAS_TIS, true);
return response;
}
+
+ case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING:
+ runOnTISBinder(tisBinder -> {
+ enableManualTaskbarStashing(tisBinder, true);
+ });
+ return response;
+
+ case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING:
+ runOnTISBinder(tisBinder -> {
+ enableManualTaskbarStashing(tisBinder, false);
+ });
+ return response;
+
+ case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED:
+ runOnTISBinder(tisBinder -> {
+ enableManualTaskbarStashing(tisBinder, true);
+
+ // Allow null-pointer to catch illegal states.
+ tisBinder.getTaskbarManager().getCurrentActivityContext()
+ .unstashTaskbarIfStashed();
+
+ enableManualTaskbarStashing(tisBinder, false);
+ });
+ return response;
+
+ case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: {
+ final Resources resources = mContext.getResources();
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size));
+ return response;
+ }
}
return super.call(method, arg, extras);
@@ -93,4 +133,30 @@
protected boolean isLauncherInitialized() {
return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
}
+
+ private void enableManualTaskbarStashing(
+ TouchInteractionService.TISBinder tisBinder, boolean enable) {
+ // Allow null-pointer to catch illegal states.
+ tisBinder.getTaskbarManager().getCurrentActivityContext().enableManualStashingDuringTests(
+ enable);
+ }
+
+ /**
+ * Runs the given command on the UI thread, after ensuring we are connected to
+ * TouchInteractionService.
+ */
+ protected void runOnTISBinder(Consumer<TouchInteractionService.TISBinder> connectionCallback) {
+ try {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ TISBindHelper helper = MAIN_EXECUTOR.submit(() ->
+ new TISBindHelper(mContext, tisBinder -> {
+ connectionCallback.accept(tisBinder);
+ countDownLatch.countDown();
+ })).get();
+ countDownLatch.await();
+ MAIN_EXECUTOR.execute(helper::onDestroy);
+ } catch (ExecutionException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 24d2b9b..dcf685a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -58,6 +58,7 @@
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.SmartspaceState;
import com.android.wm.shell.back.IBackAnimation;
+import com.android.wm.shell.floating.IFloatingTasks;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.pip.IPipAnimationListener;
@@ -88,6 +89,7 @@
private IPip mPip;
private ISysuiUnlockAnimationController mSysuiUnlockAnimationController;
private ISplitScreen mSplitScreen;
+ private IFloatingTasks mFloatingTasks;
private IOneHanded mOneHanded;
private IShellTransitions mShellTransitions;
private IStartingWindow mStartingWindow;
@@ -164,7 +166,7 @@
}
public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
- IOneHanded oneHanded, IShellTransitions shellTransitions,
+ IFloatingTasks floatingTasks, IOneHanded oneHanded, IShellTransitions shellTransitions,
IStartingWindow startingWindow, IRecentTasks recentTasks,
ISysuiUnlockAnimationController sysuiUnlockAnimationController,
IBackAnimation backAnimation) {
@@ -172,6 +174,7 @@
mSystemUiProxy = proxy;
mPip = pip;
mSplitScreen = splitScreen;
+ mFloatingTasks = floatingTasks;
mOneHanded = oneHanded;
mShellTransitions = shellTransitions;
mStartingWindow = startingWindow;
@@ -204,7 +207,7 @@
}
public void clearProxy() {
- setProxy(null, null, null, null, null, null, null, null, null);
+ setProxy(null, null, null, null, null, null, null, null, null, null);
}
// TODO(141886704): Find a way to remove this
@@ -663,6 +666,20 @@
}
//
+ // Floating tasks
+ //
+
+ public void showFloatingTask(Intent intent) {
+ if (mFloatingTasks != null) {
+ try {
+ mFloatingTasks.showTask(intent);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Launcher: Failed call showFloatingTask", e);
+ }
+ }
+ }
+
+ //
// One handed
//
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 4923948..e207a1b 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -28,6 +28,7 @@
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
@@ -110,6 +111,7 @@
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.wm.shell.back.IBackAnimation;
+import com.android.wm.shell.floating.IFloatingTasks;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.recents.IRecentTasks;
@@ -167,6 +169,8 @@
IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
KEY_EXTRA_SHELL_SPLIT_SCREEN));
+ IFloatingTasks floatingTasks = IFloatingTasks.Stub.asInterface(bundle.getBinder(
+ KEY_EXTRA_SHELL_FLOATING_TASKS));
IOneHanded onehanded = IOneHanded.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));
IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
@@ -182,8 +186,8 @@
bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
MAIN_EXECUTOR.execute(() -> {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
- splitscreen, onehanded, shellTransitions, startingWindow, recentTasks,
- launcherUnlockAnimationController, backAnimation);
+ splitscreen, floatingTasks, onehanded, shellTransitions, startingWindow,
+ recentTasks, launcherUnlockAnimationController, backAnimation);
TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
preloadOverview(true /* fromInit */);
});
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 1e0ceed..19a6c38 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -78,6 +78,11 @@
}
// While animating into recents, update the visible task data as needed
setter.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
+ setter.addEndListener(success -> {
+ if (!success) {
+ mRecentsView.reset();
+ }
+ });
mRecentsView.updateEmptyMessage();
setProperties(toState, config, setter);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 93eb0f1..e32aaee 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -232,11 +232,6 @@
}
@Override
- public void onStateTransitionFailed(RecentsState toState) {
- reset();
- }
-
- @Override
public void onStateTransitionComplete(RecentsState finalState) {
if (finalState == HOME) {
// Clean-up logic that occurs when recents is no longer in use/visible.
diff --git a/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java b/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java
new file mode 100644
index 0000000..1bf5a5d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Normal > OverviewSplitSelect animation.
+ */
+public class NormalToSplitTimings extends OverviewToSplitTimings implements SplitAnimationTimings {
+ @Override
+ public Interpolator getStagedRectXInterpolator() { return LINEAR; }
+ @Override
+ public Interpolator getStagedRectScaleXInterpolator() { return LINEAR; }
+ @Override
+ public Interpolator getStagedRectScaleYInterpolator() { return LINEAR; }
+
+ public int getScrimFadeInStart() { return 0; }
+ public int getScrimFadeInEnd() { return 167; }
+
+ public float getScrimFadeInStartOffset() {
+ return (float) getScrimFadeInStart() / getDuration();
+ }
+ public float getScrimFadeInEndOffset() {
+ return (float) getScrimFadeInEnd() / getDuration();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
new file mode 100644
index 0000000..f44796b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Overview > OverviewSplitSelect animation.
+ */
+public class OverviewToSplitTimings implements SplitAnimationTimings {
+ public int getPlaceholderFadeInStart() { return 0; }
+ public int getPlaceholderFadeInEnd() { return 133; }
+ public int getPlaceholderIconFadeInStart() { return 167; }
+ public int getPlaceholderIconFadeInEnd() { return 250; }
+ public int getStagedRectSlideStart() { return 0; }
+ public int getStagedRectSlideEnd() { return 417; }
+ public int getGridSlideStart() { return 67; }
+ public int getGridSlideStagger() { return 16; }
+ public int getGridSlideDuration() { return 500; }
+ public int getActionsFadeStart() { return 0; }
+ public int getActionsFadeEnd() { return 83; }
+ public int getIconFadeStart() { return 0; }
+ public int getIconFadeEnd() { return 83; }
+ public int getInstructionsContainerFadeInStart() { return 167; }
+ public int getInstructionsContainerFadeInEnd() { return 250; }
+ public int getInstructionsTextFadeInStart() { return 217; }
+ public int getInstructionsTextFadeInEnd() { return 300; }
+ public int getInstructionsUnfoldStart() { return 167; }
+ public int getInstructionsUnfoldEnd() { return 500; }
+
+ public int getDuration() { return ENTER_DURATION; }
+ public Interpolator getStagedRectXInterpolator() { return DEACCEL_2; }
+ public Interpolator getStagedRectYInterpolator() { return DEACCEL_2; }
+ public Interpolator getStagedRectScaleXInterpolator() { return DEACCEL_2; }
+ public Interpolator getStagedRectScaleYInterpolator() { return DEACCEL_2; }
+
+ public float getGridSlideStartOffset() {
+ return (float) getGridSlideStart() / getDuration();
+ }
+ public float getGridSlideStaggerOffset() {
+ return (float) getGridSlideStagger() / getDuration();
+ }
+ public float getGridSlideDurationOffset() {
+ return (float) getGridSlideDuration() / getDuration();
+ }
+ public float getActionsFadeStartOffset() {
+ return (float) getActionsFadeStart() / getDuration();
+ }
+ public float getActionsFadeEndOffset() {
+ return (float) getActionsFadeEnd() / getDuration();
+ }
+ public float getIconFadeStartOffset() {
+ return (float) getIconFadeStart() / getDuration();
+ }
+ public float getIconFadeEndOffset() {
+ return (float) getIconFadeEnd() / getDuration();
+ }
+ public float getInstructionsContainerFadeInStartOffset() {
+ return (float) getInstructionsContainerFadeInStart() / getDuration();
+ }
+ public float getInstructionsContainerFadeInEndOffset() {
+ return (float) getInstructionsContainerFadeInEnd() / getDuration();
+ }
+ public float getInstructionsTextFadeInStartOffset() {
+ return (float) getInstructionsTextFadeInStart() / getDuration();
+ }
+ public float getInstructionsTextFadeInEndOffset() {
+ return (float) getInstructionsTextFadeInEnd() / getDuration();
+ }
+ public float getInstructionsUnfoldStartOffset() {
+ return (float) getInstructionsUnfoldStart() / getDuration();
+ }
+ public float getInstructionsUnfoldEndOffset() {
+ return (float) getInstructionsUnfoldEnd() / getDuration();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
new file mode 100644
index 0000000..133be03
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import android.view.animation.Interpolator;
+
+/**
+ * An interface that supports the centralization of timing information for splitscreen animations.
+ */
+public interface SplitAnimationTimings {
+ int ENTER_DURATION = 866;
+ int ABORT_DURATION = 500;
+ int CONFIRM_DURATION = 383;
+
+ SplitAnimationTimings OVERVIEW_TO_SPLIT = new OverviewToSplitTimings();
+ SplitAnimationTimings NORMAL_TO_SPLIT = new NormalToSplitTimings();
+ SplitAnimationTimings SPLIT_TO_CONFIRM = new SplitToConfirmTimings();
+
+ // Shared methods
+ int getDuration();
+ int getPlaceholderFadeInStart();
+ int getPlaceholderFadeInEnd();
+ int getPlaceholderIconFadeInStart();
+ int getPlaceholderIconFadeInEnd();
+ int getStagedRectSlideStart();
+ int getStagedRectSlideEnd();
+ Interpolator getStagedRectXInterpolator();
+ Interpolator getStagedRectYInterpolator();
+ Interpolator getStagedRectScaleXInterpolator();
+ Interpolator getStagedRectScaleYInterpolator();
+ default float getPlaceholderFadeInStartOffset() {
+ return (float) getPlaceholderFadeInStart() / getDuration();
+ }
+ default float getPlaceholderFadeInEndOffset() {
+ return (float) getPlaceholderFadeInEnd() / getDuration();
+ }
+ default float getPlaceholderIconFadeInStartOffset() {
+ return (float) getPlaceholderIconFadeInStart() / getDuration();
+ }
+ default float getPlaceholderIconFadeInEndOffset() {
+ return (float) getPlaceholderIconFadeInEnd() / getDuration();
+ }
+ default float getStagedRectSlideStartOffset() {
+ return (float) getStagedRectSlideStart() / getDuration();
+ }
+ default float getStagedRectSlideEndOffset() {
+ return (float) getStagedRectSlideEnd() / getDuration();
+ }
+
+ // Defaults for OverviewToSplit
+ default float getGridSlideStartOffset() { return 0; }
+ default float getGridSlideStaggerOffset() { return 0; }
+ default float getGridSlideDurationOffset() { return 0; }
+ default float getActionsFadeStartOffset() { return 0; }
+ default float getActionsFadeEndOffset() { return 0; }
+ default float getIconFadeStartOffset() { return 0; }
+ default float getIconFadeEndOffset() { return 0; }
+ default float getInstructionsContainerFadeInStartOffset() { return 0; }
+ default float getInstructionsContainerFadeInEndOffset() { return 0; }
+ default float getInstructionsTextFadeInStartOffset() { return 0; }
+ default float getInstructionsTextFadeInEndOffset() { return 0; }
+ default float getInstructionsUnfoldStartOffset() { return 0; }
+ default float getInstructionsUnfoldEndOffset() { return 0; }
+
+ // Defaults for NormalToSplit
+ default float getScrimFadeInStartOffset() { return 0; }
+ default float getScrimFadeInEndOffset() { return 0; }
+
+ // Defaults for SplitToConfirm
+ default float getInstructionsFadeStartOffset() { return 0; }
+ default float getInstructionsFadeEndOffset() { return 0; }
+}
+
diff --git a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
new file mode 100644
index 0000000..84cfb0b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the OverviewSplitSelect > confirmed animation.
+ */
+public class SplitToConfirmTimings implements SplitAnimationTimings {
+ public int getPlaceholderFadeInStart() { return 0; }
+ public int getPlaceholderFadeInEnd() { return 133; }
+ public int getPlaceholderIconFadeInStart() { return 167; }
+ public int getPlaceholderIconFadeInEnd() { return 250; }
+ public int getStagedRectSlideStart() { return 0; }
+ public int getStagedRectSlideEnd() { return 383; }
+ public int getInstructionsFadeStart() { return 0; }
+ public int getInstructionsFadeEnd() { return 67; }
+
+ public int getDuration() { return CONFIRM_DURATION; }
+ public Interpolator getStagedRectXInterpolator() { return LINEAR; }
+ public Interpolator getStagedRectYInterpolator() { return LINEAR; }
+ public Interpolator getStagedRectScaleXInterpolator() { return LINEAR; }
+ public Interpolator getStagedRectScaleYInterpolator() { return LINEAR; }
+
+ public float getInstructionsFadeStartOffset() {
+ return (float) getInstructionsFadeStart() / getDuration();
+ }
+ public float getInstructionsFadeEndOffset() {
+ return (float) getInstructionsFadeEnd() / getDuration();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ViewCapture.java b/quickstep/src/com/android/quickstep/util/ViewCapture.java
index 6071bc8..cfcfce0 100644
--- a/quickstep/src/com/android/quickstep/util/ViewCapture.java
+++ b/quickstep/src/com/android/quickstep/util/ViewCapture.java
@@ -67,6 +67,10 @@
private static final String TAG = "ViewCapture";
+ // These flags are copies of two private flags in the View class.
+ private static final int PFLAG_INVALIDATED = 0x80000000;
+ private static final int PFLAG_DIRTY_MASK = 0x00200000;
+
// Number of frames to keep in memory
private static final int MEMORY_SIZE = 2000;
// Initial size of the reference pool. This is at least be 5 * total number of views in
@@ -187,6 +191,7 @@
private final ViewRef mViewRef = new ViewRef();
private int mFrameIndexBg = -1;
+ private boolean mIsFirstFrame = true;
private final long[] mFrameTimesBg = new long[MEMORY_SIZE];
private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE];
@@ -205,6 +210,7 @@
Message m = Message.obtain(mHandler);
m.obj = mViewRef.next;
mHandler.sendMessage(m);
+ mIsFirstFrame = false;
Trace.endSection();
}
@@ -214,9 +220,9 @@
*/
@WorkerThread
private boolean captureViewPropertiesBg(Message msg) {
- ViewRef start = (ViewRef) msg.obj;
+ ViewRef viewRefStart = (ViewRef) msg.obj;
long time = msg.getWhen();
- if (start == null) {
+ if (viewRefStart == null) {
return false;
}
mFrameIndexBg++;
@@ -227,12 +233,11 @@
ViewPropertyRef recycle = mNodesBg[mFrameIndexBg];
- ViewPropertyRef result = null;
+ ViewPropertyRef resultStart = null;
ViewPropertyRef resultEnd = null;
- ViewRef current = start;
- ViewRef last = start;
- while (current != null) {
+ ViewRef viewRefEnd = viewRefStart;
+ while (viewRefEnd != null) {
ViewPropertyRef propertyRef = recycle;
if (propertyRef == null) {
propertyRef = new ViewPropertyRef();
@@ -241,24 +246,64 @@
propertyRef.next = null;
}
- propertyRef.transfer(current);
- last = current;
- current = current.next;
+ ViewPropertyRef copy = null;
+ if (viewRefEnd.childCount < 0) {
+ copy = findInLastFrame(viewRefEnd.view.hashCode());
+ viewRefEnd.childCount = (copy != null) ? copy.childCount : 0;
+ }
+ viewRefEnd.transferTo(propertyRef);
- if (result == null) {
- result = propertyRef;
- resultEnd = result;
+ if (resultStart == null) {
+ resultStart = propertyRef;
+ resultEnd = resultStart;
} else {
resultEnd.next = propertyRef;
- resultEnd = propertyRef;
+ resultEnd = resultEnd.next;
}
+
+ if (copy != null) {
+ int pending = copy.childCount;
+ while (pending > 0) {
+ copy = copy.next;
+ pending = pending - 1 + copy.childCount;
+
+ propertyRef = recycle;
+ if (propertyRef == null) {
+ propertyRef = new ViewPropertyRef();
+ } else {
+ recycle = recycle.next;
+ propertyRef.next = null;
+ }
+
+ copy.transferTo(propertyRef);
+
+ resultEnd.next = propertyRef;
+ resultEnd = resultEnd.next;
+ }
+ }
+
+ if (viewRefEnd.next == null) {
+ // The compiler will complain about using a non-final variable from
+ // an outer class in a lambda if we pass in viewRefEnd directly.
+ final ViewRef finalViewRefEnd = viewRefEnd;
+ MAIN_EXECUTOR.execute(() -> addToPool(viewRefStart, finalViewRefEnd));
+ break;
+ }
+ viewRefEnd = viewRefEnd.next;
}
- mNodesBg[mFrameIndexBg] = result;
- ViewRef end = last;
- MAIN_EXECUTOR.execute(() -> addToPool(start, end));
+ mNodesBg[mFrameIndexBg] = resultStart;
return true;
}
+ private ViewPropertyRef findInLastFrame(int hashCode) {
+ int lastFrameIndex = (mFrameIndexBg == 0) ? MEMORY_SIZE - 1 : mFrameIndexBg - 1;
+ ViewPropertyRef viewPropertyRef = mNodesBg[lastFrameIndex];
+ while (viewPropertyRef != null && viewPropertyRef.hashCode != hashCode) {
+ viewPropertyRef = viewPropertyRef.next;
+ }
+ return viewPropertyRef;
+ }
+
void attachToRoot() {
if (mRoot.isAttachedToWindow()) {
mRoot.getViewTreeObserver().addOnDrawListener(this);
@@ -314,8 +359,16 @@
ref.view = view;
start.next = ref;
if (view instanceof ViewGroup) {
- ViewRef result = ref;
ViewGroup parent = (ViewGroup) view;
+ // If a view has not changed since the last frame, we will copy
+ // its children from the last processed frame's data.
+ if ((view.mPrivateFlags & (PFLAG_INVALIDATED | PFLAG_DIRTY_MASK)) == 0
+ && !mIsFirstFrame) {
+ // A negative child count is the signal to copy this view from the last frame.
+ ref.childCount = -parent.getChildCount();
+ return ref;
+ }
+ ViewRef result = ref;
int childCount = ref.childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
result = captureViewTree(parent.getChildAt(i), result);
@@ -349,31 +402,27 @@
public ViewPropertyRef next;
- public void transfer(ViewRef viewRef) {
- childCount = viewRef.childCount;
-
- View view = viewRef.view;
- viewRef.view = null;
-
- clazz = view.getClass();
- hashCode = view.hashCode();
- id = view.getId();
- left = view.getLeft();
- top = view.getTop();
- right = view.getRight();
- bottom = view.getBottom();
- scrollX = view.getScrollX();
- scrollY = view.getScrollY();
-
- translateX = view.getTranslationX();
- translateY = view.getTranslationY();
- scaleX = view.getScaleX();
- scaleY = view.getScaleY();
- alpha = view.getAlpha();
-
- visibility = view.getVisibility();
- willNotDraw = view.willNotDraw();
- elevation = view.getElevation();
+ public void transferTo(ViewPropertyRef out) {
+ out.clazz = this.clazz;
+ out.hashCode = this.hashCode;
+ out.childCount = this.childCount;
+ out.id = this.id;
+ out.left = this.left;
+ out.top = this.top;
+ out.right = this.right;
+ out.bottom = this.bottom;
+ out.scrollX = this.scrollX;
+ out.scrollY = this.scrollY;
+ out.scaleX = this.scaleX;
+ out.scaleY = this.scaleY;
+ out.translateX = this.translateX;
+ out.translateY = this.translateY;
+ out.alpha = this.alpha;
+ out.visibility = this.visibility;
+ out.willNotDraw = this.willNotDraw;
+ out.clipChildren = this.clipChildren;
+ out.next = this.next;
+ out.elevation = this.elevation;
}
/**
@@ -420,6 +469,33 @@
public View view;
public int childCount = 0;
public ViewRef next;
+
+ public void transferTo(ViewPropertyRef out) {
+ out.childCount = this.childCount;
+
+ View view = this.view;
+ this.view = null;
+
+ out.clazz = view.getClass();
+ out.hashCode = view.hashCode();
+ out.id = view.getId();
+ out.left = view.getLeft();
+ out.top = view.getTop();
+ out.right = view.getRight();
+ out.bottom = view.getBottom();
+ out.scrollX = view.getScrollX();
+ out.scrollY = view.getScrollY();
+
+ out.translateX = view.getTranslationX();
+ out.translateY = view.getTranslationY();
+ out.scaleX = view.getScaleX();
+ out.scaleY = view.getScaleY();
+ out.alpha = view.getAlpha();
+ out.elevation = view.getElevation();
+
+ out.visibility = view.getVisibility();
+ out.willNotDraw = view.willNotDraw();
+ }
}
private static final class ViewIdProvider {
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 76552a3..96504af 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -385,4 +385,12 @@
mBanner.setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint);
mBanner.setLayerPaint(layerPaint);
}
+
+ void setBannerVisibility(int visibility) {
+ if (mBanner == null) {
+ return;
+ }
+
+ mBanner.setVisibility(visibility);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index c211d7a..1a02f03 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -1,9 +1,6 @@
package com.android.quickstep.views;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
@@ -31,10 +28,10 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.states.SplitScreenSelectState;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.util.TaskCornerRadius;
import com.android.systemui.shared.system.QuickStepContract;
@@ -44,38 +41,14 @@
* which will have the thumbnail from the provided existing TaskView overlaying the taskview itself.
*
* Can then animate the taskview using
- * {@link #addAnimation(PendingAnimation, RectF, Rect, boolean, boolean)}
+ * {@link #addStagingAnimation(PendingAnimation, RectF, Rect, boolean, boolean)} or
+ * {@link #addConfirmAnimation(PendingAnimation, RectF, Rect, boolean, boolean)}
* giving a starting and ending bounds. Currently this is set to use the split placeholder view,
* but it could be generified.
*
* TODO: Figure out how to copy thumbnail data from existing TaskView to this view.
*/
public class FloatingTaskView extends FrameLayout {
- private static final int OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_START = 0;
- private static final int OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_END = 133;
- private static final int OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_START = 167;
- private static final int OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_END = 250;
- private static final int OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START = 0;
- private static final int OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END = 417;
-
- private static final float OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_START_OFFSET =
- (float) OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_START
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_END_OFFSET =
- (float) OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_END
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_START_OFFSET =
- (float) OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_START
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_END_OFFSET =
- (float) OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_END
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START_OFFSET =
- (float) OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END_OFFSET =
- (float) OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END
- / SplitScreenSelectState.ENTER_DURATION;
public static final FloatProperty<FloatingTaskView> PRIMARY_TRANSLATE_OFFSCREEN =
new FloatProperty<FloatingTaskView>("floatingTaskPrimaryTranslateOffscreen") {
@@ -151,7 +124,7 @@
RecentsView recentsView = launcher.getOverviewPanel();
mOrientationHandler = recentsView.getPagedOrientationHandler();
- mStagePosition = recentsView.getSplitPlaceholder().getActiveSplitStagePosition();
+ mStagePosition = recentsView.getSplitSelectController().getActiveSplitStagePosition();
mSplitPlaceholderView.setIcon(icon,
mContext.getResources().getDimensionPixelSize(R.dimen.split_placeholder_icon_size));
mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
@@ -236,8 +209,37 @@
layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
}
- public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds,
- boolean fadeWithThumbnail, boolean isStagedTask) {
+ /**
+ * Animates a FloatingTaskThumbnailView and its overlapping SplitPlaceholderView when a split
+ * is staged.
+ */
+ public void addStagingAnimation(PendingAnimation animation, RectF startingBounds,
+ Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) {
+ SplitAnimationTimings timings = fadeWithThumbnail
+ ? SplitAnimationTimings.OVERVIEW_TO_SPLIT
+ : SplitAnimationTimings.NORMAL_TO_SPLIT;
+ addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask,
+ timings);
+ }
+
+ /**
+ * Animates the FloatingTaskThumbnailView and SplitPlaceholderView for the two thumbnails
+ * when a split is confirmed.
+ */
+ public void addConfirmAnimation(PendingAnimation animation, RectF startingBounds,
+ Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) {
+ addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask,
+ SplitAnimationTimings.SPLIT_TO_CONFIRM);
+ }
+
+ /**
+ * Sets up and builds a split staging animation.
+ * Called by {@link #addStagingAnimation(PendingAnimation, RectF, Rect, boolean, boolean)} and
+ * {@link #addConfirmAnimation(PendingAnimation, RectF, Rect, boolean, boolean)}.
+ */
+ public void addAnimation(PendingAnimation animation, RectF startingBounds,
+ Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask,
+ SplitAnimationTimings timings) {
mFullscreenParams.setIsStagedTask(isStagedTask);
final BaseDragLayer dragLayer = mActivity.getDragLayer();
int[] dragLayerBounds = new int[2];
@@ -251,49 +253,47 @@
RectF floatingTaskViewBounds = new RectF();
if (fadeWithThumbnail) {
- // This code block runs when animating from Overview > OverviewSplitSelect
- // And for the second thumbnail on confirm
+ // This code block runs for the placeholder view during Overview > OverviewSplitSelect
+ // and for the selected (secondary) thumbnail during OverviewSplitSelect > Confirmed
// FloatingTaskThumbnailView: thumbnail fades out to transparent
animation.setViewAlpha(mThumbnailView, 0, clampToProgress(LINEAR,
- OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_START_OFFSET,
- OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_END_OFFSET));
+ timings.getPlaceholderFadeInStartOffset(),
+ timings.getPlaceholderFadeInEndOffset()));
- // SplitPlaceholderView: gray background fades in at the same time, then new icon fades
- // in
- animation.setViewAlpha(mSplitPlaceholderView, 1, clampToProgress(LINEAR,
- OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_START_OFFSET,
- OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_END_OFFSET));
- animation.setViewAlpha(mSplitPlaceholderView.getIconView(), 1, clampToProgress(
- LINEAR, OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_START_OFFSET,
- OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_END_OFFSET));
+ // SplitPlaceholderView: gray background fades in at same time, then new icon fades in
+ fadeInSplitPlaceholder(animation, timings);
} else if (isStagedTask) {
- // This code block runs when animating from Normal > OverviewSplitSelect
- // and for the first thumbnail on confirm
+ // This code block runs for the placeholder view during Normal > OverviewSplitSelect
+ // and for the placeholder (primary) thumbnail during OverviewSplitSelect > Confirmed
- // Fade in the placeholder view when split is initiated from homescreen / all apps
+ // Fade in the placeholder view during Normal > OverviewSplitSelect
if (mSplitPlaceholderView.getAlpha() == 0) {
- animation.setViewAlpha(mSplitPlaceholderView, 0.3f, INSTANT);
- animation.setViewAlpha(mSplitPlaceholderView, 1, ACCEL);
+ mSplitPlaceholderView.getIconView().setAlpha(0);
+ fadeInSplitPlaceholder(animation, timings);
}
+
+ // No-op for placeholder during OverviewSplitSelect > Confirmed, alpha should be set
}
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
// SplitPlaceholderView: rectangle translates and stretches to new position
final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration,
- clampToProgress(DEACCEL_2, OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START_OFFSET,
- OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END_OFFSET));
+ clampToProgress(timings.getStagedRectXInterpolator(),
+ timings.getStagedRectSlideStartOffset(),
+ timings.getStagedRectSlideEndOffset()));
final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration,
- clampToProgress(DEACCEL_2, OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START_OFFSET,
- OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END_OFFSET));
+ clampToProgress(timings.getStagedRectYInterpolator(),
+ timings.getStagedRectSlideStartOffset(),
+ timings.getStagedRectSlideEndOffset()));
final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0,
- animDuration, clampToProgress(DEACCEL_2,
- OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START_OFFSET,
- OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END_OFFSET));
+ animDuration, clampToProgress(timings.getStagedRectScaleXInterpolator(),
+ timings.getStagedRectSlideStartOffset(),
+ timings.getStagedRectSlideEndOffset()));
final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, 0,
- animDuration, clampToProgress(DEACCEL_2,
- OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START_OFFSET,
- OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END_OFFSET));
+ animDuration, clampToProgress(timings.getStagedRectScaleYInterpolator(),
+ timings.getStagedRectSlideStartOffset(),
+ timings.getStagedRectSlideEndOffset()));
@Override
public void onUpdate(float percent, boolean initOnly) {
// Calculate the icon position.
@@ -305,9 +305,19 @@
update(floatingTaskViewBounds, percent);
}
};
+
transitionAnimator.addUpdateListener(listener);
}
+ void fadeInSplitPlaceholder(PendingAnimation animation, SplitAnimationTimings timings) {
+ animation.setViewAlpha(mSplitPlaceholderView, 1, clampToProgress(LINEAR,
+ timings.getPlaceholderFadeInStartOffset(),
+ timings.getPlaceholderFadeInEndOffset()));
+ animation.setViewAlpha(mSplitPlaceholderView.getIconView(), 1, clampToProgress(LINEAR,
+ timings.getPlaceholderIconFadeInStartOffset(),
+ timings.getPlaceholderIconFadeInEndOffset()));
+ }
+
void drawRoundedRect(Canvas canvas, Paint paint) {
if (mFullscreenParams == null) {
return;
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 44ea0a0..3a5f606 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -191,7 +191,7 @@
// Callbacks run from remote animation when recents animation not currently running
InteractionJankMonitorWrapper.begin(this,
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView");
- recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/,
+ recentsView.getSplitSelectController().launchTasks(this /*groupedTaskView*/,
success -> {
endCallback.executeAllAndDestroy();
InteractionJankMonitorWrapper.end(
@@ -206,7 +206,7 @@
@Override
public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
- getRecentsView().getSplitPlaceholder().launchTasks(mTask.key.id, mSecondaryTask.key.id,
+ getRecentsView().getSplitSelectController().launchTasks(mTask.key.id, mSecondaryTask.key.id,
STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList, getSplitRatio());
}
@@ -230,11 +230,12 @@
}
@Override
- protected int getChildTaskIndexAtPosition(PointF position) {
- if (isCoordInView(mIconView2, position) || isCoordInView(mSnapshotView2, position)) {
+ protected int getLastSelectedChildTaskIndex() {
+ if (isCoordInView(mIconView2, mLastTouchDownPosition)
+ || isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
return 1;
}
- return super.getChildTaskIndexAtPosition(position);
+ return super.getLastSelectedChildTaskIndex();
}
private boolean isCoordInView(View v, PointF position) {
@@ -336,9 +337,26 @@
mSnapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha);
}
+ /**
+ * Sets visibility for thumbnails and associated elements (DWB banners).
+ * IconView is unaffected.
+ *
+ * When setting INVISIBLE, sets the visibility for the last selected child task.
+ * When setting VISIBLE (as a reset), sets the visibility for both tasks.
+ */
@Override
void setThumbnailVisibility(int visibility) {
- super.setThumbnailVisibility(visibility);
- mSnapshotView2.setVisibility(visibility);
+ if (visibility == VISIBLE) {
+ mSnapshotView.setVisibility(visibility);
+ mDigitalWellBeingToast.setBannerVisibility(visibility);
+ mSnapshotView2.setVisibility(visibility);
+ mDigitalWellBeingToast2.setBannerVisibility(visibility);
+ } else if (getLastSelectedChildTaskIndex() == 0) {
+ mSnapshotView.setVisibility(visibility);
+ mDigitalWellBeingToast.setBannerVisibility(visibility);
+ } else {
+ mSnapshotView2.setVisibility(visibility);
+ mDigitalWellBeingToast2.setBannerVisibility(visibility);
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 364cf7b..bb8506d 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -120,11 +120,6 @@
}
@Override
- public void onStateTransitionFailed(LauncherState toState) {
- reset();
- }
-
- @Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState == NORMAL || finalState == SPRING_LOADED) {
// Clean-up logic that occurs when recents is no longer in use/visible.
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6a94094..96607a0 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -39,7 +39,7 @@
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_85;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_75;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
@@ -142,7 +142,6 @@
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.states.SplitScreenSelectState;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -175,6 +174,7 @@
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.util.SplitScreenBounds;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.SurfaceTransactionApplier;
@@ -419,54 +419,6 @@
private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f;
private static final float END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.75f;
- private static final int OVERVIEW_TO_SPLIT_THUMBNAIL_SLIDE_START = 67;
- private static final int OVERVIEW_TO_SPLIT_THUMBNAIL_SLIDE_OFFSET = 16;
- private static final int SPRING_DISMISS_TRANSLATION_DURATION = 500;
-
- private static final float INITIAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET =
- (float) OVERVIEW_TO_SPLIT_THUMBNAIL_SLIDE_START
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float ADDITIONAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET =
- (float) OVERVIEW_TO_SPLIT_THUMBNAIL_SLIDE_OFFSET
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float END_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET =
- (float) SPRING_DISMISS_TRANSLATION_DURATION
- / SplitScreenSelectState.ENTER_DURATION;
-
- private static final int OVERVIEW_TO_SPLIT_ICON_FADE_START = 0;
- private static final int OVERVIEW_TO_SPLIT_ICON_FADE_END = 83;
- private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_START = 167;
- private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_END = 250;
- private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_START = 217;
- private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_END = 300;
- private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_START = 167;
- private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_END = 500;
-
- private static final float OVERVIEW_TO_SPLIT_ICON_FADE_START_OFFSET =
- (float) OVERVIEW_TO_SPLIT_ICON_FADE_START
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_ICON_FADE_END_OFFSET =
- (float) OVERVIEW_TO_SPLIT_ICON_FADE_END
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_START_OFFSET =
- (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_START
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_END_OFFSET =
- (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_END
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_START_OFFSET =
- (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_START
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_END_OFFSET =
- (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_END
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_START_OFFSET =
- (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_START
- / SplitScreenSelectState.ENTER_DURATION;
- private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_END_OFFSET =
- (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_END
- / SplitScreenSelectState.ENTER_DURATION;
-
private static final float SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE = 0.15f;
protected final RecentsOrientedState mOrientationState;
@@ -703,7 +655,7 @@
@Nullable
private TaskView mSplitHiddenTaskView;
@Nullable
- private View mSecondSplitHiddenView;
+ private TaskView mSecondSplitHiddenView;
@Nullable
private SplitBounds mSplitBoundsConfig;
private final Toast mSplitToast = Toast.makeText(getContext(),
@@ -960,7 +912,7 @@
mSplitSelectStateController = splitController;
}
- public SplitSelectStateController getSplitPlaceholder() {
+ public SplitSelectStateController getSplitSelectController() {
return mSplitSelectStateController;
}
@@ -1292,7 +1244,7 @@
protected void onPageEndTransition() {
super.onPageEndTransition();
ActiveGestureLog.INSTANCE.addLog(
- "onPageEndTransition: current page index updated", mCurrentPage);
+ "onPageEndTransition: current page index updated", getNextPage());
if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
}
@@ -2857,28 +2809,30 @@
mOrientationHandler.getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
mSplitPlaceholderInset, mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
+ SplitAnimationTimings timings = SplitAnimationTimings.OVERVIEW_TO_SPLIT;
RectF startingTaskRect = new RectF();
safeRemoveDragLayerView(mFirstFloatingTaskView);
if (mSplitHiddenTaskView != null) {
- // Split staging is initiated
+ // Create the split select animation from Overview
mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE);
anim.setViewAlpha(mSplitHiddenTaskView.getIconView(), 0, clampToProgress(LINEAR,
- OVERVIEW_TO_SPLIT_ICON_FADE_START_OFFSET,
- OVERVIEW_TO_SPLIT_ICON_FADE_END_OFFSET));
+ timings.getIconFadeStartOffset(),
+ timings.getIconFadeEndOffset()));
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
mSplitHiddenTaskView.getThumbnail(),
mSplitHiddenTaskView.getThumbnail().getThumbnail(),
mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
mFirstFloatingTaskView.setAlpha(1);
- mFirstFloatingTaskView.addAnimation(anim, startingTaskRect, mTempRect,
+ mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
true /* fadeWithThumbnail */, true /* isStagedTask */);
} else {
+ // Create the split select animation from Home
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
mSplitSelectSource.view, null /* thumbnail */,
mSplitSelectSource.drawable, startingTaskRect);
mFirstFloatingTaskView.setAlpha(1);
- mFirstFloatingTaskView.addAnimation(anim, startingTaskRect, mTempRect,
+ mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
false /* fadeWithThumbnail */, true /* isStagedTask */);
}
@@ -2887,15 +2841,15 @@
mSplitInstructionsView = SplitInstructionsView.getSplitInstructionsView(mActivity);
mSplitInstructionsView.setAlpha(0);
anim.setViewAlpha(mSplitInstructionsView, 1, clampToProgress(LINEAR,
- OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_START_OFFSET,
- OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_END_OFFSET));
+ timings.getInstructionsContainerFadeInStartOffset(),
+ timings.getInstructionsContainerFadeInEndOffset()));
anim.setViewAlpha(mSplitInstructionsView.getTextView(), 1, clampToProgress(LINEAR,
- OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_START_OFFSET,
- OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_END_OFFSET));
+ timings.getInstructionsTextFadeInStartOffset(),
+ timings.getInstructionsTextFadeInEndOffset()));
anim.addFloat(mSplitInstructionsView, mSplitInstructionsView.UNFOLD, 0.1f, 1,
clampToProgress(EMPHASIZED_DECELERATE,
- OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_START_OFFSET,
- OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_END_OFFSET));
+ timings.getInstructionsUnfoldStartOffset(),
+ timings.getInstructionsUnfoldEndOffset()));
InteractionJankMonitorWrapper.begin(this,
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
@@ -3184,24 +3138,26 @@
: distanceFromDismissedTask;
// Set timings based on if user is initiating splitscreen on the focused task,
// or splitting/dismissing some other task.
+ SplitAnimationTimings timings = SplitAnimationTimings.OVERVIEW_TO_SPLIT;
float animationStartProgress = isStagingFocusedTask
? Utilities.boundToRange(
- INITIAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- + ADDITIONAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- * staggerColumn, 0f, dismissTranslationInterpolationEnd)
+ timings.getGridSlideStartOffset()
+ + (timings.getGridSlideStaggerOffset() * staggerColumn),
+ 0f,
+ dismissTranslationInterpolationEnd)
: Utilities.boundToRange(
INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
* staggerColumn, 0f, dismissTranslationInterpolationEnd);
float animationEndProgress = isStagingFocusedTask
? Utilities.boundToRange(
- INITIAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- + END_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- + ADDITIONAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- * staggerColumn,
- 0f, dismissTranslationInterpolationEnd)
+ timings.getGridSlideStartOffset()
+ + (timings.getGridSlideStaggerOffset() * staggerColumn)
+ + timings.getGridSlideDurationOffset(),
+ 0f,
+ dismissTranslationInterpolationEnd)
: dismissTranslationInterpolationEnd;
- Interpolator dismissInterpolator = isStagingFocusedTask ? OVERSHOOT_0_85 : LINEAR;
+ Interpolator dismissInterpolator = isStagingFocusedTask ? OVERSHOOT_0_75 : LINEAR;
if (taskView == nextFocusedTaskView) {
// Enlarge the task to be focused next, and translate into focus position.
@@ -4223,9 +4179,9 @@
// TODO(194414938) starting bounds seem slightly off, investigate
Rect firstTaskStartingBounds = new Rect();
Rect firstTaskEndingBounds = mTempRect;
- int duration = mActivity.getStateManager().getState().getTransitionDuration(mActivity,
- false /* isToState */);
- PendingAnimation pendingAnimation = new PendingAnimation(duration);
+ PendingAnimation pendingAnimation =
+ new PendingAnimation(SplitAnimationTimings.CONFIRM_DURATION);
+ SplitAnimationTimings timings = SplitAnimationTimings.SPLIT_TO_CONFIRM;
int halfDividerSize = getResources()
.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
@@ -4235,7 +4191,7 @@
secondTaskEndingBounds);
mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
- mFirstFloatingTaskView.addAnimation(pendingAnimation,
+ mFirstFloatingTaskView.addConfirmAnimation(pendingAnimation,
new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
false /* fadeWithThumbnail */, true /* isStagedTask */);
@@ -4244,20 +4200,22 @@
thumbnailView, thumbnailView.getThumbnail(),
iconView.getDrawable(), secondTaskStartingBounds);
mSecondFloatingTaskView.setAlpha(1);
- mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
+ mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
+
+ pendingAnimation.setViewAlpha(mSplitInstructionsView, 0, clampToProgress(LINEAR,
+ timings.getInstructionsFadeStartOffset(),
+ timings.getInstructionsFadeEndOffset()));
+
pendingAnimation.addEndListener(aBoolean -> {
mSplitSelectStateController.launchSplitTasks(
aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
});
- if (containerTaskView.containsMultipleTasks()) {
- // If we are launching from a child task, then only hide the thumbnail itself
- mSecondSplitHiddenView = thumbnailView;
- } else {
- mSecondSplitHiddenView = containerTaskView;
- }
- mSecondSplitHiddenView.setVisibility(INVISIBLE);
+
+ mSecondSplitHiddenView = containerTaskView;
+ mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE);
+
InteractionJankMonitorWrapper.begin(this,
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");
@@ -4278,13 +4236,14 @@
mFirstFloatingTaskView = null;
mSecondFloatingTaskView = null;
mSplitInstructionsView = null;
- if (mSecondSplitHiddenView != null) {
- mSecondSplitHiddenView.setVisibility(VISIBLE);
- mSecondSplitHiddenView = null;
- }
mSplitSelectSource = null;
}
+ if (mSecondSplitHiddenView != null) {
+ mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE);
+ mSecondSplitHiddenView = null;
+ }
+
if (mSplitHiddenTaskViewIndex == -1) {
return;
}
@@ -4300,10 +4259,6 @@
onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
resetTaskVisuals();
mSplitHiddenTaskViewIndex = -1;
- if (mSplitHiddenTaskView != null) {
- mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
- mSplitHiddenTaskView = null;
- }
}
private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
@@ -4317,7 +4272,7 @@
* Note that the translation can be its primary or secondary dimension.
*/
public float getSplitSelectTranslation() {
- int splitPosition = getSplitPlaceholder().getActiveSplitStagePosition();
+ int splitPosition = getSplitSelectController().getActiveSplitStagePosition();
if (!shouldShiftThumbnailsForSplitSelect()) {
return 0f;
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index ee8489d..a81f95f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -95,6 +95,7 @@
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TaskCornerRadius;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
@@ -387,7 +388,7 @@
private final float[] mIconCenterCoords = new float[2];
- private final PointF mLastTouchDownPosition = new PointF();
+ protected final PointF mLastTouchDownPosition = new PointF();
private boolean mIsClickableAsLiveTile = true;
@@ -561,6 +562,18 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ RecentsView recentsView = getRecentsView();
+ if (recentsView == null || mTask == null) {
+ return false;
+ }
+ SplitSelectStateController splitSelectStateController =
+ recentsView.getSplitSelectController();
+ if (splitSelectStateController.isSplitSelectActive() &&
+ splitSelectStateController.getInitialTaskId() == mTask.key.id) {
+ // Prevent taps on the this taskview if it's being animated into split select state
+ return false;
+ }
+
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mLastTouchDownPosition.set(ev.getX(), ev.getY());
}
@@ -584,16 +597,16 @@
* second app. {@code false} otherwise
*/
private boolean confirmSecondSplitSelectApp() {
- int index = getChildTaskIndexAtPosition(mLastTouchDownPosition);
+ int index = getLastSelectedChildTaskIndex();
TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
return getRecentsView().confirmSplitSelect(this, container.getTask(),
container.getIconView(), container.getThumbnailView());
}
/**
- * Returns the task under the given position in the local coordinates of this task view.
+ * Returns the task index of the last selected child task (0 or 1).
*/
- protected int getChildTaskIndexAtPosition(PointF position) {
+ protected int getLastSelectedChildTaskIndex() {
return 0;
}
@@ -1516,8 +1529,17 @@
return display != null ? display.getDisplayId() : DEFAULT_DISPLAY;
}
+ /**
+ * Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
+ * IconView is unaffected.
+ */
void setThumbnailVisibility(int visibility) {
- mSnapshotView.setVisibility(visibility);
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child != mIconView) {
+ child.setVisibility(visibility);
+ }
+ }
}
/**
diff --git a/res/layout/all_apps_personal_work_tabs.xml b/res/layout/all_apps_personal_work_tabs.xml
index d15b906..4459c87 100644
--- a/res/layout/all_apps_personal_work_tabs.xml
+++ b/res/layout/all_apps_personal_work_tabs.xml
@@ -22,6 +22,7 @@
android:layout_gravity="center_horizontal"
android:paddingTop="@dimen/all_apps_tabs_vertical_padding"
android:paddingBottom="@dimen/all_apps_tabs_vertical_padding"
+ android:layout_marginTop="@dimen/all_apps_tabs_margin_top"
android:orientation="horizontal"
style="@style/TextHeadline">
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 709160d..3a58b85 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -85,7 +85,7 @@
<string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> телефон чалууларды аткарууга уруксаты жок"</string>
<string name="gadget_error_text" msgid="740356548025791839">"Виджет жүктөлбөй жатат"</string>
<string name="gadget_setup_text" msgid="8348374825537681407">"Виджеттин жөндөөлөрү"</string>
- <string name="gadget_complete_setup_text" msgid="309040266978007925">"Жөндөп бүтүү үчүн таптап коюңуз"</string>
+ <string name="gadget_complete_setup_text" msgid="309040266978007925">"Аягына чейин тууралоо үчүн басып коюңуз"</string>
<string name="uninstall_system_app_text" msgid="4172046090762920660">"Бул системдик колдонмо жана аны чечкенге болбойт."</string>
<string name="folder_hint_text" msgid="5174843001373488816">"Аталышын түзөтүү"</string>
<string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> өчүрүлгөн"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 48f7355..b553c90 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -120,7 +120,7 @@
<string name="abandoned_clean_this" msgid="7610119707847920412">"काढा"</string>
<string name="abandoned_search" msgid="891119232568284442">"शोधा"</string>
<string name="abandoned_promises_title" msgid="7096178467971716750">"हा अॅप इंस्टॉल केलेला नाही"</string>
- <string name="abandoned_promise_explanation" msgid="3990027586878167529">"या चिन्हासाठी अॅप इंस्टॉल केलेला नाही. तुम्ही ते काढू शकता किंवा अॅपचा शोध घेऊ शकता आणि त्यास व्यक्तिचलितपणे इंस्टॉल करू शकता."</string>
+ <string name="abandoned_promise_explanation" msgid="3990027586878167529">"या आयकनसाठी अॅप इंस्टॉल केलेले नाही. तुम्ही तो काढू शकता किंवा अॅपचा शोध घेऊन ते मॅन्युअली इंस्टॉल करू शकता."</string>
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल करत आहे, <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड होत आहे , <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल करण्याची प्रतिक्षा करत आहे"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index d3f5033..11b6e8c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -204,4 +204,7 @@
<!-- The max scale for the wallpaper when it's zoomed in -->
<item name="config_wallpaperMaxScale" format="float" type="dimen">0</item>
+
+ <string name="floating_task_package" translatable="false"></string>
+ <string name="floating_task_action" translatable="false"></string>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2e886db..5dae9f2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -121,6 +121,7 @@
<dimen name="all_apps_work_profile_tab_footer_bottom_padding">20dp</dimen>
<dimen name="all_apps_tabs_button_horizontal_padding">4dp</dimen>
<dimen name="all_apps_tabs_vertical_padding">6dp</dimen>
+ <dimen name="all_apps_tabs_margin_top">8dp</dimen>
<dimen name="all_apps_divider_height">2dp</dimen>
<dimen name="all_apps_divider_width">128dp</dimen>
<dimen name="all_apps_content_fade_in_offset">150dp</dimen>
@@ -128,9 +129,9 @@
<dimen name="all_apps_height_extra">6dp</dimen>
<dimen name="all_apps_bottom_sheet_horizontal_padding">0dp</dimen>
<dimen name="all_apps_paged_view_top_padding">40dp</dimen>
- <dimen name="all_apps_personal_work_tabs_vertical_margin">16dp</dimen>
<dimen name="all_apps_icon_drawable_padding">8dp</dimen>
+ <dimen name="all_apps_predicted_icon_vertical_padding">8dp</dimen>
<!-- The size of corner radius of the arrow in the arrow toast. -->
<dimen name="arrow_toast_corner_radius">2dp</dimen>
<dimen name="arrow_toast_elevation">2dp</dimen>
@@ -247,6 +248,8 @@
<!-- Folders -->
<dimen name="page_indicator_dot_size">8dp</dimen>
+ <dimen name="page_indicator_current_page_indicator_size">10dp</dimen>
+
<dimen name="folder_cell_x_padding">9dp</dimen>
<dimen name="folder_cell_y_padding">6dp</dimen>
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index d34f535..83ff084 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -25,12 +25,16 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.Configuration;
+import android.os.Bundle;
+import android.window.OnBackInvokedDispatcher;
import androidx.annotation.IntDef;
import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.ViewCache;
import com.android.launcher3.views.AppLauncher;
@@ -172,6 +176,19 @@
}
@Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (Utilities.ATLEAST_T) {
+ getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ () -> {
+ onBackPressed();
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
+ });
+ }
+ }
+
+ @Override
protected void onStart() {
addActivityFlags(ACTIVITY_STATE_STARTED);
super.onStart();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ce3abe6..a6831aa 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -114,7 +114,6 @@
import android.view.animation.OvershootInterpolator;
import android.widget.ImageView;
import android.widget.Toast;
-import android.window.OnBackInvokedDispatcher;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
@@ -543,8 +542,6 @@
getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
}
setTitle(R.string.home_screen);
-
- registerOnBackInvokedCallback();
}
protected LauncherOverlayManager getDefaultOverlay() {
@@ -2065,17 +2062,6 @@
mStateManager.getState().onBackPressed(this);
}
- private void registerOnBackInvokedCallback() {
- if (Utilities.ATLEAST_T) {
- getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- () -> {
- onBackPressed();
- TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
- });
- }
- }
-
protected void onScreenOff() {
// Reset AllApps to its initial state only if we are not in the middle of
// processing a multi-step drop
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index c1eaa16..08b42cd 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -181,11 +181,6 @@
}
@Override
- protected boolean shouldShowTabs() {
- return super.shouldShowTabs() && !isSearching();
- }
-
- @Override
public boolean isSearching() {
return mIsSearching;
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 391de8d..872c4fd 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -319,7 +319,6 @@
* TODO: This logic should go in {@link LauncherState}
*/
private void onProgressAnimationEnd() {
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
if (Float.compare(mProgress, 1f) == 0) {
mAppsView.reset(false /* animate */);
mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 73efd3f..1ef5498 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -272,7 +272,8 @@
}
mMaxTranslation += mFloatingRowsHeight;
if (!mTabsHidden) {
- mMaxTranslation += mTabsAdditionalPaddingBottom;
+ mMaxTranslation += mTabsAdditionalPaddingBottom
+ + getResources().getDimensionPixelSize(R.dimen.all_apps_tabs_margin_top);
}
}
diff --git a/src/com/android/launcher3/allapps/SearchTransitionController.java b/src/com/android/launcher3/allapps/SearchTransitionController.java
index 4be715b..8fc7965 100644
--- a/src/com/android/launcher3/allapps/SearchTransitionController.java
+++ b/src/com/android/launcher3/allapps/SearchTransitionController.java
@@ -23,7 +23,7 @@
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import android.animation.ObjectAnimator;
@@ -35,15 +35,16 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
/** Coordinates the transition between Search and A-Z in All Apps. */
public class SearchTransitionController {
// Interpolator when the user taps the QSB while already in All Apps.
- private static final Interpolator DEFAULT_INTERPOLATOR_WITHIN_ALL_APPS = DEACCEL_1_7;
+ private static final Interpolator INTERPOLATOR_WITHIN_ALL_APPS = DEACCEL_1_7;
// Interpolator when the user taps the QSB from home screen, so transition to all apps is
// happening simultaneously.
- private static final Interpolator DEFAULT_INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = LINEAR;
+ private static final Interpolator INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = INSTANT;
/**
* These values represent points on the [0, 1] animation progress spectrum. They are used to
@@ -103,9 +104,11 @@
boolean inAllApps = Launcher.getLauncher(
mAllAppsContainerView.getContext()).getStateManager().isInStableState(
LauncherState.ALL_APPS);
+ if (!inAllApps) {
+ duration = 0; // Don't want to animate when coming from QSB.
+ }
mSearchToAzAnimator.setDuration(duration).setInterpolator(
- inAllApps ? DEFAULT_INTERPOLATOR_WITHIN_ALL_APPS
- : DEFAULT_INTERPOLATOR_TRANSITIONING_TO_ALL_APPS);
+ inAllApps ? INTERPOLATOR_WITHIN_ALL_APPS : INTERPOLATOR_TRANSITIONING_TO_ALL_APPS);
mSearchToAzAnimator.addListener(forEndCallback(() -> mSearchToAzAnimator = null));
if (!goingToSearch) {
mSearchToAzAnimator.addListener(forSuccessCallback(() -> {
@@ -144,8 +147,11 @@
headerView.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f));
// Account for the additional padding added for the tabs.
- appsTranslationY -=
- headerView.getPaddingTop() - headerView.getTabsAdditionalPaddingBottom();
+ appsTranslationY +=
+ headerView.getTabsAdditionalPaddingBottom()
+ + mAllAppsContainerView.getResources().getDimensionPixelOffset(
+ R.dimen.all_apps_tabs_margin_top)
+ - headerView.getPaddingTop();
}
View appsContainer = mAllAppsContainerView.getAppsRecyclerViewContainer();
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index a589448..15fb77c 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -97,12 +97,10 @@
bottomMargin += dp.hotseatQsbHeight;
}
- if (!dp.isGestureMode) {
- if (dp.isTaskbarPresent) {
- bottomMargin += dp.taskbarSize;
- } else {
- bottomMargin += insets.bottom;
- }
+ if (!dp.isGestureMode && dp.isTaskbarPresent) {
+ bottomMargin += dp.taskbarSize;
+ } else {
+ bottomMargin += insets.bottom;
}
lp.bottomMargin = bottomMargin;
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index d900c90..b55a1e4 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -83,7 +83,7 @@
EXAGGERATED_EASE = new PathInterpolator(exaggeratedEase);
}
- public static final Interpolator OVERSHOOT_0_85 = new OvershootInterpolator(0.85f);
+ public static final Interpolator OVERSHOOT_0_75 = new OvershootInterpolator(0.75f);
public static final Interpolator OVERSHOOT_1_2 = new OvershootInterpolator(1.2f);
public static final Interpolator OVERSHOOT_1_7 = new OvershootInterpolator(1.7f);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index f14d3aa..4a47e98 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -281,6 +281,14 @@
public static final BooleanFlag ENABLE_WIDGET_PICKER_DEPTH = new DeviceFlag(
"ENABLE_WIDGET_PICKER_DEPTH", false, "Enable changing depth in widget picker.");
+ public static final BooleanFlag SHOW_DELIGHTFUL_PAGINATION_FOLDER = new DeviceFlag(
+ "SHOW_DELIGHTFUL_PAGINATION_FOLDER", false,
+ "Enable showing the new 'delightful pagination'"
+ + " which is a brand new animation for folder pagination");
+
+ public static final BooleanFlag POPUP_MATERIAL_U = new DeviceFlag(
+ "POPUP_MATERIAL_U", false, "Switch popup UX to use material U");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 32237e2..22627b4 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -744,8 +744,9 @@
HOT(2),
TIMEOUT(3),
FAIL(4),
- COLD_USERWAITING(5);
-
+ COLD_USERWAITING(5),
+ ATOMIC(6),
+ CONTROLLED(7);
private final int mId;
LatencyType(int id) {
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 76a0c4d..e5fb015 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,7 +16,6 @@
package com.android.launcher3.model.data;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -220,10 +219,10 @@
/** Creates an intent to that launches the app store at this app's page. */
@Nullable
public Intent getMarketIntent(Context context) {
- ComponentName componentName = getTargetComponent();
+ String targetPackage = getTargetPackage();
- return componentName != null
- ? new PackageManagerHelper(context).getMarketIntent(componentName.getPackageName())
+ return targetPackage != null
+ ? new PackageManagerHelper(context).getMarketIntent(targetPackage)
: null;
}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 29eefe2..1798536 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -16,6 +16,8 @@
package com.android.launcher3.pageindicators;
+import static com.android.launcher3.config.FeatureFlags.SHOW_DELIGHTFUL_PAGINATION_FOLDER;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -37,6 +39,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.util.Themes;
/**
@@ -55,6 +58,7 @@
private static final int DOT_ACTIVE_ALPHA = 255;
private static final int DOT_INACTIVE_ALPHA = 128;
+ private static final int DOT_GAP_FACTOR = 3;
// This value approximately overshoots to 1.5 times the original size.
private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
@@ -76,21 +80,25 @@
}
};
- private final Paint mCirclePaint;
+ private final Paint mPaginationPaint;
private final float mDotRadius;
+ private final float mCircleGap;
+ private final float mPageIndicatorSize;
private final boolean mIsRtl;
private int mNumPages;
private int mActivePage;
+ private int mCurrentScroll;
+ private int mTotalScroll;
/**
* The current position of the active dot including the animation progress.
* For ex:
- * 0.0 => Active dot is at position 0
- * 0.33 => Active dot is at position 0 and is moving towards 1
- * 0.50 => Active dot is at position [0, 1]
- * 0.77 => Active dot has left position 0 and is collapsing towards position 1
- * 1.0 => Active dot is at position 1
+ * 0.0 => Active dot is at position 0
+ * 0.33 => Active dot is at position 0 and is moving towards 1
+ * 0.50 => Active dot is at position [0, 1]
+ * 0.77 => Active dot has left position 0 and is collapsing towards position 1
+ * 1.0 => Active dot is at position 1
*/
private float mCurrentPosition;
private float mFinalPosition;
@@ -109,37 +117,51 @@
public PageIndicatorDots(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mCirclePaint.setStyle(Style.FILL);
- mCirclePaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
+ mPaginationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mPaginationPaint.setStyle(Style.FILL);
+ mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
- setOutlineProvider(new MyOutlineProver());
-
+ mCircleGap = DOT_GAP_FACTOR * mDotRadius;
+ mPageIndicatorSize = getResources().getDimension(
+ R.dimen.page_indicator_current_page_indicator_size);
+ if (!SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+ setOutlineProvider(new MyOutlineProver());
+ }
mIsRtl = Utilities.isRtl(getResources());
}
@Override
public void setScroll(int currentScroll, int totalScroll) {
- if (mNumPages > 1) {
- if (mIsRtl) {
- currentScroll = totalScroll - currentScroll;
- }
- int scrollPerPage = totalScroll / (mNumPages - 1);
- int pageToLeft = currentScroll / scrollPerPage;
- int pageToLeftScroll = pageToLeft * scrollPerPage;
- int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+ if (mNumPages <= 1) {
+ return;
+ }
- float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
- if (currentScroll < pageToLeftScroll + scrollThreshold) {
- // scroll is within the left page's threshold
- animateToPosition(pageToLeft);
- } else if (currentScroll > pageToRightScroll - scrollThreshold) {
- // scroll is far enough from left page to go to the right page
- animateToPosition(pageToLeft + 1);
- } else {
- // scroll is between left and right page
- animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
- }
+ if (mIsRtl) {
+ currentScroll = totalScroll - currentScroll;
+ }
+
+ if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+ mCurrentScroll = currentScroll;
+ mTotalScroll = totalScroll;
+ invalidate();
+ return;
+ }
+
+ int scrollPerPage = totalScroll / (mNumPages - 1);
+ int pageToLeft = currentScroll / scrollPerPage;
+ int pageToLeftScroll = pageToLeft * scrollPerPage;
+ int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+
+ float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+ if (currentScroll < pageToLeftScroll + scrollThreshold) {
+ // scroll is within the left page's threshold
+ animateToPosition(pageToLeft);
+ } else if (currentScroll > pageToRightScroll - scrollThreshold) {
+ // scroll is far enough from left page to go to the right page
+ animateToPosition(pageToLeft + 1);
+ } else {
+ // scroll is between left and right page
+ animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
}
}
@@ -177,7 +199,7 @@
}
public void playEntryAnimation() {
- int count = mEntryAnimationRadiusFactors.length;
+ int count = mEntryAnimationRadiusFactors.length;
if (count == 0) {
mEntryAnimationRadiusFactors = null;
invalidate();
@@ -231,16 +253,16 @@
// Add extra spacing of mDotRadius on all sides so than entry animation could be run.
int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ?
MeasureSpec.getSize(widthMeasureSpec) : (int) ((mNumPages * 3 + 2) * mDotRadius);
- int height= MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ?
- MeasureSpec.getSize(heightMeasureSpec) : (int) (4 * mDotRadius);
+ int height = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
+ ? MeasureSpec.getSize(heightMeasureSpec) : (int) (4 * mDotRadius);
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
// Draw all page indicators;
- float circleGap = 3 * mDotRadius;
- float startX = (getWidth() - mNumPages * circleGap + mDotRadius) / 2;
+ float circleGap = mCircleGap;
+ float startX = (getWidth() - (mNumPages * circleGap) + mDotRadius) / 2;
float x = startX + mDotRadius;
float y = getHeight() / 2;
@@ -252,43 +274,88 @@
circleGap = -circleGap;
}
for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) {
- mCirclePaint.setAlpha(i == mActivePage ? DOT_ACTIVE_ALPHA : DOT_INACTIVE_ALPHA);
- canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], mCirclePaint);
+ mPaginationPaint.setAlpha(i == mActivePage ? DOT_ACTIVE_ALPHA : DOT_INACTIVE_ALPHA);
+ if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+ canvas.drawCircle(x, y, getRadius(x) * mEntryAnimationRadiusFactors[i],
+ mPaginationPaint);
+ } else {
+ canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
+ mPaginationPaint);
+ }
x += circleGap;
}
} else {
- mCirclePaint.setAlpha(DOT_INACTIVE_ALPHA);
+ mPaginationPaint.setAlpha(DOT_INACTIVE_ALPHA);
for (int i = 0; i < mNumPages; i++) {
- canvas.drawCircle(x, y, mDotRadius, mCirclePaint);
+ if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+ canvas.drawCircle(x, y, getRadius(x), mPaginationPaint);
+ } else {
+ canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
+ }
x += circleGap;
}
- mCirclePaint.setAlpha(DOT_ACTIVE_ALPHA);
- canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mCirclePaint);
+ mPaginationPaint.setAlpha(DOT_ACTIVE_ALPHA);
+
+ if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+ canvas.drawRect(getActiveRect(), mPaginationPaint);
+ } else {
+ canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
+ }
}
}
+ /**
+ * Returns the radius of the circle based on how close the page indicator is to it
+ *
+ * @param dotPositionX is the position the dot is located at in the x-axis
+ */
+ private float getRadius(float dotPositionX) {
+
+ float startXIndicator =
+ ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2) - getOffset();
+ float indicatorPosition = startXIndicator + getIndicatorScrollDistance()
+ + (mPageIndicatorSize / 2);
+
+ // If the indicator gets close enough to a dot then we change the radius
+ // of the dot based on how close the indicator is to it.
+ float dotDistance = Math.abs(indicatorPosition - dotPositionX);
+ if (dotDistance <= mCircleGap) {
+ return Utilities.mapToRange(dotDistance, 0, mCircleGap, 0f, mDotRadius,
+ Interpolators.LINEAR);
+ }
+ return mDotRadius;
+ }
+
private RectF getActiveRect() {
float startCircle = (int) mCurrentPosition;
float delta = mCurrentPosition - startCircle;
float diameter = 2 * mDotRadius;
- float circleGap = 3 * mDotRadius;
- float startX = (getWidth() - mNumPages * circleGap + mDotRadius) / 2;
+ float startX;
- sTempRect.top = getHeight() * 0.5f - mDotRadius;
- sTempRect.bottom = getHeight() * 0.5f + mDotRadius;
- sTempRect.left = startX + startCircle * circleGap;
- sTempRect.right = sTempRect.left + diameter;
-
- if (delta < SHIFT_PER_ANIMATION) {
- // dot is capturing the right circle.
- sTempRect.right += delta * circleGap * 2;
+ if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+ startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2) - getOffset();
+ sTempRect.top = (getHeight() - mPageIndicatorSize) * 0.5f;
+ sTempRect.bottom = (getHeight() + mPageIndicatorSize) * 0.5f;
+ sTempRect.left = startX + getIndicatorScrollDistance();
+ sTempRect.right = sTempRect.left + mPageIndicatorSize;
} else {
- // Dot is leaving the left circle.
- sTempRect.right += circleGap;
+ startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2);
+ sTempRect.top = (getHeight() * 0.5f) - mDotRadius;
+ sTempRect.bottom = (getHeight() * 0.5f) + mDotRadius;
+ sTempRect.left = startX + (startCircle * mCircleGap);
+ sTempRect.right = sTempRect.left + diameter;
- delta -= SHIFT_PER_ANIMATION;
- sTempRect.left += delta * circleGap * 2;
+ if (delta < SHIFT_PER_ANIMATION) {
+ // dot is capturing the right circle.
+ sTempRect.right += delta * mCircleGap * 2;
+ } else {
+ // Dot is leaving the left circle.
+ sTempRect.right += mCircleGap;
+
+ delta -= SHIFT_PER_ANIMATION;
+ sTempRect.left += delta * mCircleGap * 2;
+ }
}
if (mIsRtl) {
@@ -296,9 +363,26 @@
sTempRect.right = getWidth() - sTempRect.left;
sTempRect.left = sTempRect.right - rectWidth;
}
+
return sTempRect;
}
+ /**
+ * The offset between the radius of the dot and the midpoint of the indicator so that
+ * the indicator is centered in with the indicator circles
+ */
+ private float getOffset() {
+ return (mPageIndicatorSize / 2) - mDotRadius;
+ }
+
+ /**
+ * The current scroll adjusted for the distance the indicator needs to travel on the screen
+ */
+ private float getIndicatorScrollDistance() {
+ float scrollPerPage = mNumPages > 1 ? mTotalScroll / (mNumPages - 1) : 0;
+ return scrollPerPage != 0 ? ((float) mCurrentScroll / scrollPerPage) * mCircleGap : 0;
+ }
+
private class MyOutlineProver extends ViewOutlineProvider {
@Override
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 54e8e5b..86277a7 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -343,11 +343,6 @@
onStateTransitionEnd(state);
}
- @Override
- public void onAnimationCancel(Animator animation) {
- super.onAnimationCancel(animation);
- onStateTransitionFailed(state);
- }
};
}
@@ -360,12 +355,6 @@
}
}
- private void onStateTransitionFailed(STATE_TYPE state) {
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- mListeners.get(i).onStateTransitionFailed(state);
- }
- }
-
private void onStateTransitionEnd(STATE_TYPE state) {
// Only change the stable states after the transitions have finished
if (state != mCurrentStableState) {
@@ -600,11 +589,6 @@
default void onStateTransitionStart(STATE_TYPE toState) { }
- /**
- * If the state transition animation fails (e.g. is canceled by the user), this fires.
- */
- default void onStateTransitionFailed(STATE_TYPE toState) { }
-
default void onStateTransitionComplete(STATE_TYPE finalState) { }
}
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index c408904..b94ea07 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -24,12 +24,16 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
+import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Handler;
import android.os.Message;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
@@ -58,6 +62,7 @@
public static final int REQUEST_ROTATE = 1;
public static final int REQUEST_LOCK = 2;
+ @Nullable
private BaseActivity mActivity;
private SharedPreferences mSharedPrefs = null;
private final Handler mRequestOrientationHandler;
@@ -209,8 +214,12 @@
}
}
+ @WorkerThread
private boolean setOrientationAsync(Message msg) {
- mActivity.setRequestedOrientation(msg.what);
+ Activity activity = mActivity;
+ if (activity != null) {
+ activity.setRequestedOrientation(msg.what);
+ }
return true;
}
diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java
index bcc7c2d..5444d92 100644
--- a/src/com/android/launcher3/testing/TestInformationProvider.java
+++ b/src/com/android/launcher3/testing/TestInformationProvider.java
@@ -21,10 +21,14 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
+import android.util.Log;
import com.android.launcher3.Utilities;
public class TestInformationProvider extends ContentProvider {
+
+ private static final String TAG = "TestInformationProvider";
+
@Override
public boolean onCreate() {
return true;
@@ -60,7 +64,13 @@
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
TestInformationHandler handler = TestInformationHandler.newInstance(getContext());
handler.init(getContext());
- return handler.call(method, arg, extras);
+
+ Bundle response = handler.call(method, arg, extras);
+ if (response == null) {
+ Log.e(TAG, "Couldn't handle method: " + method + "; current handler="
+ + handler.getClass().getSimpleName());
+ }
+ return response;
}
return null;
}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 5fa30bc..b4be061 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -248,13 +248,19 @@
final Launcher launcher = Launcher.getLauncher(context);
if (shortcut.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& shortcut.isDisabledVersionLower()) {
+ final Intent marketIntent = shortcut.getMarketIntent(context);
+ // No market intent means no target package for the shortcut, which should be an
+ // issue. Falling back to showing toast messages.
+ if (marketIntent == null) {
+ return false;
+ }
new AlertDialog.Builder(context)
.setTitle(R.string.dialog_update_title)
.setMessage(R.string.dialog_update_message)
.setPositiveButton(R.string.dialog_update, (d, i) -> {
// Direct the user to the play store to update the app
- context.startActivity(shortcut.getMarketIntent(context));
+ context.startActivity(marketIntent);
})
.setNeutralButton(R.string.dialog_remove, (d, i) -> {
// Remove the icon if launcher is successfully initialized
diff --git a/src/com/android/launcher3/views/AllAppsButton.java b/src/com/android/launcher3/views/AllAppsButton.java
index b1e69c7..ab8e5db 100644
--- a/src/com/android/launcher3/views/AllAppsButton.java
+++ b/src/com/android/launcher3/views/AllAppsButton.java
@@ -45,5 +45,6 @@
Bitmap bitmap = LauncherAppState.getInstance(context).getIconCache().getIconFactory()
.createScaledBitmapWithShadow(theme.getDrawable(R.drawable.ic_all_apps_button));
setIcon(new FastBitmapDrawable(bitmap));
+ setContentDescription(context.getString(R.string.all_apps_button_label));
}
}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index efc83eb..55af622 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 android.view.Gravity.LEFT;
+
import static com.android.launcher3.Utilities.getBadge;
import static com.android.launcher3.Utilities.getFullDrawable;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -181,8 +183,10 @@
updatePosition(positionOut, lp);
setLayoutParams(lp);
- mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
- mBtvDrawable.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
+ // For code simplicity, we always layout the child views using Gravity.LEFT
+ // and manually handle RTL for FloatingIconView when positioning it on the screen.
+ mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height, LEFT));
+ mBtvDrawable.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height, LEFT));
}
private void updatePosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index df3cbff..2d519d9 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -151,6 +151,8 @@
device.executeShellCommand(
"am dumpheap " + device.getLauncherPackageName() + " " + fileName);
}
+ Log.d(TAG, "Saved leak dump, the leak is still present: "
+ + !launcher.noLeakedActivities());
sDumpWasGenerated = true;
result = "saved memory dump as an artifact";
} catch (Throwable e) {
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 43766bf..3986df6 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -239,7 +239,7 @@
// 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.
- mLauncherPackage = testPackage.equals(targetPackage)
+ mLauncherPackage = testPackage.equals(targetPackage) || isGradleInstrumentation()
? getLauncherPackageName()
: targetPackage;
@@ -286,6 +286,20 @@
}
}
+ /**
+ * Gradle only supports out of process instrumentation. The test package is automatically
+ * generated by appending `.test` to the target package.
+ */
+ private boolean isGradleInstrumentation() {
+ final String testPackage = getContext().getPackageName();
+ final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
+ final String testSuffix = ".test";
+
+ return testPackage.endsWith(testSuffix) && testPackage.length() > testSuffix.length()
+ && testPackage.substring(0, testPackage.length() - testSuffix.length())
+ .equals(targetPackage);
+ }
+
public void enableCheckEventsForSuccessfulGestures() {
mCheckEventsForSuccessfulGestures = true;
}
@@ -893,7 +907,14 @@
}
/**
- * Presses nav bar home button.
+ * Goes to home by swiping up in zero-button mode or pressing Home button.
+ * Calling it after another TAPL call is safe because all TAPL methods wait for the animations
+ * to finish.
+ * When calling it after a non-TAPL method, make sure that all animations have already
+ * completed, otherwise it may detect the current state (for example "Application" or "Home")
+ * incorrectly.
+ * The method expects either app or Launcher to be active when it's called. Other states, such
+ * as visible notification shade are not supported.
*
* @return the Workspace object.
*/
@@ -1894,8 +1915,9 @@
/**
* Taps outside container to dismiss.
+ *
* @param container container to be dismissed
- * @param tapRight tap on the right of the container if true, or left otherwise
+ * @param tapRight tap on the right of the container if true, or left otherwise
*/
void touchOutsideContainer(UiObject2 container, boolean tapRight) {
try (LauncherInstrumentation.Closable c = addContextLayer(