From e4607587edf141413844d8b7c81fe5cc110e8791 Mon Sep 17 00:00:00 2001 From: Jeff Pierce Date: Thu, 19 Sep 2019 16:17:55 -0700 Subject: [PATCH] Add feature flag + leftward swipe for Compose access. Stole ag/9453040 from jspierce@ and added a feature flag. ag/9453040: Exploratory prototype to test leftward swipe access to Compose across home screen, launcher, and lock screen. Requires Compose APK (installed separately). Change-Id: I15a045976b1eb41392795d3a4f0743f365dec1d2 --- .../quickstep/TouchInteractionService.java | 9 + .../AssistantInputConsumer.java | 2 +- .../inputconsumers/InputConsumer.java | 2 + .../QuickCaptureTouchConsumer.java | 219 ++++++++++++++++++ res/anim/slide_in_right.xml | 9 + .../launcher3/config/FeatureFlags.java | 2 + 6 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureTouchConsumer.java create mode 100644 res/anim/slide_in_right.xml diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 4ca80556d6..85da0aa488 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -53,6 +53,7 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.EventLogArray; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.AppLaunchTracker; @@ -69,6 +70,7 @@ import com.android.quickstep.inputconsumers.InputConsumer; import com.android.quickstep.inputconsumers.OtherActivityInputConsumer; import com.android.quickstep.inputconsumers.OverviewInputConsumer; import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer; +import com.android.quickstep.inputconsumers.QuickCaptureTouchConsumer; import com.android.quickstep.inputconsumers.ResetGestureInputConsumer; import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; import com.android.systemui.shared.recents.IOverviewProxy; @@ -454,6 +456,13 @@ public class TouchInteractionService extends Service implements mInputMonitorCompat); } + if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) { + // Put the Compose gesture as higher priority than the Assistant or base gestures + base = new QuickCaptureTouchConsumer(this, base, + mInputMonitorCompat, mOverviewComponentObserver.getActivityControlHelper()); + } + + if (mDeviceState.isScreenPinningActive()) { // Note: we only allow accessibility to wrap this, and it replaces the previous // base input consumer (which should be NO_OP anyway since topTaskLocked == true). diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java index a2a1c43023..7cec924d52 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java @@ -131,8 +131,8 @@ public class AssistantInputConsumer extends DelegateInputConsumer { case ACTION_POINTER_DOWN: { if (mState != STATE_ACTIVE) { mState = STATE_DELEGATE_ACTIVE; - break; } + break; } case ACTION_POINTER_UP: { int ptrIdx = ev.getActionIndex(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java index a1e5d47a53..045bafec03 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java @@ -33,6 +33,7 @@ public interface InputConsumer { int TYPE_SCREEN_PINNED = 1 << 6; int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7; int TYPE_RESET_GESTURE = 1 << 8; + int TYPE_QUICK_CAPTURE = 1 << 9; String[] NAMES = new String[] { "TYPE_NO_OP", // 0 @@ -44,6 +45,7 @@ public interface InputConsumer { "TYPE_SCREEN_PINNED", // 6 "TYPE_OVERVIEW_WITHOUT_FOCUS", // 7 "TYPE_RESET_GESTURE", // 8 + "TYPE_QUICK_CAPTURE", // 9 }; InputConsumer NO_OP = () -> TYPE_NO_OP; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureTouchConsumer.java new file mode 100644 index 0000000000..3101bb8263 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureTouchConsumer.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2019 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.inputconsumers; + +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_MOVE; +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.launcher3.Utilities.squaredHypot; + +import android.app.ActivityOptions; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.PointF; +import android.os.Bundle; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.R; +import com.android.quickstep.ActivityControlHelper; +import com.android.quickstep.views.RecentsView; +import com.android.systemui.shared.system.InputMonitorCompat; + +/** + * Touch consumer for handling events to launch quick capture from launcher + * @param Draggable activity subclass used by RecentsView + */ +public class QuickCaptureTouchConsumer + extends DelegateInputConsumer { + + private static final String TAG = "QuickCaptureTouchConsumer"; + + private static final String QUICK_CAPTURE_PACKAGE = "com.google.auxe.compose"; + private static final String QUICK_CAPTURE_PACKAGE_DEV = "com.google.auxe.compose.debug"; + + private static final String EXTRA_DEVICE_STATE = "deviceState"; + private static final String DEVICE_STATE_LOCKED = "Locked"; + private static final String DEVICE_STATE_LAUNCHER = "Launcher"; + private static final String DEVICE_STATE_APP = "App"; + private static final String DEVICE_STATE_UNKNOWN = "Unknown"; + + private static final int ANGLE_THRESHOLD = 35; // Degrees + + private final PointF mDownPos = new PointF(); + private final PointF mLastPos = new PointF(); + private final PointF mStartDragPos = new PointF(); + + private int mActivePointerId = -1; + private boolean mPassedSlop = false; + + private final float mSquaredSlop; + + private Context mContext; + + private RecentsView mRecentsView; + + public QuickCaptureTouchConsumer(Context context, InputConsumer delegate, + InputMonitorCompat inputMonitor, ActivityControlHelper activityControlHelper) { + super(delegate, inputMonitor); + mContext = context; + + float slop = ViewConfiguration.get(context).getScaledTouchSlop(); + mSquaredSlop = slop * slop; + + activityControlHelper.createActivityInitListener(this::onActivityInit).register(); + } + + @Override + public int getType() { + return TYPE_QUICK_CAPTURE | mDelegate.getType(); + } + + private boolean onActivityInit(final T activity, Boolean alreadyOnHome) { + mRecentsView = activity.getOverviewPanel(); + + return true; + } + + @Override + public void onMotionEvent(MotionEvent ev) { + switch (ev.getActionMasked()) { + case ACTION_DOWN: { + mActivePointerId = ev.getPointerId(0); + mDownPos.set(ev.getX(), ev.getY()); + mLastPos.set(mDownPos); + + break; + } + case ACTION_POINTER_DOWN: { + if (mState != STATE_ACTIVE) { + mState = STATE_DELEGATE_ACTIVE; + } + break; + } + case ACTION_POINTER_UP: { + int ptrIdx = ev.getActionIndex(); + int ptrId = ev.getPointerId(ptrIdx); + if (ptrId == mActivePointerId) { + final int newPointerIdx = ptrIdx == 0 ? 1 : 0; + mDownPos.set( + ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x), + ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y)); + mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx)); + mActivePointerId = ev.getPointerId(newPointerIdx); + } + break; + } + case ACTION_MOVE: { + if (mState == STATE_DELEGATE_ACTIVE) { + break; + } + if (!mDelegate.allowInterceptByParent()) { + mState = STATE_DELEGATE_ACTIVE; + break; + } + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == -1) { + break; + } + mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); + + if (!mPassedSlop) { + // Normal gesture, ensure we pass the slop before we start tracking the gesture + if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) + > mSquaredSlop) { + + mPassedSlop = true; + mStartDragPos.set(mLastPos.x, mLastPos.y); + + if (isValidQuickCaptureGesture()) { + setActive(ev); + } else { + mState = STATE_DELEGATE_ACTIVE; + } + } + } + + break; + } + case ACTION_CANCEL: + case ACTION_UP: + if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop) { + startQuickCapture(); + } + + mPassedSlop = false; + mState = STATE_INACTIVE; + break; + } + + if (mState != STATE_ACTIVE) { + mDelegate.onMotionEvent(ev); + } + } + + private boolean isValidQuickCaptureGesture() { + // Make sure there isn't an app to quick switch to on our right + boolean atRightMostApp = (mRecentsView == null || mRecentsView.getRunningTaskIndex() <= 0); + + // Check if the gesture is within our angle threshold of horizontal + float deltaY = Math.abs(mLastPos.y - mDownPos.y); + float deltaX = mDownPos.x - mLastPos.x; // Positive if this is a gesture to the left + boolean angleInBounds = Math.toDegrees(Math.atan2(deltaY, deltaX)) < ANGLE_THRESHOLD; + + return atRightMostApp && angleInBounds; + } + + private void startQuickCapture() { + // Inspect our delegate's type to figure out where the user invoked Compose + String deviceState = DEVICE_STATE_UNKNOWN; + int consumerType = mDelegate.getType(); + if (((consumerType & InputConsumer.TYPE_OVERVIEW) > 0) + || ((consumerType & InputConsumer.TYPE_OVERVIEW_WITHOUT_FOCUS)) > 0) { + deviceState = DEVICE_STATE_LAUNCHER; + } else if ((consumerType & InputConsumer.TYPE_OTHER_ACTIVITY) > 0) { + deviceState = DEVICE_STATE_APP; + } else if (((consumerType & InputConsumer.TYPE_RESET_GESTURE) > 0) + || ((consumerType & InputConsumer.TYPE_DEVICE_LOCKED) > 0)) { + deviceState = DEVICE_STATE_LOCKED; + } + + // Then launch the app + PackageManager pm = mContext.getPackageManager(); + + Intent qcIntent = pm.getLaunchIntentForPackage(QUICK_CAPTURE_PACKAGE); + + if (qcIntent == null) { + // If we couldn't find the regular app, try the dev version + qcIntent = pm.getLaunchIntentForPackage(QUICK_CAPTURE_PACKAGE_DEV); + } + + if (qcIntent != null) { + qcIntent.putExtra(EXTRA_DEVICE_STATE, deviceState); + + Bundle options = ActivityOptions.makeCustomAnimation(mContext, R.anim.slide_in_right, + 0).toBundle(); + + mContext.startActivity(qcIntent, options); + } + } +} diff --git a/res/anim/slide_in_right.xml b/res/anim/slide_in_right.xml new file mode 100644 index 0000000000..55d3e54025 --- /dev/null +++ b/res/anim/slide_in_right.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index c502dd7af1..4abdbefc1b 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -117,6 +117,8 @@ public final class FeatureFlags { public static final TogglableFlag ENABLE_PREDICTION_DISMISS = new TogglableFlag( "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list"); + public static final TogglableFlag ENABLE_QUICK_CAPTURE_GESTURE = new TogglableFlag( + "ENABLE_QUICK_CAPTURE_GESTURE", false, "Swipe from right to left to quick capture"); public static void initialize(Context context) { // Avoid the disk read for user builds