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