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 { 18 Log, 19 windowManager, 20 CommonConstants 21} from '@ohos/common'; 22 23const TAG = 'GestureNavigationExecutors'; 24 25export default class GestureNavigationExecutors { 26 private static readonly HOME_DISTANCE_LIMIT_MIN = 0.1; 27 private static readonly RECENT_DISTANCE_LIMIT_MIN = 0.15; 28 private static readonly NS_PER_MS = 1000000; 29 private timeOfFirstLeavingTheBackEventHotArea: number | null = null; 30 private screenWidth = 0; 31 private screenHeight = 0; 32 private curEventType: string | null = null; 33 private eventName: string | null = null; 34 private startEventPosition: {x: number, y: number} | null = null; 35 private preEventPosition: {x: number, y: number} | null = null; 36 private preEventTime: number | null = null; 37 private preSpeed = 0; 38 private startTime = 0; 39 40 private constructor() { 41 } 42 43 /** 44 * Set screenWidth. 45 */ 46 setScreenWidth(screenWidth: number) { 47 this.screenWidth = screenWidth; 48 } 49 50 /** 51 * Set screenHeight. 52 */ 53 setScreenHeight(screenHeight: number) { 54 this.screenHeight = screenHeight; 55 } 56 57 /** 58 * Get the GestureNavigationExecutors instance. 59 */ 60 static getInstance(): GestureNavigationExecutors { 61 if (globalThis.sGestureNavigationExecutors == null) { 62 globalThis.sGestureNavigationExecutors = new GestureNavigationExecutors(); 63 } 64 return globalThis.sGestureNavigationExecutors; 65 } 66 67 /** 68 * touchEvent Callback. 69 * @return true: Returns true if the gesture is within the specified hot zone. 70 */ 71 touchEventCallback(event: any): boolean { 72 Log.showDebug(TAG, `touchEventCallback enter. ${JSON.stringify(event)}`); 73 if (event.touches.length != 1) { 74 return false; 75 } 76 const startXPosition = event.touches[0].globalX; 77 const startYPosition = event.touches[0].globalY; 78 if (event.type == 'down' && this.isSpecifiesRegion(startXPosition, startYPosition)) { 79 this.initializationParameters(); 80 this.startEventPosition = this.preEventPosition = { 81 x: startXPosition, 82 y: startYPosition 83 }; 84 this.startTime = this.preEventTime = event.timestamp; 85 this.curEventType = event.type; 86 if (vp2px(16) >= startXPosition || startXPosition >= (this.screenWidth - vp2px(16))) { 87 this.eventName = 'backEvent'; 88 return true; 89 } 90 } 91 if (this.startEventPosition && this.isSpecifiesRegion(this.startEventPosition.x, this.startEventPosition.y)) { 92 if (event.type == 'move') { 93 this.curEventType = event.type; 94 const curTime = event.timestamp; 95 const speedX = (startXPosition - this.preEventPosition.x) / ((curTime - this.preEventTime) / 1000); 96 const speedY = (startYPosition - this.preEventPosition.y) / ((curTime - this.preEventTime) / 1000); 97 const sqrt = Math.sqrt(speedX * speedX + speedY * speedY); 98 const curSpeed = startYPosition <= this.preEventPosition.y ? -sqrt : sqrt; 99 const acceleration = (curSpeed - this.preSpeed) / ((curTime - this.preEventTime) / 1000); 100 this.preEventPosition = { 101 x: startXPosition, 102 y: startYPosition 103 }; 104 this.preSpeed = curSpeed; 105 const isDistance = this.isRecentsViewShowOfDistanceLimit(startYPosition); 106 const isSpeed = this.isRecentsViewShowOfSpeedLimit(curTime, acceleration, curSpeed); 107 this.preEventTime = curTime; 108 if (isDistance && isSpeed && !this.eventName && curSpeed) { 109 this.eventName = 'recentEvent'; 110 this.recentEventCall(); 111 return true; 112 } 113 if (this.eventName == 'backEvent' && startXPosition > vp2px(16) && !this.timeOfFirstLeavingTheBackEventHotArea) { 114 this.timeOfFirstLeavingTheBackEventHotArea = (curTime - this.startTime) / 1000; 115 } 116 } 117 if (event.type == 'up') { 118 let distance = 0; 119 let slidingSpeed = 0; 120 if (this.curEventType == 'move') { 121 if (this.eventName == 'backEvent') { 122 distance = Math.abs((startXPosition - this.startEventPosition.x)); 123 if (distance >= vp2px(16) * 1.2 && this.timeOfFirstLeavingTheBackEventHotArea <= 120) { 124 this.backEventCall(); 125 this.initializationParameters(); 126 return true; 127 } 128 } else if (this.eventName == 'recentEvent') { 129 this.initializationParameters(); 130 return true; 131 } else { 132 distance = this.startEventPosition.y - startYPosition; 133 const isDistance = this.isHomeViewShowOfDistanceLimit(startYPosition); 134 Log.showDebug(TAG, `touchEventCallback isDistance: ${isDistance}`); 135 if (isDistance) { 136 slidingSpeed = distance / ((event.timestamp - this.startTime) / GestureNavigationExecutors.NS_PER_MS); 137 Log.showDebug(TAG, `touchEventCallback homeEvent slidingSpeed: ${slidingSpeed}`); 138 if (slidingSpeed >= vp2px(500)) { 139 this.homeEventCall(); 140 } 141 this.initializationParameters(); 142 return true; 143 } 144 } 145 } 146 this.initializationParameters(); 147 } 148 } 149 return false; 150 } 151 152 private initializationParameters() { 153 this.startEventPosition = null; 154 this.eventName = null; 155 this.preEventPosition = null; 156 this.timeOfFirstLeavingTheBackEventHotArea = null; 157 this.startTime = 0; 158 this.preSpeed = 0; 159 } 160 161 private backEventCall() { 162 Log.showInfo(TAG, 'backEventCall backEvent start'); 163 let keyEvent = { 164 isPressed: true, 165 keyCode: 2, 166 keyDownDuration: 1, 167 isIntercepted: false 168 }; 169 170 let res = Input.injectEvent({KeyEvent: keyEvent}); 171 Log.showDebug(TAG, `backEventCall result: ${res}`); 172 keyEvent = { 173 isPressed: false, 174 keyCode: 2, 175 keyDownDuration: 1, 176 isIntercepted: false 177 }; 178 179 setTimeout(() => { 180 res = Input.injectEvent({KeyEvent: keyEvent}); 181 Log.showDebug(TAG, `backEventCall result: ${res}`); 182 }, 20) 183 } 184 185 private homeEventCall() { 186 Log.showInfo(TAG, 'homeEventCall homeEvent start'); 187 globalThis.desktopContext.startAbility({ 188 bundleName: CommonConstants.LAUNCHER_BUNDLE, 189 abilityName: CommonConstants.LAUNCHER_ABILITY 190 }) 191 .then(() => { 192 Log.showDebug(TAG, 'homeEventCall startAbility Promise in service successful.'); 193 }) 194 .catch(() => { 195 Log.showDebug(TAG, 'homeEventCall startAbility Promise in service failed.'); 196 }); 197 } 198 199 private recentEventCall() { 200 Log.showInfo(TAG, 'recentEventCall recentEvent start'); 201 globalThis.createWindowWithName(windowManager.RECENT_WINDOW_NAME, windowManager.RECENT_RANK); 202 } 203 204 private isRecentsViewShowOfDistanceLimit(eventY: number) { 205 return (this.screenHeight - eventY) / this.screenHeight >= GestureNavigationExecutors.RECENT_DISTANCE_LIMIT_MIN; 206 } 207 208 private isHomeViewShowOfDistanceLimit(eventY: number) { 209 return (this.screenHeight - eventY) / this.screenHeight >= GestureNavigationExecutors.HOME_DISTANCE_LIMIT_MIN; 210 } 211 212 private isRecentsViewShowOfSpeedLimit(curTime: number, acceleration: number, curSpeed: number): boolean { 213 const MIN_ACCELERATION = 0.05; 214 const CUR_SPEED_NEGATIVE_FIVE = -5.0; 215 const CUR_SPEED_NEGATIVE_ONE = -1.0; 216 const TIMESTAMP_CONVERTED_TO_MILLISECONDS_CARDINALITY = 1000;// 时间戳转换为毫秒的基数 217 const MIN_TIME_DIFFERENCE = 10.0;// 最小时间差 218 return (acceleration > MIN_ACCELERATION && curSpeed > CUR_SPEED_NEGATIVE_FIVE) || 219 ((curSpeed > CUR_SPEED_NEGATIVE_ONE) && 220 (curTime - this.preEventTime) / TIMESTAMP_CONVERTED_TO_MILLISECONDS_CARDINALITY > MIN_TIME_DIFFERENCE); 221 } 222 223 private isSpecifiesRegion(startXPosition: number, startYPosition: number) { 224 const isStatusBarRegion = startYPosition <= this.screenHeight * 0.07; 225 const isSpecifiesXRegion = startXPosition <= vp2px(16) || startXPosition >= (this.screenWidth - vp2px(16)); 226 const isSpecifiesYRegion = (this.screenHeight - vp2px(22)) <= startYPosition && startYPosition <= this.screenHeight; 227 return (isSpecifiesXRegion && !isStatusBarRegion) || (isSpecifiesYRegion && !isSpecifiesXRegion); 228 } 229}