mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-19 02:38:20 +00:00
Merge "Add the KeyboardQuickSwitchView (1/2)" into tm-qpr-dev am: 673e6437b8
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/21087434 Change-Id: Ifdb495e6480338f41650431c6a8944ae9caad639 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
committed by
Automerger Merge Worker
commit
031e3d38b8
@@ -17,11 +17,14 @@
|
||||
file, they need to be loaded at runtime. -->
|
||||
<com.android.quickstep.views.TaskView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:defaultFocusHighlightEnabled="false"
|
||||
android:focusable="true">
|
||||
android:focusable="true"
|
||||
launcher:borderColor="?androidprv:attr/colorAccentSecondaryVariant">
|
||||
|
||||
<com.android.quickstep.views.TaskThumbnailView
|
||||
android:id="@+id/snapshot"
|
||||
|
||||
@@ -22,11 +22,14 @@
|
||||
|
||||
<com.android.quickstep.views.GroupedTaskView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:defaultFocusHighlightEnabled="false"
|
||||
android:focusable="true">
|
||||
android:focusable="true"
|
||||
launcher:borderColor="?androidprv:attr/colorAccentSecondaryVariant">
|
||||
|
||||
<com.android.quickstep.views.TaskThumbnailView
|
||||
android:id="@+id/snapshot"
|
||||
|
||||
@@ -19,4 +19,13 @@
|
||||
<attr name="android:textSize"/>
|
||||
<attr name="android:fontFamily"/>
|
||||
</declare-styleable>
|
||||
|
||||
<!--
|
||||
TaskView specific attributes. These attributes are used to customize a TaskView view in
|
||||
XML files.
|
||||
-->
|
||||
<declare-styleable name="TaskView">
|
||||
<!-- Border color for a keyboard quick switch task views -->
|
||||
<attr name="borderColor" format="color" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
||||
@@ -328,4 +328,7 @@
|
||||
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
|
||||
<!-- starting_surface_exit_animation_window_shift_length -->
|
||||
<dimen name="starting_surface_exit_animation_window_shift_length">20dp</dimen>
|
||||
|
||||
<!-- Keyboard Quick Switch -->
|
||||
<dimen name="keyboard_quick_switch_border_width">4dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.annotation.ColorInt;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.TaskTransitionSpec;
|
||||
import android.view.View;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -49,6 +50,7 @@ import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -379,6 +381,17 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
.getValue() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecentsView getRecentsView() {
|
||||
return mLauncher.getOverviewPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchSplitTasks(View taskView, GroupTask groupTask) {
|
||||
super.launchSplitTasks(taskView, groupTask);
|
||||
mLauncher.launchSplitTasks(taskView, groupTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpLogs(String prefix, PrintWriter pw) {
|
||||
super.dumpLogs(prefix, pw);
|
||||
@@ -399,9 +412,4 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecentsView getRecentsView() {
|
||||
return mLauncher.getOverviewPanel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
|
||||
@@ -228,4 +229,11 @@ public class TaskbarUIController {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the focused task in splitscreen.
|
||||
*
|
||||
* No-op if the view is not yet open.
|
||||
*/
|
||||
public void launchSplitTasks(View taskview, GroupTask groupTask) { }
|
||||
}
|
||||
|
||||
@@ -27,17 +27,45 @@ import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/** Root drag layer for the Taskbar overlay window. */
|
||||
public class TaskbarOverlayDragLayer extends
|
||||
BaseDragLayer<TaskbarOverlayContext> implements
|
||||
ViewTreeObserver.OnComputeInternalInsetsListener {
|
||||
|
||||
private final List<OnClickListener> mOnClickListeners = new CopyOnWriteArrayList<>();
|
||||
private final TouchController mClickListenerTouchController = new TouchController() {
|
||||
@Override
|
||||
public boolean onControllerTouchEvent(MotionEvent ev) {
|
||||
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
|
||||
for (OnClickListener listener : mOnClickListeners) {
|
||||
listener.onClick(TaskbarOverlayDragLayer.this);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
if (isEventOverView(getChildAt(i), ev)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
TaskbarOverlayDragLayer(Context context) {
|
||||
super(context, null, 1);
|
||||
setClipChildren(false);
|
||||
@@ -58,7 +86,10 @@ public class TaskbarOverlayDragLayer extends
|
||||
|
||||
@Override
|
||||
public void recreateControllers() {
|
||||
mControllers = new TouchController[]{mActivity.getDragController()};
|
||||
mControllers = mOnClickListeners.isEmpty()
|
||||
? new TouchController[]{mActivity.getDragController()}
|
||||
: new TouchController[] {
|
||||
mActivity.getDragController(), mClickListenerTouchController};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,6 +129,51 @@ public class TaskbarOverlayDragLayer extends
|
||||
mActivity.getOverlayController().maybeCloseWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given callback to clicks to this drag layer.
|
||||
* <p>
|
||||
* Clicks are only accepted on this drag layer if they fall within this drag layer's bounds and
|
||||
* outside the bounds of all child views.
|
||||
* <p>
|
||||
* If the click falls within the bounds of a child view, then this callback does not run and
|
||||
* that child can optionally handle it.
|
||||
*/
|
||||
private void addOnClickListener(@NonNull OnClickListener listener) {
|
||||
boolean wasEmpty = mOnClickListeners.isEmpty();
|
||||
mOnClickListeners.add(listener);
|
||||
if (wasEmpty) {
|
||||
recreateControllers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given on click callback.
|
||||
* <p>
|
||||
* No-op if the callback was never added.
|
||||
*/
|
||||
private void removeOnClickListener(@NonNull OnClickListener listener) {
|
||||
boolean wasEmpty = mOnClickListeners.isEmpty();
|
||||
mOnClickListeners.remove(listener);
|
||||
if (!wasEmpty && mOnClickListeners.isEmpty()) {
|
||||
recreateControllers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the given callback on the next click on this drag layer.
|
||||
* <p>
|
||||
* Once run, this callback is immediately removed.
|
||||
*/
|
||||
public void runOnClickOnce(@NonNull OnClickListener listener) {
|
||||
addOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onClick(v);
|
||||
removeOnClickListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Taskbar automatically stashes when opening all apps, but we don't report the insets as
|
||||
* changing to avoid moving the underlying app. But internally, the apps view should still
|
||||
|
||||
@@ -50,6 +50,7 @@ import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STA
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
|
||||
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
|
||||
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
|
||||
|
||||
@@ -143,6 +144,7 @@ import com.android.launcher3.util.ObjectWrapper;
|
||||
import com.android.launcher3.util.PendingRequestArgs;
|
||||
import com.android.launcher3.util.PendingSplitSelectInfo;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
@@ -152,6 +154,7 @@ import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.TouchInteractionService.TISBinder;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.util.LauncherUnfoldAnimationController;
|
||||
import com.android.quickstep.util.ProxyScreenStatusProvider;
|
||||
import com.android.quickstep.util.QuickstepOnboardingPrefs;
|
||||
@@ -1206,6 +1209,32 @@ public class QuickstepLauncher extends Launcher {
|
||||
getDeviceProfile().toSmallString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the given {@link GroupTask} in splitscreen.
|
||||
*
|
||||
* If the second split task is missing, launches the first task normally.
|
||||
*/
|
||||
public void launchSplitTasks(View taskView, GroupTask groupTask) {
|
||||
if (groupTask.task2 == null) {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecents(
|
||||
groupTask.task1.key,
|
||||
getActivityLaunchOptions(taskView, null).options));
|
||||
return;
|
||||
}
|
||||
mSplitSelectStateController.launchTasks(
|
||||
groupTask.task1.key.id,
|
||||
groupTask.task2.key.id,
|
||||
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
|
||||
/* callback= */ success -> {},
|
||||
/* freezeTaskList= */ true,
|
||||
groupTask.mSplitBounds == null
|
||||
? DEFAULT_SPLIT_RATIO
|
||||
: groupTask.mSplitBounds.appsStackedVertically
|
||||
? groupTask.mSplitBounds.topTaskPercent
|
||||
: groupTask.mSplitBounds.leftTaskPercent);
|
||||
}
|
||||
|
||||
private static final class LauncherTaskViewController extends
|
||||
TaskViewTouchController<Launcher> {
|
||||
|
||||
|
||||
@@ -24,8 +24,10 @@ import android.graphics.PointF;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Trace;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.BinderThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@@ -48,7 +50,7 @@ import java.util.HashMap;
|
||||
public class OverviewCommandHelper {
|
||||
|
||||
public static final int TYPE_SHOW = 1;
|
||||
public static final int TYPE_SHOW_NEXT_FOCUS = 2;
|
||||
public static final int TYPE_KEYBOARD_INPUT = 2;
|
||||
public static final int TYPE_HIDE = 3;
|
||||
public static final int TYPE_TOGGLE = 4;
|
||||
public static final int TYPE_HOME = 5;
|
||||
@@ -66,6 +68,13 @@ public class OverviewCommandHelper {
|
||||
private final TaskAnimationManager mTaskAnimationManager;
|
||||
private final ArrayList<CommandInfo> mPendingCommands = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Index of the TaskView that should be focused when launching Overview. Persisted so that we
|
||||
* do not lose the focus across multiple calls of
|
||||
* {@link OverviewCommandHelper#executeCommand(CommandInfo)} for the same command
|
||||
*/
|
||||
private int mTaskFocusIndexOverride = -1;
|
||||
|
||||
public OverviewCommandHelper(TouchInteractionService service,
|
||||
OverviewComponentObserver observer,
|
||||
TaskAnimationManager taskAnimationManager) {
|
||||
@@ -179,6 +188,7 @@ public class OverviewCommandHelper {
|
||||
// already visible
|
||||
return true;
|
||||
case TYPE_HIDE: {
|
||||
mTaskFocusIndexOverride = -1;
|
||||
int currentPage = recents.getNextPage();
|
||||
TaskView tv = (currentPage >= 0 && currentPage < recents.getTaskViewCount())
|
||||
? (TaskView) recents.getPageAt(currentPage)
|
||||
@@ -194,15 +204,9 @@ public class OverviewCommandHelper {
|
||||
}
|
||||
|
||||
final Runnable completeCallback = () -> {
|
||||
if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
|
||||
RecentsView rv = activityInterface.getVisibleRecentsView();
|
||||
// When the overview is launched via alt tab (cmd type is TYPE_SHOW_NEXT_FOCUS),
|
||||
// the touch mode somehow is not change to false by the Android framework.
|
||||
// The subsequent tab to go through tasks in overview can only be dispatched to
|
||||
// focuses views, while focus can only be requested in
|
||||
// {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
|
||||
// here we launch overview from home.
|
||||
rv.getViewRootImpl().touchModeChanged(false);
|
||||
RecentsView rv = activityInterface.getVisibleRecentsView();
|
||||
if (rv != null && (cmd.type == TYPE_KEYBOARD_INPUT || cmd.type == TYPE_HIDE)) {
|
||||
updateRecentsViewFocus(rv);
|
||||
}
|
||||
scheduleNextTask(cmd);
|
||||
};
|
||||
@@ -280,40 +284,55 @@ public class OverviewCommandHelper {
|
||||
cmd.removeListener(handler);
|
||||
Trace.endAsyncSection(TRANSITION_NAME, 0);
|
||||
|
||||
if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
|
||||
RecentsView rv =
|
||||
mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
|
||||
if (rv != null) {
|
||||
// When the overview is launched via alt tab (cmd type is TYPE_SHOW_NEXT_FOCUS),
|
||||
// the touch mode somehow is not change to false by the Android framework.
|
||||
// The subsequent tab to go through tasks in overview can only be dispatched to
|
||||
// focuses views, while focus can only be requested in
|
||||
// {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
|
||||
// here we launch overview with live tile.
|
||||
rv.getViewRootImpl().touchModeChanged(false);
|
||||
// Ensure that recents view has focus so that it receives the followup key inputs
|
||||
TaskView taskView = rv.getNextTaskView();
|
||||
if (taskView == null) {
|
||||
taskView = rv.getTaskViewAt(0);
|
||||
if (taskView != null) {
|
||||
taskView.requestFocus();
|
||||
} else {
|
||||
rv.requestFocus();
|
||||
}
|
||||
} else {
|
||||
taskView.requestFocus();
|
||||
}
|
||||
}
|
||||
RecentsView rv =
|
||||
mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
|
||||
if (rv != null && (cmd.type == TYPE_KEYBOARD_INPUT || cmd.type == TYPE_HIDE)) {
|
||||
updateRecentsViewFocus(rv);
|
||||
}
|
||||
scheduleNextTask(cmd);
|
||||
}
|
||||
|
||||
private void updateRecentsViewFocus(@NonNull RecentsView rv) {
|
||||
// When the overview is launched via alt tab (cmd type is TYPE_KEYBOARD_INPUT),
|
||||
// the touch mode somehow is not change to false by the Android framework.
|
||||
// The subsequent tab to go through tasks in overview can only be dispatched to
|
||||
// focuses views, while focus can only be requested in
|
||||
// {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
|
||||
// here we launch overview with live tile.
|
||||
rv.getViewRootImpl().touchModeChanged(false);
|
||||
// Ensure that recents view has focus so that it receives the followup key inputs
|
||||
TaskView taskView = rv.getTaskViewAt(mTaskFocusIndexOverride);
|
||||
if (taskView != null) {
|
||||
requestFocus(taskView);
|
||||
return;
|
||||
}
|
||||
taskView = rv.getNextTaskView();
|
||||
if (taskView != null) {
|
||||
requestFocus(taskView);
|
||||
return;
|
||||
}
|
||||
taskView = rv.getTaskViewAt(0);
|
||||
if (taskView != null) {
|
||||
requestFocus(taskView);
|
||||
return;
|
||||
}
|
||||
requestFocus(rv);
|
||||
}
|
||||
|
||||
private void requestFocus(@NonNull View view) {
|
||||
view.post(() -> {
|
||||
view.requestFocus();
|
||||
view.requestAccessibilityFocus();
|
||||
});
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.println("OverviewCommandHelper:");
|
||||
pw.println(" mPendingCommands=" + mPendingCommands.size());
|
||||
if (!mPendingCommands.isEmpty()) {
|
||||
pw.println(" pendingCommandType=" + mPendingCommands.get(0).type);
|
||||
}
|
||||
pw.println(" mTaskFocusIndexOverride=" + mTaskFocusIndexOverride);
|
||||
}
|
||||
|
||||
private static class CommandInfo {
|
||||
|
||||
@@ -205,7 +205,7 @@ public class TouchInteractionService extends Service
|
||||
public void onOverviewShown(boolean triggeredFromAltTab) {
|
||||
if (triggeredFromAltTab) {
|
||||
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
|
||||
mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW_NEXT_FOCUS);
|
||||
mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_KEYBOARD_INPUT);
|
||||
} else {
|
||||
mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
|
||||
}
|
||||
|
||||
163
quickstep/src/com/android/quickstep/util/BorderAnimator.java
Normal file
163
quickstep/src/com/android/quickstep/util/BorderAnimator.java
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep.util;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Px;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
|
||||
/**
|
||||
* Utility class for drawing a rounded-rect border around a view.
|
||||
* <p>
|
||||
* To use this class:
|
||||
* 1. Create an instance in the target view.
|
||||
* 2. Override the target view's {@link android.view.View#draw(Canvas)} method and call
|
||||
* {@link BorderAnimator#drawBorder(Canvas)} after {@code super.draw(canvas)}.
|
||||
* 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation where appropriate.
|
||||
*/
|
||||
public final class BorderAnimator {
|
||||
|
||||
public static final int DEFAULT_BORDER_COLOR = 0xffffffff;
|
||||
|
||||
private static final long DEFAULT_APPEARANCE_ANIMATION_DURATION_MS = 300;
|
||||
private static final long DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS = 133;
|
||||
private static final Interpolator DEFAULT_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE;
|
||||
|
||||
@NonNull private final AnimatedFloat mBorderAnimationProgress = new AnimatedFloat(
|
||||
this::updateOutline);
|
||||
@NonNull private final Rect mBorderBounds = new Rect();
|
||||
@NonNull private final BorderBoundsBuilder mBorderBoundsBuilder;
|
||||
@Px private final int mBorderWidthPx;
|
||||
@Px private final int mBorderRadiusPx;
|
||||
@NonNull private final Runnable mInvalidateViewCallback;
|
||||
private final long mAppearanceDurationMs;
|
||||
private final long mDisappearanceDurationMs;
|
||||
@NonNull private final Interpolator mInterpolator;
|
||||
@NonNull private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private int mAlignmentAdjustment;
|
||||
|
||||
@Nullable private Animator mRunningBorderAnimation;
|
||||
|
||||
public BorderAnimator(
|
||||
@NonNull BorderBoundsBuilder borderBoundsBuilder,
|
||||
int borderWidthPx,
|
||||
int borderRadiusPx,
|
||||
@ColorInt int borderColor,
|
||||
@NonNull Runnable invalidateViewCallback) {
|
||||
this(borderBoundsBuilder,
|
||||
borderWidthPx,
|
||||
borderRadiusPx,
|
||||
borderColor,
|
||||
invalidateViewCallback,
|
||||
DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
|
||||
DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
|
||||
DEFAULT_INTERPOLATOR);
|
||||
}
|
||||
|
||||
public BorderAnimator(
|
||||
@NonNull BorderBoundsBuilder borderBoundsBuilder,
|
||||
int borderWidthPx,
|
||||
int borderRadiusPx,
|
||||
@ColorInt int borderColor,
|
||||
@NonNull Runnable invalidateViewCallback,
|
||||
long appearanceDurationMs,
|
||||
long disappearanceDurationMs,
|
||||
@NonNull Interpolator interpolator) {
|
||||
mBorderBoundsBuilder = borderBoundsBuilder;
|
||||
mBorderWidthPx = borderWidthPx;
|
||||
mBorderRadiusPx = borderRadiusPx;
|
||||
mInvalidateViewCallback = invalidateViewCallback;
|
||||
mAppearanceDurationMs = appearanceDurationMs;
|
||||
mDisappearanceDurationMs = disappearanceDurationMs;
|
||||
mInterpolator = interpolator;
|
||||
|
||||
mBorderPaint.setColor(borderColor);
|
||||
mBorderPaint.setStyle(Paint.Style.STROKE);
|
||||
mBorderPaint.setAlpha(0);
|
||||
}
|
||||
|
||||
private void updateOutline() {
|
||||
float interpolatedProgress = mInterpolator.getInterpolation(
|
||||
mBorderAnimationProgress.value);
|
||||
mAlignmentAdjustment = (int) Utilities.mapBoundToRange(
|
||||
mBorderAnimationProgress.value,
|
||||
/* lowerBound= */ 0f,
|
||||
/* upperBound= */ 1f,
|
||||
/* toMin= */ 0f,
|
||||
/* toMax= */ (float) (mBorderWidthPx / 2f),
|
||||
mInterpolator);
|
||||
|
||||
mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress));
|
||||
mBorderPaint.setStrokeWidth(Math.round(mBorderWidthPx * interpolatedProgress));
|
||||
mInvalidateViewCallback.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the border on the given canvas.
|
||||
* <p>
|
||||
* Call this method in the target view's {@link android.view.View#draw(Canvas)} method after
|
||||
* calling super.
|
||||
*/
|
||||
public void drawBorder(Canvas canvas) {
|
||||
canvas.drawRoundRect(
|
||||
/* left= */ mBorderBounds.left + mAlignmentAdjustment,
|
||||
/* top= */ mBorderBounds.top + mAlignmentAdjustment,
|
||||
/* right= */ mBorderBounds.right - mAlignmentAdjustment,
|
||||
/* bottom= */ mBorderBounds.bottom - mAlignmentAdjustment,
|
||||
/* rx= */ mBorderRadiusPx - mAlignmentAdjustment,
|
||||
/* ry= */ mBorderRadiusPx - mAlignmentAdjustment,
|
||||
/* paint= */ mBorderPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the border appearance/disappearance animation.
|
||||
*/
|
||||
public Animator buildAnimator(boolean isAppearing) {
|
||||
mBorderBoundsBuilder.updateBorderBounds(mBorderBounds);
|
||||
mRunningBorderAnimation = mBorderAnimationProgress.animateToValue(isAppearing ? 1f : 0f);
|
||||
mRunningBorderAnimation.setDuration(
|
||||
isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs);
|
||||
|
||||
mRunningBorderAnimation.addListener(
|
||||
AnimatorListeners.forEndCallback(() -> mRunningBorderAnimation = null));
|
||||
|
||||
return mRunningBorderAnimation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to update the border bounds when building this animation.
|
||||
*/
|
||||
public interface BorderBoundsBuilder {
|
||||
|
||||
/**
|
||||
* Sets the given rect to the most up-to-date bounds.
|
||||
*/
|
||||
void updateBorderBounds(Rect rect);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
@@ -71,6 +72,23 @@ public class GroupedTaskView extends TaskView {
|
||||
mDigitalWellBeingToast2 = new DigitalWellBeingToast(mActivity, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateBorderBounds(Rect bounds) {
|
||||
if (mSplitBoundsConfig == null) {
|
||||
super.updateBorderBounds(bounds);
|
||||
return;
|
||||
}
|
||||
bounds.set(
|
||||
Math.min(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()),
|
||||
mSnapshotView2.getLeft() + Math.round(mSnapshotView2.getTranslationX())),
|
||||
Math.min(mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()),
|
||||
mSnapshotView2.getTop() + Math.round(mSnapshotView2.getTranslationY())),
|
||||
Math.max(mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()),
|
||||
mSnapshotView2.getRight() + Math.round(mSnapshotView2.getTranslationX())),
|
||||
Math.max(mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()),
|
||||
mSnapshotView2.getBottom() + Math.round(mSnapshotView2.getTranslationY())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
@@ -31,6 +31,7 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
|
||||
import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
@@ -42,6 +43,7 @@ import android.annotation.IdRes;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
@@ -94,6 +96,7 @@ import com.android.quickstep.TaskOverlayFactory;
|
||||
import com.android.quickstep.TaskThumbnailCache;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.TaskViewUtils;
|
||||
import com.android.quickstep.util.BorderAnimator;
|
||||
import com.android.quickstep.util.CancellableTask;
|
||||
import com.android.quickstep.util.RecentsOrientedState;
|
||||
import com.android.quickstep.util.SplitSelectStateController;
|
||||
@@ -405,6 +408,8 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
|
||||
private boolean mIsClickableAsLiveTile = true;
|
||||
|
||||
@Nullable private final BorderAnimator mBorderAnimator;
|
||||
|
||||
public TaskView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -414,12 +419,46 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
}
|
||||
|
||||
public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public TaskView(
|
||||
Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
mActivity = StatefulActivity.fromContext(context);
|
||||
setOnClickListener(this::onClick);
|
||||
|
||||
mCurrentFullscreenParams = new FullscreenDrawParams(context);
|
||||
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
|
||||
|
||||
setWillNotDraw(!FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get());
|
||||
|
||||
mBorderAnimator = !FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
|
||||
? null
|
||||
: new BorderAnimator(
|
||||
/* borderBoundsBuilder= */ this::updateBorderBounds,
|
||||
/* borderWidthPx= */ context.getResources().getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_border_width),
|
||||
/* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
|
||||
/* borderColor= */ attrs == null
|
||||
? DEFAULT_BORDER_COLOR
|
||||
: context.getTheme()
|
||||
.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.TaskView,
|
||||
defStyleAttr,
|
||||
defStyleRes)
|
||||
.getColor(
|
||||
R.styleable.TaskView_borderColor,
|
||||
DEFAULT_BORDER_COLOR),
|
||||
/* invalidateViewCallback= */ TaskView.this::invalidate);
|
||||
}
|
||||
|
||||
protected void updateBorderBounds(Rect bounds) {
|
||||
bounds.set(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()),
|
||||
mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()),
|
||||
mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()),
|
||||
mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()));
|
||||
}
|
||||
|
||||
public void setTaskViewId(int id) {
|
||||
@@ -463,6 +502,22 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
mIconTouchDelegate = new TransformingTouchDelegate(mIconView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
|
||||
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
|
||||
if (mBorderAnimator != null) {
|
||||
mBorderAnimator.buildAnimator(gainFocus).start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
if (mBorderAnimator != null) {
|
||||
mBorderAnimator.drawBorder(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the taskview should take the touch event from parent. Events passed to children
|
||||
* that might require special handling.
|
||||
|
||||
@@ -411,6 +411,10 @@ public final class FeatureFlags {
|
||||
"Enables receiving unfold animation events from sysui instead of calculating "
|
||||
+ "them in launcher process using hinge sensor values.");
|
||||
|
||||
public static final BooleanFlag ENABLE_KEYBOARD_QUICK_SWITCH = getDebugFlag(
|
||||
"ENABLE_KEYBOARD_QUICK_SWITCH", false,
|
||||
"Enables keyboard quick switching");
|
||||
|
||||
public static void initialize(Context context) {
|
||||
synchronized (sDebugFlags) {
|
||||
for (DebugFlag flag : sDebugFlags) {
|
||||
|
||||
Reference in New Issue
Block a user