• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}