1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.carlauncher.displayarea; 18 19 import static android.view.Display.DEFAULT_DISPLAY; 20 21 import android.graphics.Rect; 22 import android.hardware.input.InputManager; 23 import android.os.Looper; 24 import android.view.InputChannel; 25 import android.view.InputEvent; 26 import android.view.InputEventReceiver; 27 import android.view.InputMonitor; 28 import android.view.MotionEvent; 29 30 import com.android.wm.shell.common.ShellExecutor; 31 32 /** 33 * Manages all the touch handling for display area, including user tap outside region 34 * to exit. 35 */ 36 public class CarDisplayAreaTouchHandler { 37 private static final int CLICK_ACTION_THRESHOLD = 10; 38 39 private final ShellExecutor mMainExecutor; 40 41 private InputMonitor mInputMonitor; 42 private InputEventReceiver mInputEventReceiver; 43 private OnClickDisplayAreaListener mOnClickDisplayAreaListener; 44 private OnDragDisplayAreaListener mOnTouchTitleBarListener; 45 private boolean mIsEnabled; 46 private float mStartX; 47 private float mStartY; 48 private Rect mTitleBarBounds; 49 private boolean mIsTitleBarVisible; 50 private boolean mIsTitleBarDragged; 51 CarDisplayAreaTouchHandler(ShellExecutor mainExecutor)52 public CarDisplayAreaTouchHandler(ShellExecutor mainExecutor) { 53 mMainExecutor = mainExecutor; 54 } 55 56 /** 57 * Notified by {@link CarDisplayAreaController}, to update settings of Enabled or Disabled. 58 */ enable(boolean isEnabled)59 public void enable(boolean isEnabled) { 60 mIsEnabled = isEnabled; 61 updateIsEnabled(); 62 } 63 64 /** 65 * Register {@link OnClickDisplayAreaListener} to receive onClick() callback 66 */ registerOnClickListener(OnClickDisplayAreaListener listener)67 public void registerOnClickListener(OnClickDisplayAreaListener listener) { 68 mOnClickDisplayAreaListener = listener; 69 } 70 71 /** 72 * Register {@link OnDragDisplayAreaListener} to receive onTouch() callbacks 73 */ registerTouchEventListener(OnDragDisplayAreaListener listener)74 public void registerTouchEventListener(OnDragDisplayAreaListener listener) { 75 mOnTouchTitleBarListener = listener; 76 } 77 78 /** 79 * Updated whether the titleBar is visible or not. 80 */ updateTitleBarVisibility(boolean isTitleBarVisible)81 public void updateTitleBarVisibility(boolean isTitleBarVisible) { 82 mIsTitleBarVisible = isTitleBarVisible; 83 } 84 setTitleBarBounds(Rect titleBarBounds)85 public void setTitleBarBounds(Rect titleBarBounds) { 86 mTitleBarBounds = titleBarBounds; 87 } 88 onMotionEvent(MotionEvent event)89 private void onMotionEvent(MotionEvent event) { 90 switch (event.getAction()) { 91 case MotionEvent.ACTION_DOWN: 92 mStartX = event.getX(); 93 mStartY = event.getY(); 94 if (mOnTouchTitleBarListener != null && mIsTitleBarVisible) { 95 mOnTouchTitleBarListener.onStart(mStartX, mStartY); 96 } 97 mIsTitleBarDragged = false; 98 break; 99 case MotionEvent.ACTION_MOVE: 100 if (mIsTitleBarVisible && isTitleBarGrabbed() && mOnTouchTitleBarListener != null) { 101 mOnTouchTitleBarListener.onMove(event.getX(), event.getY()); 102 mIsTitleBarDragged = true; 103 } 104 105 break; 106 case MotionEvent.ACTION_UP: 107 float endX = event.getX(); 108 float endY = event.getY(); 109 // TODO: use utility functions from gesture class. 110 if (isAClick(mStartX, endX, mStartY, endY)) { 111 mOnClickDisplayAreaListener.onClick(endX, endY); 112 } 113 if (mIsTitleBarDragged && mOnTouchTitleBarListener != null) { 114 mOnTouchTitleBarListener.onFinish(event.getX(), event.getY()); 115 } 116 mIsTitleBarDragged = false; 117 break; 118 default: 119 mIsTitleBarDragged = false; 120 } 121 } 122 isAClick(float startX, float endX, float startY, float endY)123 private static boolean isAClick(float startX, float endX, float startY, float endY) { 124 float differenceX = Math.abs(startX - endX); 125 float differenceY = Math.abs(startY - endY); 126 return !(differenceX > CLICK_ACTION_THRESHOLD || differenceY > CLICK_ACTION_THRESHOLD); 127 } 128 isTitleBarGrabbed()129 private boolean isTitleBarGrabbed() { 130 return mStartX >= mTitleBarBounds.left && mStartX <= mTitleBarBounds.right 131 && mStartY >= mTitleBarBounds.top && mStartY <= mTitleBarBounds.bottom; 132 } 133 disposeInputChannel()134 private void disposeInputChannel() { 135 if (mInputEventReceiver != null) { 136 mInputEventReceiver.dispose(); 137 mInputEventReceiver = null; 138 } 139 if (mInputMonitor != null) { 140 mInputMonitor.dispose(); 141 mInputMonitor = null; 142 } 143 } 144 onInputEvent(InputEvent ev)145 private void onInputEvent(InputEvent ev) { 146 if (ev instanceof MotionEvent) { 147 onMotionEvent((MotionEvent) ev); 148 } 149 } 150 updateIsEnabled()151 private void updateIsEnabled() { 152 disposeInputChannel(); 153 if (mIsEnabled) { 154 mInputMonitor = InputManager.getInstance().monitorGestureInput( 155 "car-display-area-touch", DEFAULT_DISPLAY); 156 try { 157 mMainExecutor.executeBlocking(() -> { 158 mInputEventReceiver = new EventReceiver( 159 mInputMonitor.getInputChannel(), Looper.myLooper()); 160 }); 161 } catch (InterruptedException e) { 162 throw new RuntimeException("Failed to create input event receiver", e); 163 } 164 } 165 } 166 167 private class EventReceiver extends InputEventReceiver { EventReceiver(InputChannel channel, Looper looper)168 EventReceiver(InputChannel channel, Looper looper) { 169 super(channel, looper); 170 } 171 onInputEvent(InputEvent event)172 public void onInputEvent(InputEvent event) { 173 CarDisplayAreaTouchHandler.this.onInputEvent(event); 174 finishInputEvent(event, true); 175 } 176 } 177 178 /** 179 * Callback invoked when a user clicks anywhere on the display area. 180 */ 181 interface OnClickDisplayAreaListener { 182 183 /** 184 * Called when a user clicks on the display area. Returns the co-ordinate of the click. 185 */ onClick(float x, float y)186 void onClick(float x, float y); 187 } 188 189 /** 190 * Callback invoked when a user touches anywhere on the display area. 191 */ 192 interface OnDragDisplayAreaListener { 193 194 /** 195 * Called for ACTION_MOVE touch events on the title bar. Returns the co-ordinate of the 196 * touch. This is only called when the title bar is visible. 197 */ onMove(float x, float y)198 void onMove(float x, float y); 199 200 /** 201 * Called for ACTION_UP touch events on the title bar. Returns the co-ordinate of the 202 * touch. This is only called when the title bar is visible. 203 */ onFinish(float x, float y)204 void onFinish(float x, float y); 205 206 /** 207 * Called for ACTION_DOWN touch events on the title bar. Returns the co-ordinate of the 208 * touch. This is only called when the title bar is visible. 209 */ onStart(float x, float y)210 void onStart(float x, float y); 211 } 212 } 213