1/*
2 * Copyright (C) 2012 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.phone;
18
19import android.util.Log;
20import android.view.MotionEvent;
21import android.view.View;
22
23/**
24 * OnTouchListener used to shrink the "hit target" of some onscreen buttons.
25 *
26 * We do this for a few specific buttons which are vulnerable to
27 * "false touches" because either (1) they're near the edge of the
28 * screen and might be unintentionally touched while holding the
29 * device in your hand, (2) they're in the upper corners and might
30 * be touched by the user's ear before the prox sensor has a chance to
31 * kick in, or (3) they are close to other buttons.
32 */
33public class SmallerHitTargetTouchListener implements View.OnTouchListener {
34    private static final String TAG = "SmallerHitTargetTouchListener";
35
36    /**
37     * Edge dimensions where a touch does not register an action (in DIP).
38     */
39    private static final int HIT_TARGET_EDGE_IGNORE_DP_X = 30;
40    private static final int HIT_TARGET_EDGE_IGNORE_DP_Y = 10;
41    private static final int HIT_TARGET_MIN_SIZE_DP_X = HIT_TARGET_EDGE_IGNORE_DP_X * 3;
42    private static final int HIT_TARGET_MIN_SIZE_DP_Y = HIT_TARGET_EDGE_IGNORE_DP_Y * 3;
43
44    // True if the most recent DOWN event was a "hit".
45    boolean mDownEventHit;
46
47    /**
48     * Called when a touch event is dispatched to a view. This allows listeners to
49     * get a chance to respond before the target view.
50     *
51     * @return True if the listener has consumed the event, false otherwise.
52     *         (In other words, we return true when the touch is *outside*
53     *         the "smaller hit target", which will prevent the actual
54     *         button from handling these events.)
55     */
56    @Override
57    public boolean onTouch(View v, MotionEvent event) {
58        // if (DBG) log("SmallerHitTargetTouchListener: " + v + ", event " + event);
59
60        if (event.getAction() == MotionEvent.ACTION_DOWN) {
61            // Note that event.getX() and event.getY() are already
62            // translated into the View's coordinates.  (In other words,
63            // "0,0" is a touch on the upper-left-most corner of the view.)
64            final int touchX = (int) event.getX();
65            final int touchY = (int) event.getY();
66
67            final int viewWidth = v.getWidth();
68            final int viewHeight = v.getHeight();
69
70            final float pixelDensity = v.getResources().getDisplayMetrics().density;
71            final int targetMinSizeX = (int) (HIT_TARGET_MIN_SIZE_DP_X * pixelDensity);
72            final int targetMinSizeY = (int) (HIT_TARGET_MIN_SIZE_DP_Y * pixelDensity);
73
74            int edgeIgnoreX = (int) (HIT_TARGET_EDGE_IGNORE_DP_X * pixelDensity);
75            int edgeIgnoreY = (int) (HIT_TARGET_EDGE_IGNORE_DP_Y * pixelDensity);
76
77            // If we are dealing with smaller buttons where the dead zone defined by
78            // HIT_TARGET_EDGE_IGNORE_DP_[X|Y] is too large.
79            if (viewWidth < targetMinSizeX || viewHeight < targetMinSizeY) {
80                // This really should not happen given our two use cases (as of this writing)
81                // in the call edge button and secondary calling card. However, we leave
82                // this is as a precautionary measure.
83                Log.w(TAG, "onTouch: view is too small for SmallerHitTargetTouchListener");
84                edgeIgnoreX = 0;
85                edgeIgnoreY = 0;
86            }
87
88            final int minTouchX = edgeIgnoreX;
89            final int maxTouchX = viewWidth - edgeIgnoreX;
90            final int minTouchY = edgeIgnoreY;
91            final int maxTouchY = viewHeight - edgeIgnoreY;
92
93            if (touchX < minTouchX || touchX > maxTouchX ||
94                    touchY < minTouchY || touchY > maxTouchY) {
95                // Missed!
96                // if (DBG) log("  -> MISSED!");
97                mDownEventHit = false;
98                return true;  // Consume this event; don't let the button see it
99            } else {
100                // Hit!
101                // if (DBG) log("  -> HIT!");
102                mDownEventHit = true;
103                return false;  // Let this event through to the actual button
104            }
105        } else {
106            // This is a MOVE, UP or CANCEL event.
107            //
108            // We only do the "smaller hit target" check on DOWN events.
109            // For the subsequent MOVE/UP/CANCEL events, we let them
110            // through to the actual button IFF the previous DOWN event
111            // got through to the actual button (i.e. it was a "hit".)
112            return !mDownEventHit;
113        }
114    }
115}
116