Merge "Don't moveToRestState from onTaskAppeared" 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/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/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/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/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 7ae2ae8..f3630c1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1140,13 +1140,6 @@
boolean isCancel) {
long duration = MAX_SWIPE_DURATION;
float currentShift = mCurrentShift.value;
- boolean recentsVisible = mRecentsView != null
- && (mRecentsView.getWindowVisibility() == View.VISIBLE);
- if (!recentsVisible) {
- // We've hit a case where Launcher is been stopped mid-gesture, in this case, force
- // a LAST_TASK end target
- isCancel = true;
- }
final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
isFling, isCancel);
// Set the state, but don't notify until the animation completes
@@ -1226,7 +1219,7 @@
// Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
// or resumeLastTask().
- if (recentsVisible) {
+ if (mRecentsView != null) {
ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent
.SET_ON_PAGE_TRANSITION_END_CALLBACK);
mRecentsView.setOnPageTransitionEndCallback(
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/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/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index bf88702..1a02f03 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -124,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());
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 5bc7f18..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());
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index b00794f..96607a0 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -912,7 +912,7 @@
mSplitSelectStateController = splitController;
}
- public SplitSelectStateController getSplitPlaceholder() {
+ public SplitSelectStateController getSplitSelectController() {
return mSplitSelectStateController;
}
@@ -4272,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 5df03cc..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;
@@ -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());
}
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-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/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5e2187e..a6831aa 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -480,7 +480,6 @@
mAppWidgetHost = createAppWidgetHost();
mAppWidgetHost.startListening();
- inflateRootView(R.layout.launcher);
setupViews();
crossFadeWithPreviousAppearance();
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
@@ -1259,6 +1258,7 @@
* Finds all the views we need and configure them properly.
*/
protected void setupViews() {
+ inflateRootView(R.layout.launcher);
mDragLayer = findViewById(R.id.drag_layer);
mFocusHandler = mDragLayer.getFocusIndicatorHelper();
mWorkspace = mDragLayer.findViewById(R.id.workspace);
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/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index ef6350e..4a47e98 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -94,9 +94,6 @@
getDebugFlag("ENABLE_FLOATING_SEARCH_BAR", false,
"Keep All Apps search bar at the bottom (but above keyboard if open)");
- public static final BooleanFlag ENABLE_QUICK_SEARCH = new DeviceFlag("ENABLE_QUICK_SEARCH",
- true, "Use quick search behavior.");
-
public static final BooleanFlag ENABLE_HIDE_HEADER = new DeviceFlag("ENABLE_HIDE_HEADER",
true, "Hide header on keyboard before typing in all apps");
@@ -281,17 +278,17 @@
"FOLDABLE_WORKSPACE_REORDER", true,
"In foldables, when reordering the icons and widgets, is now going to use both sides");
- public static final BooleanFlag SHOW_SEARCH_EDUCARD_QSB = new DeviceFlag(
- "SHOW_SEARCH_EDUCARD_QSB", false, "Shows Search Educard for QSB entry in OneSearch.");
-
- public static final BooleanFlag ENABLE_IME_LATENCY_LOGGER = getDebugFlag(
- "ENABLE_IME_LATENCY_LOGGER", false,
- "Enable option to log the keyboard latency for both atomic and controlled keyboard "
- + "animations on an EditText");
-
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/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 29eefe2..439e1c7 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,10 +58,14 @@
private static final int DOT_ACTIVE_ALPHA = 255;
private static final int DOT_INACTIVE_ALPHA = 128;
+ private static final int DOT_GAP_FACTOR = 3;
+ private static final float DOT_GAP_FACTOR_FLOAT = 3.8f;
// This value approximately overshoots to 1.5 times the original size.
private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
+ private static final float INDICATOR_ROTATION = 180f;
+
private static final RectF sTempRect = new RectF();
private static final Property<PageIndicatorDots, Float> CURRENT_POSITION
@@ -76,21 +83,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 +120,56 @@
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());
+ if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+ mCircleGap = DOT_GAP_FACTOR_FLOAT * mDotRadius;
+ } else {
+ 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 +207,7 @@
}
public void playEntryAnimation() {
- int count = mEntryAnimationRadiusFactors.length;
+ int count = mEntryAnimationRadiusFactors.length;
if (count == 0) {
mEntryAnimationRadiusFactors = null;
invalidate();
@@ -231,16 +261,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 +282,98 @@
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()) {
+ RectF currRect = getActiveRect();
+ int scrollPerPage = getScrollPerPage();
+
+ // This IF is to avoid division by 0
+ if (scrollPerPage != 0) {
+ int delta = mCurrentScroll % scrollPerPage;
+ canvas.rotate((INDICATOR_ROTATION * delta) / scrollPerPage,
+ currRect.centerX(), currRect.centerY());
+ }
+
+ canvas.drawRect(currRect, 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 +381,33 @@
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;
+ }
+
+ /**
+ * Returns an int that is the amount we need to scroll per page
+ */
+ private int getScrollPerPage() {
+ return mNumPages > 1 ? mTotalScroll / (mNumPages - 1) : 0;
+ }
+
+ /**
+ * The current scroll adjusted for the distance the indicator needs to travel on the screen
+ */
+ private float getIndicatorScrollDistance() {
+ int scrollPerPage = getScrollPerPage();
+ return scrollPerPage != 0 ? ((float) mCurrentScroll / scrollPerPage) * mCircleGap : 0;
+ }
+
private class MyOutlineProver extends ViewOutlineProvider {
@Override
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/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) {