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:
Schneider Victor-tulias
2023-02-14 18:52:06 +00:00
committed by Automerger Merge Worker
14 changed files with 442 additions and 44 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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) { }
}

View File

@@ -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

View File

@@ -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> {

View File

@@ -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 {

View File

@@ -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);
}

View 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);
}
}

View File

@@ -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();

View File

@@ -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.

View File

@@ -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) {