blob: 5b9f0c41e31ee781be8bcaa1b7ccc7ca995531b2 [file] [log] [blame]
/*
* 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();
}
}