| /* |
| * Copyright (C) 2020 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.wm.shell.onehanded; |
| |
| import static android.view.Display.DEFAULT_DISPLAY; |
| |
| import android.graphics.Rect; |
| import android.hardware.input.InputManager; |
| import android.os.Looper; |
| import android.view.InputChannel; |
| import android.view.InputEvent; |
| import android.view.InputEventReceiver; |
| import android.view.InputMonitor; |
| import android.view.MotionEvent; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.VisibleForTesting; |
| |
| import com.android.wm.shell.common.ShellExecutor; |
| |
| import java.io.PrintWriter; |
| |
| /** |
| * Manages all the touch handling for One Handed on the Phone, including user tap outside region |
| * to exit, reset timer when user is in one-handed mode. |
| */ |
| public class OneHandedTouchHandler implements OneHandedTransitionCallback { |
| private static final String TAG = "OneHandedTouchHandler"; |
| private final Rect mLastUpdatedBounds = new Rect(); |
| |
| private final OneHandedTimeoutHandler mTimeoutHandler; |
| private final ShellExecutor mMainExecutor; |
| |
| @VisibleForTesting |
| InputMonitor mInputMonitor; |
| @VisibleForTesting |
| InputEventReceiver mInputEventReceiver; |
| @VisibleForTesting |
| OneHandedTouchEventCallback mTouchEventCallback; |
| |
| private boolean mIsEnabled; |
| private boolean mIsOnStopTransitioning; |
| private boolean mIsInOutsideRegion; |
| |
| public OneHandedTouchHandler(OneHandedTimeoutHandler timeoutHandler, |
| ShellExecutor mainExecutor) { |
| mTimeoutHandler = timeoutHandler; |
| mMainExecutor = mainExecutor; |
| updateIsEnabled(); |
| } |
| |
| /** |
| * Notified by {@link OneHandedController}, when user update settings of Enabled or Disabled |
| * |
| * @param isEnabled is one handed settings enabled or not |
| */ |
| public void onOneHandedEnabled(boolean isEnabled) { |
| mIsEnabled = isEnabled; |
| updateIsEnabled(); |
| } |
| |
| /** |
| * Register {@link OneHandedTouchEventCallback} to receive onEnter(), onExit() callback |
| */ |
| public void registerTouchEventListener(OneHandedTouchEventCallback callback) { |
| mTouchEventCallback = callback; |
| } |
| |
| private boolean onMotionEvent(MotionEvent ev) { |
| mIsInOutsideRegion = isWithinTouchOutsideRegion(ev.getX(), ev.getY()); |
| switch (ev.getAction()) { |
| case MotionEvent.ACTION_DOWN: |
| case MotionEvent.ACTION_MOVE: { |
| if (!mIsInOutsideRegion) { |
| mTimeoutHandler.resetTimer(); |
| } |
| break; |
| } |
| case MotionEvent.ACTION_UP: |
| case MotionEvent.ACTION_CANCEL: { |
| mTimeoutHandler.resetTimer(); |
| if (mIsInOutsideRegion && !mIsOnStopTransitioning) { |
| mTouchEventCallback.onStop(); |
| mIsOnStopTransitioning = true; |
| } |
| // Reset flag for next operation |
| mIsInOutsideRegion = false; |
| break; |
| } |
| } |
| return true; |
| } |
| |
| private void disposeInputChannel() { |
| if (mInputEventReceiver != null) { |
| mInputEventReceiver.dispose(); |
| mInputEventReceiver = null; |
| } |
| if (mInputMonitor != null) { |
| mInputMonitor.dispose(); |
| mInputMonitor = null; |
| } |
| } |
| |
| private boolean isWithinTouchOutsideRegion(float x, float y) { |
| return Math.round(y) < mLastUpdatedBounds.top; |
| } |
| |
| private void onInputEvent(InputEvent ev) { |
| if (ev instanceof MotionEvent) { |
| onMotionEvent((MotionEvent) ev); |
| } |
| } |
| |
| private void updateIsEnabled() { |
| disposeInputChannel(); |
| if (mIsEnabled) { |
| mInputMonitor = InputManager.getInstance().monitorGestureInput( |
| "onehanded-touch", DEFAULT_DISPLAY); |
| try { |
| mMainExecutor.executeBlocking(() -> { |
| mInputEventReceiver = new EventReceiver( |
| mInputMonitor.getInputChannel(), Looper.myLooper()); |
| }); |
| } catch (InterruptedException e) { |
| throw new RuntimeException("Failed to create input event receiver", e); |
| } |
| } |
| } |
| |
| @Override |
| public void onStartFinished(Rect bounds) { |
| mLastUpdatedBounds.set(bounds); |
| } |
| |
| @Override |
| public void onStopFinished(Rect bounds) { |
| mLastUpdatedBounds.set(bounds); |
| mIsOnStopTransitioning = false; |
| } |
| |
| void dump(@NonNull PrintWriter pw) { |
| final String innerPrefix = " "; |
| pw.println(TAG); |
| pw.print(innerPrefix + "mLastUpdatedBounds="); |
| pw.println(mLastUpdatedBounds); |
| } |
| |
| // TODO: Use BatchedInputEventReceiver |
| private class EventReceiver extends InputEventReceiver { |
| EventReceiver(InputChannel channel, Looper looper) { |
| super(channel, looper); |
| } |
| |
| public void onInputEvent(InputEvent event) { |
| OneHandedTouchHandler.this.onInputEvent(event); |
| finishInputEvent(event, true); |
| } |
| } |
| |
| /** |
| * The touch(gesture) events to notify {@link OneHandedController} start or stop one handed |
| */ |
| public interface OneHandedTouchEventCallback { |
| /** |
| * Handle the exit event. |
| */ |
| void onStop(); |
| } |
| } |