1/*
2 * Copyright (C) 2013 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
17package com.android.server.wm;
18
19import android.graphics.Rect;
20import android.graphics.Region;
21import android.view.DisplayInfo;
22import android.view.GestureDetector;
23import android.view.InputDevice;
24import android.view.MotionEvent;
25import android.view.WindowManagerPolicy.PointerEventListener;
26
27import com.android.server.wm.WindowManagerService.H;
28
29import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
30import static android.view.PointerIcon.TYPE_DEFAULT;
31import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
32import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
33import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
34import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
35
36public class TaskTapPointerEventListener implements PointerEventListener {
37
38    final private Region mTouchExcludeRegion = new Region();
39    private final WindowManagerService mService;
40    private final DisplayContent mDisplayContent;
41    private final Rect mTmpRect = new Rect();
42    private final Region mNonResizeableRegion = new Region();
43    private boolean mTwoFingerScrolling;
44    private boolean mInGestureDetection;
45    private GestureDetector mGestureDetector;
46    private int mPointerIconType = TYPE_NOT_SPECIFIED;
47
48    public TaskTapPointerEventListener(WindowManagerService service,
49            DisplayContent displayContent) {
50        mService = service;
51        mDisplayContent = displayContent;
52    }
53
54    // initialize the object, note this must be done outside WindowManagerService
55    // ctor, otherwise it may cause recursion as some code in GestureDetector ctor
56    // depends on WMS being already created.
57    void init() {
58        mGestureDetector = new GestureDetector(
59                mService.mContext, new TwoFingerScrollListener(), mService.mH);
60    }
61
62    @Override
63    public void onPointerEvent(MotionEvent motionEvent) {
64        doGestureDetection(motionEvent);
65
66        final int action = motionEvent.getAction();
67        switch (action & MotionEvent.ACTION_MASK) {
68            case MotionEvent.ACTION_DOWN: {
69                final int x = (int) motionEvent.getX();
70                final int y = (int) motionEvent.getY();
71
72                synchronized (this) {
73                    if (!mTouchExcludeRegion.contains(x, y)) {
74                        mService.mH.obtainMessage(H.TAP_OUTSIDE_TASK,
75                                x, y, mDisplayContent).sendToTarget();
76                    }
77                }
78                break;
79            }
80
81            case MotionEvent.ACTION_MOVE: {
82                if (motionEvent.getPointerCount() != 2) {
83                    stopTwoFingerScroll();
84                }
85                break;
86            }
87
88            case MotionEvent.ACTION_HOVER_MOVE: {
89                final int x = (int) motionEvent.getX();
90                final int y = (int) motionEvent.getY();
91                final Task task = mDisplayContent.findTaskForControlPoint(x, y);
92                InputDevice inputDevice = motionEvent.getDevice();
93                if (task == null || inputDevice == null) {
94                    mPointerIconType = TYPE_NOT_SPECIFIED;
95                    break;
96                }
97                task.getDimBounds(mTmpRect);
98                if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) {
99                    int iconType = TYPE_DEFAULT;
100                    if (x < mTmpRect.left) {
101                        iconType =
102                            (y < mTmpRect.top) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
103                            (y > mTmpRect.bottom) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
104                            TYPE_HORIZONTAL_DOUBLE_ARROW;
105                    } else if (x > mTmpRect.right) {
106                        iconType =
107                            (y < mTmpRect.top) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
108                            (y > mTmpRect.bottom) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
109                            TYPE_HORIZONTAL_DOUBLE_ARROW;
110                    } else if (y < mTmpRect.top || y > mTmpRect.bottom) {
111                        iconType = TYPE_VERTICAL_DOUBLE_ARROW;
112                    }
113                    if (mPointerIconType != iconType) {
114                        mPointerIconType = iconType;
115                        inputDevice.setPointerType(iconType);
116                    }
117                } else {
118                    mPointerIconType = TYPE_NOT_SPECIFIED;
119                }
120            } break;
121
122            case MotionEvent.ACTION_HOVER_EXIT:
123                mPointerIconType = TYPE_NOT_SPECIFIED;
124                InputDevice inputDevice = motionEvent.getDevice();
125                if (inputDevice != null) {
126                    inputDevice.setPointerType(TYPE_DEFAULT);
127                }
128                break;
129
130            case MotionEvent.ACTION_UP:
131            case MotionEvent.ACTION_POINTER_UP: {
132                stopTwoFingerScroll();
133                break;
134            }
135        }
136    }
137
138    private void doGestureDetection(MotionEvent motionEvent) {
139        if (mGestureDetector == null || mNonResizeableRegion.isEmpty()) {
140            return;
141        }
142        final int action = motionEvent.getAction() & MotionEvent.ACTION_MASK;
143        final int x = (int) motionEvent.getX();
144        final int y = (int) motionEvent.getY();
145        final boolean isTouchInside = mNonResizeableRegion.contains(x, y);
146        if (mInGestureDetection || action == MotionEvent.ACTION_DOWN && isTouchInside) {
147            // If we receive the following actions, or the pointer goes out of the area
148            // we're interested in, stop detecting and cancel the current detection.
149            mInGestureDetection = isTouchInside
150                    && action != MotionEvent.ACTION_UP
151                    && action != MotionEvent.ACTION_POINTER_UP
152                    && action != MotionEvent.ACTION_CANCEL;
153            if (mInGestureDetection) {
154                mGestureDetector.onTouchEvent(motionEvent);
155            } else {
156                MotionEvent cancelEvent = motionEvent.copy();
157                cancelEvent.cancel();
158                mGestureDetector.onTouchEvent(cancelEvent);
159                stopTwoFingerScroll();
160            }
161        }
162    }
163
164    private void onTwoFingerScroll(MotionEvent e) {
165        final int x = (int)e.getX(0);
166        final int y = (int)e.getY(0);
167        if (!mTwoFingerScrolling) {
168            mTwoFingerScrolling = true;
169            mService.mH.obtainMessage(
170                    H.TWO_FINGER_SCROLL_START, x, y, mDisplayContent).sendToTarget();
171        }
172    }
173
174    private void stopTwoFingerScroll() {
175        if (mTwoFingerScrolling) {
176            mTwoFingerScrolling = false;
177            mService.mH.obtainMessage(H.FINISH_TASK_POSITIONING).sendToTarget();
178        }
179    }
180
181    private final class TwoFingerScrollListener extends GestureDetector.SimpleOnGestureListener {
182        @Override
183        public boolean onScroll(MotionEvent e1, MotionEvent e2,
184                float distanceX, float distanceY) {
185            if (e2.getPointerCount() == 2) {
186                onTwoFingerScroll(e2);
187                return true;
188            }
189            stopTwoFingerScroll();
190            return false;
191        }
192    }
193
194    void setTouchExcludeRegion(Region newRegion, Region nonResizeableRegion) {
195        synchronized (this) {
196           mTouchExcludeRegion.set(newRegion);
197           mNonResizeableRegion.set(nonResizeableRegion);
198        }
199    }
200}
201