1/* 2 * Copyright (c) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import Input from '@ohos.multimodalInput.inputEventClient'; 17import touchEvent from '@ohos.multimodalInput.touchEvent'; 18import { 19 Log, 20 windowManager, 21 CommonConstants 22} from '@ohos/common'; 23 24const TAG = 'GestureNavigationExecutors'; 25 26export default class GestureNavigationExecutors { 27 private static readonly HOME_DISTANCE_LIMIT_MIN = 0.1; 28 private static readonly RECENT_DISTANCE_LIMIT_MIN = 0.15; 29 private static readonly NS_PER_MS = 1000000; 30 private timeOfFirstLeavingTheBackEventHotArea: number | null = null; 31 private screenWidth = 0; 32 private screenHeight = 0; 33 private curEventType: touchEvent.Action | null = null; 34 private eventName: string | null = null; 35 private startEventPosition: {x: number, y: number} | null = null; 36 private preEventPosition: {x: number, y: number} | null = null; 37 private preEventTime: number | null = null; 38 private preSpeed = 0; 39 private startTime = 0; 40 41 private constructor() { 42 } 43 44 /** 45 * Set screenWidth. 46 */ 47 setScreenWidth(screenWidth: number) { 48 this.screenWidth = screenWidth; 49 } 50 51 /** 52 * Set screenHeight. 53 */ 54 setScreenHeight(screenHeight: number) { 55 this.screenHeight = screenHeight; 56 } 57 58 /** 59 * Get the GestureNavigationExecutors instance. 60 */ 61 static getInstance(): GestureNavigationExecutors { 62 if (globalThis.sGestureNavigationExecutors == null) { 63 globalThis.sGestureNavigationExecutors = new GestureNavigationExecutors(); 64 } 65 return globalThis.sGestureNavigationExecutors; 66 } 67 68 /** 69 * touchEvent Callback. 70 * @return true: Returns true if the gesture is within the specified hot zone. 71 */ 72 touchEventCallback(event: touchEvent.TouchEvent): boolean { 73 Log.showDebug(TAG, `touchEventCallback enter. ${JSON.stringify(event)}`); 74 if (event.touches.length !== 1 || !event.actionTime || !event.action) { 75 return false; 76 } 77 const startXPosition = event.touches[0].screenX; 78 const startYPosition = event.touches[0].screenY; 79 if (event.action === touchEvent.Action.DOWN && this.isSpecifiesRegion(startXPosition, startYPosition)) { 80 this.initializationParameters(); 81 this.startEventPosition = this.preEventPosition = { 82 x: startXPosition, 83 y: startYPosition 84 }; 85 this.startTime = this.preEventTime = event.actionTime; 86 this.curEventType = event.action; 87 if (vp2px(16) >= startXPosition || startXPosition >= (this.screenWidth - vp2px(16))) { 88 this.eventName = 'backEvent'; 89 return true; 90 } 91 } 92 if (this.startEventPosition && this.isSpecifiesRegion(this.startEventPosition.x, this.startEventPosition.y)) { 93 if (event.action === touchEvent.Action.MOVE) { 94 this.curEventType = event.action; 95 const curTime = event.actionTime; 96 const speedX = (startXPosition - this.preEventPosition.x) / ((curTime - this.preEventTime) / 1000); 97 const speedY = (startYPosition - this.preEventPosition.y) / ((curTime - this.preEventTime) / 1000); 98 const sqrt = Math.sqrt(speedX * speedX + speedY * speedY); 99 const curSpeed = startYPosition <= this.preEventPosition.y ? -sqrt : sqrt; 100 const acceleration = (curSpeed - this.preSpeed) / ((curTime - this.preEventTime) / 1000); 101 this.preEventPosition = { 102 x: startXPosition, 103 y: startYPosition 104 }; 105 this.preSpeed = curSpeed; 106 const isDistance = this.isRecentsViewShowOfDistanceLimit(startYPosition); 107 const isSpeed = this.isRecentsViewShowOfSpeedLimit(curTime, acceleration, curSpeed); 108 this.preEventTime = curTime; 109 if (isDistance && isSpeed && !this.eventName && curSpeed) { 110 this.eventName = 'recentEvent'; 111 this.recentEventCall(); 112 return true; 113 } 114 if (this.eventName == 'backEvent' && startXPosition > vp2px(16) && !this.timeOfFirstLeavingTheBackEventHotArea) { 115 this.timeOfFirstLeavingTheBackEventHotArea = (curTime - this.startTime) / 1000; 116 } 117 } 118 if (event.action === touchEvent.Action.UP) { 119 let distance = 0; 120 let slidingSpeed = 0; 121 if (this.curEventType === touchEvent.Action.MOVE) { 122 if (this.eventName == 'backEvent') { 123 distance = Math.abs((startXPosition - this.startEventPosition.x)); 124 if (distance >= vp2px(16) * 1.2 && this.timeOfFirstLeavingTheBackEventHotArea <= 120) { 125 this.backEventCall(); 126 this.initializationParameters(); 127 return true; 128 } 129 } else if (this.eventName == 'recentEvent') { 130 this.initializationParameters(); 131 return true; 132 } else { 133 distance = this.startEventPosition.y - startYPosition; 134 const isDistance = this.isHomeViewShowOfDistanceLimit(startYPosition); 135 Log.showDebug(TAG, `touchEventCallback isDistance: ${isDistance}`); 136 if (isDistance) { 137 slidingSpeed = distance / ((event.actionTime - this.startTime) / GestureNavigationExecutors.NS_PER_MS); 138 Log.showDebug(TAG, `touchEventCallback homeEvent slidingSpeed: ${slidingSpeed}`); 139 if (slidingSpeed >= vp2px(500)) { 140 this.homeEventCall(); 141 } 142 this.initializationParameters(); 143 return true; 144 } 145 } 146 } 147 this.initializationParameters(); 148 } 149 } 150 return false; 151 } 152 153 private initializationParameters() { 154 this.startEventPosition = null; 155 this.eventName = null; 156 this.preEventPosition = null; 157 this.timeOfFirstLeavingTheBackEventHotArea = null; 158 this.startTime = 0; 159 this.preSpeed = 0; 160 } 161 162 private backEventCall() { 163 Log.showInfo(TAG, 'backEventCall backEvent start'); 164 let keyEvent = { 165 isPressed: true, 166 keyCode: 2, 167 keyDownDuration: 1, 168 isIntercepted: false 169 }; 170 171 let res = Input.injectEvent({KeyEvent: keyEvent}); 172 Log.showDebug(TAG, `backEventCall result: ${res}`); 173 keyEvent = { 174 isPressed: false, 175 keyCode: 2, 176 keyDownDuration: 1, 177 isIntercepted: false 178 }; 179 180 setTimeout(() => { 181 res = Input.injectEvent({KeyEvent: keyEvent}); 182 Log.showDebug(TAG, `backEventCall result: ${res}`); 183 }, 20) 184 } 185 186 private homeEventCall() { 187 Log.showInfo(TAG, 'homeEventCall homeEvent start'); 188 globalThis.desktopContext.startAbility({ 189 bundleName: CommonConstants.LAUNCHER_BUNDLE, 190 abilityName: CommonConstants.LAUNCHER_ABILITY 191 }) 192 .then(() => { 193 Log.showDebug(TAG, 'homeEventCall startAbility Promise in service successful.'); 194 }) 195 .catch(() => { 196 Log.showDebug(TAG, 'homeEventCall startAbility Promise in service failed.'); 197 }); 198 } 199 200 private recentEventCall() { 201 Log.showInfo(TAG, 'recentEventCall recentEvent start'); 202 windowManager.minimizeAllApps(); 203 windowManager.createWindowWithName(windowManager.RECENT_WINDOW_NAME, windowManager.RECENT_RANK); 204 } 205 206 private isRecentsViewShowOfDistanceLimit(eventY: number) { 207 return (this.screenHeight - eventY) / this.screenHeight >= GestureNavigationExecutors.RECENT_DISTANCE_LIMIT_MIN; 208 } 209 210 private isHomeViewShowOfDistanceLimit(eventY: number) { 211 return (this.screenHeight - eventY) / this.screenHeight >= GestureNavigationExecutors.HOME_DISTANCE_LIMIT_MIN; 212 } 213 214 private isRecentsViewShowOfSpeedLimit(curTime: number, acceleration: number, curSpeed: number): boolean { 215 const MIN_ACCELERATION = 0.05; 216 const CUR_SPEED_NEGATIVE_FIVE = -5.0; 217 const CUR_SPEED_NEGATIVE_ONE = -1.0; 218 const TIMESTAMP_CONVERTED_TO_MILLISECONDS_CARDINALITY = 1000;// 时间戳转换为毫秒的基数 219 const MIN_TIME_DIFFERENCE = 10.0;// 最小时间差 220 return (acceleration > MIN_ACCELERATION && curSpeed > CUR_SPEED_NEGATIVE_FIVE) || 221 ((curSpeed > CUR_SPEED_NEGATIVE_ONE) && 222 (curTime - this.preEventTime) / TIMESTAMP_CONVERTED_TO_MILLISECONDS_CARDINALITY > MIN_TIME_DIFFERENCE); 223 } 224 225 private isSpecifiesRegion(startXPosition: number, startYPosition: number) { 226 const isStatusBarRegion = startYPosition <= this.screenHeight * 0.07; 227 const isSpecifiesXRegion = startXPosition <= vp2px(16) || startXPosition >= (this.screenWidth - vp2px(16)); 228 const isSpecifiesYRegion = (this.screenHeight - vp2px(22)) <= startYPosition && startYPosition <= this.screenHeight; 229 return (isSpecifiesXRegion && !isStatusBarRegion) || (isSpecifiesYRegion && !isSpecifiesXRegion); 230 } 231}