diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java index 787aa19410..0565f7e976 100644 --- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java @@ -17,7 +17,6 @@ package com.android.launcher3.taskbar; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y_LONG_CLICK; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH; @@ -90,7 +89,6 @@ public class NavbarButtonsViewController { private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE; - private View.OnLongClickListener mA11yLongClickListener; private final ArrayList mPropertyHolders = new ArrayList<>(); private final ArrayList mAllButtons = new ArrayList<>(); private int mState; @@ -142,11 +140,6 @@ public class NavbarButtonsViewController { mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize; mNavButtonTranslationYMultiplier.value = 1; - mA11yLongClickListener = view -> { - mControllers.navButtonController.onButtonClick(BUTTON_A11Y_LONG_CLICK); - return true; - }; - mPropertyHolders.add(new StatePropertyHolder( mControllers.taskbarViewController.getTaskbarIconAlpha() .getProperty(ALPHA_INDEX_IME), @@ -285,7 +278,6 @@ public class NavbarButtonsViewController { mPropertyHolders.add(new StatePropertyHolder(mA11yButton, flags -> (flags & FLAG_A11Y_VISIBLE) != 0 && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)); - mA11yButton.setOnLongClickListener(mA11yLongClickListener); } private void parseSystemUiFlags(int sysUiStateFlags) { @@ -441,6 +433,8 @@ public class NavbarButtonsViewController { ImageView buttonView = addButton(parent, id, layoutId); buttonView.setImageResource(drawableId); buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType)); + buttonView.setOnLongClickListener(view -> + navButtonController.onButtonLongClick(buttonType)); return buttonView; } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java index a8a0b59f87..ae23eda2a4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java @@ -16,9 +16,11 @@ package com.android.launcher3.taskbar; -import static android.view.Display.DEFAULT_DISPLAY; -import android.view.inputmethod.InputMethodManager; +import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS; +import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY; + +import android.os.Bundle; import androidx.annotation.IntDef; @@ -35,11 +37,9 @@ import java.lang.annotation.RetentionPolicy; * Controller for 3 button mode in the taskbar. * Handles all the functionality of the various buttons, making/routing the right calls into * launcher or sysui/system. - * - * TODO: Create callbacks to hook into UI layer since state will change for more context buttons/ - * assistant invocation. */ public class TaskbarNavButtonController { + @Retention(RetentionPolicy.SOURCE) @IntDef(value = { BUTTON_BACK, @@ -47,7 +47,6 @@ public class TaskbarNavButtonController { BUTTON_RECENTS, BUTTON_IME_SWITCH, BUTTON_A11Y, - BUTTON_A11Y_LONG_CLICK }) public @interface TaskbarButton {} @@ -57,7 +56,6 @@ public class TaskbarNavButtonController { static final int BUTTON_RECENTS = BUTTON_HOME << 1; static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1; static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1; - static final int BUTTON_A11Y_LONG_CLICK = BUTTON_A11Y << 1; private final TouchInteractionService mService; @@ -82,9 +80,22 @@ public class TaskbarNavButtonController { case BUTTON_A11Y: notifyImeClick(false /* longClick */); break; - case BUTTON_A11Y_LONG_CLICK: + } + } + + public boolean onButtonLongClick(@TaskbarButton int buttonType) { + switch (buttonType) { + case BUTTON_HOME: + startAssistant(); + return true; + case BUTTON_A11Y: notifyImeClick(true /* longClick */); - break; + return true; + case BUTTON_BACK: + case BUTTON_IME_SWITCH: + case BUTTON_RECENTS: + default: + return false; } } @@ -113,4 +124,11 @@ public class TaskbarNavButtonController { systemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId()); } } + + private void startAssistant() { + Bundle args = new Bundle(); + args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS); + SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate(); + systemUiProxy.startAssistant(args); + } } diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index c5f4a53877..097850fd6f 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -36,6 +36,7 @@ import com.android.wm.shell.recents.IRecentTasksListener; import com.android.wm.shell.util.GroupedRecentTaskInfo; import com.android.wm.shell.util.StagedSplitBounds; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.function.Consumer; @@ -219,6 +220,26 @@ public class RecentTasksList { return newTasks; } + public void dump(String prefix, PrintWriter writer) { + writer.println(prefix + "RecentTasksList:"); + writer.println(prefix + " mChangeId=" + mChangeId); + writer.println(prefix + " mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks="); + for (GroupTask task : mResultsUi) { + writer.println(prefix + " t1=" + task.task1.key.id + + " t2=" + (task.hasMultipleTasks() ? task.task2.key.id : "-1")); + } + writer.println(prefix + " ]"); + int currentUserId = Process.myUserHandle().getIdentifier(); + ArrayList rawTasks = + mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId); + writer.println(prefix + " rawTasks=["); + for (GroupedRecentTaskInfo task : rawTasks) { + writer.println(prefix + " t1=" + task.mTaskInfo1.taskId + + " t2=" + (task.mTaskInfo2 != null ? task.mTaskInfo2.taskId : "-1")); + } + writer.println(prefix + " ]"); + } + private static class TaskLoadResult extends ArrayList { final int mRequestId; diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index e539a8c7c4..5d77a6e916 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -43,6 +43,7 @@ import com.android.systemui.shared.system.KeyguardManagerCompat; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -220,6 +221,11 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL mThumbnailChangeListeners.remove(listener); } + public void dump(String prefix, PrintWriter writer) { + writer.println(prefix + "RecentsModel:"); + mTaskList.dump(" ", writer); + } + /** * Listener for receiving various task properties changes */ diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 68b7558cf2..c8abd14d22 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -83,14 +83,16 @@ public class SystemUiProxy implements ISystemUiProxy, MAIN_EXECUTOR.execute(() -> clearProxy()); }; - // Save the listeners passed into the proxy since when set/register these listeners, - // setProxy may not have been called, eg. OverviewProxyService is not connected yet. - private IPipAnimationListener mPendingPipAnimationListener; - private ISplitScreenListener mPendingSplitScreenListener; - private IStartingWindowListener mPendingStartingWindowListener; - private ISmartspaceCallback mPendingSmartspaceCallback; - private IRecentTasksListener mPendingRecentTasksListener; - private final ArrayList mPendingRemoteTransitions = new ArrayList<>(); + // Save the listeners passed into the proxy since OverviewProxyService may not have been bound + // yet, and we'll need to set/register these listeners with SysUI when they do. Note that it is + // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely + // in case SysUI needs to rebind. + private IPipAnimationListener mPipAnimationListener; + private ISplitScreenListener mSplitScreenListener; + private IStartingWindowListener mStartingWindowListener; + private ISmartspaceCallback mSmartspaceCallback; + private IRecentTasksListener mRecentTasksListener; + private final ArrayList mRemoteTransitions = new ArrayList<>(); // Used to dedupe calls to SystemUI private int mLastShelfHeight; @@ -167,29 +169,23 @@ public class SystemUiProxy implements ISystemUiProxy, mRecentTasks = recentTasks; linkToDeath(); // re-attach the listeners once missing due to setProxy has not been initialized yet. - if (mPendingPipAnimationListener != null && mPip != null) { - setPinnedStackAnimationListener(mPendingPipAnimationListener); - mPendingPipAnimationListener = null; + if (mPipAnimationListener != null && mPip != null) { + setPinnedStackAnimationListener(mPipAnimationListener); } - if (mPendingSplitScreenListener != null && mSplitScreen != null) { - registerSplitScreenListener(mPendingSplitScreenListener); - mPendingSplitScreenListener = null; + if (mSplitScreenListener != null && mSplitScreen != null) { + registerSplitScreenListener(mSplitScreenListener); } - if (mPendingStartingWindowListener != null && mStartingWindow != null) { - setStartingWindowListener(mPendingStartingWindowListener); - mPendingStartingWindowListener = null; + if (mStartingWindowListener != null && mStartingWindow != null) { + setStartingWindowListener(mStartingWindowListener); } - if (mPendingSmartspaceCallback != null && mSmartspaceTransitionController != null) { - setSmartspaceCallback(mPendingSmartspaceCallback); - mPendingSmartspaceCallback = null; + if (mSmartspaceCallback != null && mSmartspaceTransitionController != null) { + setSmartspaceCallback(mSmartspaceCallback); } - for (int i = mPendingRemoteTransitions.size() - 1; i >= 0; --i) { - registerRemoteTransition(mPendingRemoteTransitions.get(i)); + for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) { + registerRemoteTransition(mRemoteTransitions.get(i)); } - mPendingRemoteTransitions.clear(); - if (mPendingRecentTasksListener != null && mRecentTasks != null) { - registerRecentTasksListener(mPendingRecentTasksListener); - mPendingRecentTasksListener = null; + if (mRecentTasksListener != null && mRecentTasks != null) { + registerRecentTasksListener(mRecentTasksListener); } if (mPendingSetNavButtonAlpha != null) { @@ -513,9 +509,8 @@ public class SystemUiProxy implements ISystemUiProxy, } catch (RemoteException e) { Log.w(TAG, "Failed call setPinnedStackAnimationListener", e); } - } else { - mPendingPipAnimationListener = listener; } + mPipAnimationListener = listener; } public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, @@ -553,9 +548,8 @@ public class SystemUiProxy implements ISystemUiProxy, } catch (RemoteException e) { Log.w(TAG, "Failed call registerSplitScreenListener"); } - } else { - mPendingSplitScreenListener = listener; } + mSplitScreenListener = listener; } public void unregisterSplitScreenListener(ISplitScreenListener listener) { @@ -566,7 +560,7 @@ public class SystemUiProxy implements ISystemUiProxy, Log.w(TAG, "Failed call unregisterSplitScreenListener"); } } - mPendingSplitScreenListener = null; + mSplitScreenListener = null; } /** Start multiple tasks in split-screen simultaneously. */ @@ -687,9 +681,8 @@ public class SystemUiProxy implements ISystemUiProxy, } catch (RemoteException e) { Log.w(TAG, "Failed call registerRemoteTransition"); } - } else { - mPendingRemoteTransitions.add(remoteTransition); } + mRemoteTransitions.add(remoteTransition); } public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) { @@ -700,7 +693,7 @@ public class SystemUiProxy implements ISystemUiProxy, Log.w(TAG, "Failed call registerRemoteTransition"); } } - mPendingRemoteTransitions.remove(remoteTransition); + mRemoteTransitions.remove(remoteTransition); } // @@ -717,9 +710,8 @@ public class SystemUiProxy implements ISystemUiProxy, } catch (RemoteException e) { Log.w(TAG, "Failed call setStartingWindowListener", e); } - } else { - mPendingStartingWindowListener = listener; } + mStartingWindowListener = listener; } // @@ -733,9 +725,8 @@ public class SystemUiProxy implements ISystemUiProxy, } catch (RemoteException e) { Log.w(TAG, "Failed call setStartingWindowListener", e); } - } else { - mPendingSmartspaceCallback = callback; } + mSmartspaceCallback = callback; } // @@ -749,9 +740,8 @@ public class SystemUiProxy implements ISystemUiProxy, } catch (RemoteException e) { Log.w(TAG, "Failed call registerRecentTasksListener", e); } - } else { - mPendingRecentTasksListener = listener; } + mRecentTasksListener = listener; } public void unregisterRecentTasksListener(IRecentTasksListener listener) { @@ -762,7 +752,7 @@ public class SystemUiProxy implements ISystemUiProxy, Log.w(TAG, "Failed call unregisterRecentTasksListener"); } } - mPendingRecentTasksListener = null; + mRecentTasksListener = null; } public ArrayList getRecentTasks(int numTasks, int userId) { diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index 8c4ba97c07..cbdbdb5571 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -72,7 +72,13 @@ public interface TaskShortcutFactory { @Override public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskIdAttributeContainer taskContainer) { - return new AppInfo(activity, taskContainer.getItemInfo()); + TaskView taskView = taskContainer.getTaskView(); + AppInfo.SplitAccessibilityInfo accessibilityInfo = + new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(), + TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()), + taskContainer.getA11yNodeId() + ); + return new AppInfo(activity, taskContainer.getItemInfo(), accessibilityInfo); } @Override diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index e1894ae8ca..6c69171b2a 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -951,6 +951,7 @@ public class TouchInteractionService extends Service pw.println(" resumed=" + resumed); pw.println(" mConsumer=" + mConsumer.getName()); ActiveGestureLog.INSTANCE.dump("", pw); + RecentsModel.INSTANCE.get(this).dump("", pw); pw.println("ProtoTrace:"); pw.println(" file=" + ProtoTracer.INSTANCE.get(this).getTraceFile()); } diff --git a/quickstep/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java index 184ab17ae2..e290be8e33 100644 --- a/quickstep/src/com/android/quickstep/ViewUtils.java +++ b/quickstep/src/com/android/quickstep/ViewUtils.java @@ -15,8 +15,10 @@ */ package com.android.quickstep; +import android.graphics.HardwareRenderer; import android.os.Handler; import android.view.View; +import android.view.ViewRootImpl; import com.android.launcher3.Utilities; import com.android.systemui.shared.system.ViewRootImplCompat; @@ -45,9 +47,9 @@ public class ViewUtils { return new FrameHandler(view, onFinishRunnable, canceled).schedule(); } - private static class FrameHandler implements LongConsumer { + private static class FrameHandler implements HardwareRenderer.FrameDrawingCallback { - final ViewRootImplCompat mViewRoot; + final ViewRootImpl mViewRoot; final Runnable mFinishCallback; final BooleanSupplier mCancelled; final Handler mHandler; @@ -55,14 +57,14 @@ public class ViewUtils { int mDeferFrameCount = 1; FrameHandler(View view, Runnable finishCallback, BooleanSupplier cancelled) { - mViewRoot = new ViewRootImplCompat(view); + mViewRoot = view.getViewRootImpl(); mFinishCallback = finishCallback; mCancelled = cancelled; mHandler = new Handler(); } @Override - public void accept(long l) { + public void onFrameDraw(long frame) { Utilities.postAsyncCallback(mHandler, this::onFrame); } @@ -83,7 +85,7 @@ public class ViewUtils { } private boolean schedule() { - if (mViewRoot.isValid()) { + if (mViewRoot.getView() != null) { mViewRoot.registerRtFrameCallback(this); mViewRoot.getView().invalidate(); return true; diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java index 510820a1e5..162ace4965 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java @@ -24,6 +24,8 @@ import static android.view.MotionEvent.ACTION_POINTER_DOWN; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; +import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_GESTURE; +import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY; import static com.android.launcher3.Utilities.squaredHypot; import android.animation.Animator; @@ -64,8 +66,6 @@ public class AssistantInputConsumer extends DelegateInputConsumer { private static final String OPA_BUNDLE_TRIGGER = "triggered_by"; // From //java/com/google/android/apps/gsa/assistant/shared/proto/opa_trigger.proto. private static final int OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE = 83; - private static final String INVOCATION_TYPE_KEY = "invocation_type"; - private static final int INVOCATION_TYPE_GESTURE = 1; private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); diff --git a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java index 3b4fd31fa5..3b1c150563 100644 --- a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java +++ b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java @@ -22,10 +22,10 @@ import android.os.Message; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.View; +import android.view.ViewRootImpl; import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; -import com.android.systemui.shared.system.ViewRootImplCompat; import java.util.function.Consumer; @@ -41,7 +41,7 @@ public class SurfaceTransactionApplier extends ReleaseCheck { private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0; private final SurfaceControl mBarrierSurfaceControl; - private final ViewRootImplCompat mTargetViewRootImpl; + private final ViewRootImpl mTargetViewRootImpl; private final Handler mApplyHandler; private int mLastSequenceNumber = 0; @@ -50,8 +50,8 @@ public class SurfaceTransactionApplier extends ReleaseCheck { * @param targetView The view in the surface that acts as synchronization anchor. */ public SurfaceTransactionApplier(View targetView) { - mTargetViewRootImpl = new ViewRootImplCompat(targetView); - mBarrierSurfaceControl = mTargetViewRootImpl.getRenderSurfaceControl(); + mTargetViewRootImpl = targetView.getViewRootImpl(); + mBarrierSurfaceControl = mTargetViewRootImpl.getSurfaceControl(); mApplyHandler = new Handler(this::onApplyMessage); } @@ -109,7 +109,7 @@ public class SurfaceTransactionApplier extends ReleaseCheck { if (targetView == null) { // No target view, no applier callback.accept(null); - } else if (new ViewRootImplCompat(targetView).isValid()) { + } else if (targetView.isAttachedToWindow()) { // Already attached, we're good to go callback.accept(new SurfaceTransactionApplier(targetView)); } else { diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java index c3b166fada..adea1a40cf 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java +++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java @@ -136,7 +136,7 @@ final class FloatingWidgetBackgroundView extends View { } /** Returns the maximum corner radius of {@param drawable}. */ - private static float getMaxRadius(Drawable drawable) { + private static float getMaxRadius(@Nullable Drawable drawable) { if (!(drawable instanceof GradientDrawable)) return 0; float[] cornerRadii = ((GradientDrawable) drawable).getCornerRadii(); float cornerRadius = ((GradientDrawable) drawable).getCornerRadius(); diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java index ccb1a991ea..5895c05dcd 100644 --- a/quickstep/src/com/android/quickstep/views/IconView.java +++ b/quickstep/src/com/android/quickstep/views/IconView.java @@ -87,6 +87,14 @@ public class IconView extends View { return mDrawable; } + public int getDrawableWidth() { + return mDrawableWidth; + } + + public int getDrawableHeight() { + return mDrawableHeight; + } + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index ddd2a8293f..4b9b8a4c55 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -2612,10 +2612,8 @@ public abstract class RecentsView secondaryViewTranslate = taskView.getSecondaryDissmissTranslationProperty(); int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView); @@ -4681,6 +4679,15 @@ public abstract class RecentsView getEventDispatcher(float navbarRotation) { float degreesRotated; if (navbarRotation == 0) { diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt index cd1691ba53..06a579300a 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt +++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt @@ -45,7 +45,10 @@ class TaskMenuViewWithArrow : ArrowPopup { companion object { const val TAG = "TaskMenuViewWithArrow" - fun showForTask(taskContainer: TaskIdAttributeContainer): Boolean { + fun showForTask( + taskContainer: TaskIdAttributeContainer, + alignSecondRow: Boolean = false + ): Boolean { val activity = BaseDraggingActivity .fromContext(taskContainer.taskView.context) val taskMenuViewWithArrow = activity.layoutInflater @@ -55,7 +58,7 @@ class TaskMenuViewWithArrow : ArrowPopup { false ) as TaskMenuViewWithArrow<*> - return taskMenuViewWithArrow.populateAndShowForTask(taskContainer) + return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignSecondRow) } } @@ -78,6 +81,9 @@ class TaskMenuViewWithArrow : ArrowPopup { CLOSE_FADE_DURATION = CLOSE_CHILD_FADE_DURATION } + private var alignSecondRow: Boolean = false + private val extraSpaceForSecondRowAlignment: Int + get() = if (alignSecondRow) optionMeasuredHeight else 0 private val menuWidth = context.resources.getDimensionPixelSize(R.dimen.task_menu_width_grid) private lateinit var taskView: TaskView @@ -91,6 +97,10 @@ class TaskMenuViewWithArrow : ArrowPopup { else 0 + private var iconView: IconView? = null + private var scrim: View? = null + private val scrimAlpha = 0.8f + override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0 override fun getTargetObjectLocation(outPos: Rect?) { @@ -112,18 +122,35 @@ class TaskMenuViewWithArrow : ArrowPopup { optionLayout = findViewById(KtR.id.menu_option_layout) } - private fun populateAndShowForTask(taskContainer: TaskIdAttributeContainer): Boolean { + private fun populateAndShowForTask( + taskContainer: TaskIdAttributeContainer, + alignSecondRow: Boolean + ): Boolean { if (isAttachedToWindow) { return false } taskView = taskContainer.taskView this.taskContainer = taskContainer + this.alignSecondRow = alignSecondRow if (!populateMenu()) return false + addScrim() show() return true } + private fun addScrim() { + scrim = View(context).apply { + layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ) + setBackgroundColor(Themes.getAttrColor(context, R.attr.overviewScrimColor)) + alpha = 0f + } + popupContainer.addView(scrim) + } + /** @return true if successfully able to populate task view menu, false otherwise */ private fun populateMenu(): Boolean { @@ -180,18 +207,50 @@ class TaskMenuViewWithArrow : ArrowPopup { } override fun onCreateOpenAnimation(anim: AnimatorSet) { - anim.play( - ObjectAnimator.ofFloat( - taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA, - TaskView.MAX_PAGE_SCRIM_ALPHA + scrim?.let { + anim.play( + ObjectAnimator.ofFloat(it, View.ALPHA, 0f, scrimAlpha) + .setDuration(OPEN_DURATION.toLong()) ) - ) + } } override fun onCreateCloseAnimation(anim: AnimatorSet) { - anim.play( - ObjectAnimator.ofFloat(taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA, 0f) - ) + scrim?.let { + anim.play( + ObjectAnimator.ofFloat(it, View.ALPHA, scrimAlpha, 0f) + .setDuration(CLOSE_DURATION.toLong()) + ) + } + } + + override fun closeComplete() { + super.closeComplete() + popupContainer.removeView(scrim) + popupContainer.removeView(iconView) + } + + /** + * Copy the iconView from taskView to dragLayer so it can stay on top of the scrim. + * It needs to be called after [getTargetObjectLocation] because [mTempRect] needs to be + * populated. + */ + private fun copyIconToDragLayer(insets: Rect) { + iconView = IconView(context).apply { + layoutParams = FrameLayout.LayoutParams( + taskContainer.iconView.width, + taskContainer.iconView.height + ) + x = mTempRect.left.toFloat() - insets.left + y = mTempRect.top.toFloat() - insets.top + drawable = taskContainer.iconView.drawable + setDrawableSize( + taskContainer.iconView.drawableWidth, + taskContainer.iconView.drawableHeight + ) + } + + popupContainer.addView(iconView) } /** @@ -217,7 +276,10 @@ class TaskMenuViewWithArrow : ArrowPopup { val dragLayer: InsettableFrameLayout = popupContainer val insets = dragLayer.insets - // Put to the right of the icon if there is space, which means left aligned with the menu + copyIconToDragLayer(insets) + + // Put this menu to the right of the icon if there is space, + // which means the arrow is left aligned with the menu val rightAlignedMenuStartX = mTempRect.left - widthWithArrow val leftAlignedMenuStartX = mTempRect.right + extraHorizontalSpace mIsLeftAligned = if (mIsRtl) { @@ -229,18 +291,17 @@ class TaskMenuViewWithArrow : ArrowPopup { var menuStartX = if (mIsLeftAligned) leftAlignedMenuStartX else rightAlignedMenuStartX - // Offset y so that the arrow and first row are center-aligned with the original icon. + // Offset y so that the arrow and row are center-aligned with the original icon. val iconHeight = mTempRect.height() - val optionHeight = optionMeasuredHeight - val yOffset = (optionHeight - iconHeight) / 2 - var menuStartY = mTempRect.top - yOffset + val yOffset = (optionMeasuredHeight - iconHeight) / 2 + var menuStartY = mTempRect.top - yOffset - extraSpaceForSecondRowAlignment // Insets are added later, so subtract them now. menuStartX -= insets.left menuStartY -= insets.top - setX(menuStartX.toFloat()) - setY(menuStartY.toFloat()) + x = menuStartX.toFloat() + y = menuStartY.toFloat() val lp = layoutParams as FrameLayout.LayoutParams val arrowLp = mArrow.layoutParams as FrameLayout.LayoutParams @@ -251,7 +312,8 @@ class TaskMenuViewWithArrow : ArrowPopup { override fun addArrow() { popupContainer.addView(mArrow) mArrow.x = getArrowX() - mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2) + mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2) + + extraSpaceForSecondRowAlignment updateArrowColor() diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 1ff2a88b31..e8077cf4a5 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -30,6 +30,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 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 java.lang.annotation.RetentionPolicy.SOURCE; @@ -38,6 +39,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.annotation.IdRes; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; @@ -840,7 +842,9 @@ public class TaskView extends FrameLayout implements Reusable { TaskIdAttributeContainer menuContainer = mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1]; if (mActivity.getDeviceProfile().overviewShowAsGrid) { - return TaskMenuViewWithArrow.Companion.showForTask(menuContainer); + boolean alignSecondRow = getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) + && mActivity.getDeviceProfile().isLandscape; + return TaskMenuViewWithArrow.Companion.showForTask(menuContainer, alignSecondRow); } else { return TaskMenuView.showForTask(menuContainer); } @@ -1302,10 +1306,14 @@ public class TaskView extends FrameLayout implements Reusable { getContext().getText(R.string.accessibility_close))); final Context context = getContext(); - // TODO(b/200609838) Determine which task to run A11y action on when in split screen - for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this, - mActivity.getDeviceProfile(), mTaskIdAttributeContainer[0])) { - info.addAction(s.createAccessibilityAction(context)); + for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) { + if (taskContainer == null) { + continue; + } + for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this, + mActivity.getDeviceProfile(), taskContainer)) { + info.addAction(s.createAccessibilityAction(context)); + } } if (mDigitalWellBeingToast.hasLimit()) { @@ -1336,12 +1344,16 @@ public class TaskView extends FrameLayout implements Reusable { return true; } - // TODO(b/200609838) Determine which task to run A11y action on when in split screen - for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this, - mActivity.getDeviceProfile(), mTaskIdAttributeContainer[0])) { - if (s.hasHandlerForAction(action)) { - s.onClick(this); - return true; + for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) { + if (taskContainer == null) { + continue; + } + for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this, + mActivity.getDeviceProfile(), taskContainer)) { + if (s.hasHandlerForAction(action)) { + s.onClick(this); + return true; + } } } @@ -1558,7 +1570,6 @@ public class TaskView extends FrameLayout implements Reusable { mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight); } } - } public class TaskIdAttributeContainer { @@ -1567,6 +1578,8 @@ public class TaskView extends FrameLayout implements Reusable { private final IconView mIconView; /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */ private @SplitConfigurationOptions.StagePosition int mStagePosition; + @IdRes + private final int mA11yNodeId; public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView, IconView iconView, int stagePosition) { @@ -1574,6 +1587,8 @@ public class TaskView extends FrameLayout implements Reusable { this.mThumbnailView = thumbnailView; this.mIconView = iconView; this.mStagePosition = stagePosition; + this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ? + R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo; } public TaskThumbnailView getThumbnailView() { @@ -1603,5 +1618,9 @@ public class TaskView extends FrameLayout implements Reusable { void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) { this.mStagePosition = stagePosition; } + + public int getA11yNodeId() { + return mA11yNodeId; + } } } diff --git a/res/values/id.xml b/res/values/id.xml index ebc4075214..508caffd48 100644 --- a/res/values/id.xml +++ b/res/values/id.xml @@ -21,6 +21,10 @@ + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 5f53d4e24a..868b5f39b8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -40,9 +40,10 @@ Split screen - Split top - Split left - Split right + Split top + Split left + Split right + App info for %1$s diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index 5ebbc67b56..08d3779ab6 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -41,8 +41,8 @@ public abstract class SystemShortcut extend implements View.OnClickListener { private final int mIconResId; - private final int mLabelResId; - private final int mAccessibilityActionId; + protected final int mLabelResId; + protected int mAccessibilityActionId; protected final T mTarget; protected final ItemInfo mItemInfo; @@ -142,11 +142,43 @@ public abstract class SystemShortcut extend public static class AppInfo extends SystemShortcut { + @Nullable + private SplitAccessibilityInfo mSplitA11yInfo; + public AppInfo(T target, ItemInfo itemInfo) { super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target, itemInfo); } + /** + * Constructor used by overview for staged split to provide custom A11y information. + * + * Future improvements considerations: + * Have the logic in {@link #createAccessibilityAction(Context)} be moved to super + * call in {@link SystemShortcut#createAccessibilityAction(Context)} by having + * SystemShortcut be aware of TaskContainers and staged split. + * That way it could directly create the correct node info for any shortcut that supports + * split, but then we'll need custom resIDs for each pair of shortcuts. + */ + public AppInfo(T target, ItemInfo itemInfo, SplitAccessibilityInfo accessibilityInfo) { + this(target, itemInfo); + mSplitA11yInfo = accessibilityInfo; + mAccessibilityActionId = accessibilityInfo.nodeId; + } + + @Override + public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction( + Context context) { + if (mSplitA11yInfo != null && mSplitA11yInfo.containsMultipleTasks) { + String accessibilityLabel = context.getString(R.string.split_app_info_accessibility, + mSplitA11yInfo.taskTitle); + return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId, + accessibilityLabel); + } else { + return super.createAccessibilityAction(context); + } + } + @Override public void onClick(View view) { dismissTaskMenuView(mTarget); @@ -156,6 +188,19 @@ public abstract class SystemShortcut extend mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo) .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP); } + + public static class SplitAccessibilityInfo { + public final boolean containsMultipleTasks; + public final CharSequence taskTitle; + public final int nodeId; + + public SplitAccessibilityInfo(boolean containsMultipleTasks, + CharSequence taskTitle, int nodeId) { + this.containsMultipleTasks = containsMultipleTasks; + this.taskTitle = taskTitle; + this.nodeId = nodeId; + } + } } public static final Factory INSTALL = (activity, itemInfo) -> {