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.view.MotionEvent;
20import android.view.View;
21
22/**
23 * OnTouchListener used to shrink the "hit target" of some onscreen buttons.
24 *
25 * We do this for a few specific buttons which are vulnerable to
26 * "false touches" because either (1) they're near the edge of the
27 * screen and might be unintentionally touched while holding the
28 * device in your hand, or (2) they're in the upper corners and might
29 * be touched by the user's ear before the prox sensor has a chance to
30 * kick in.
31 *
32 * TODO (new ICS layout): not sure which buttons need this yet.
33 * For now, use it only with the "End call" button (which extends all
34 * the way to the edges of the screen).  But we can consider doing
35 * this for "Dialpad" and/or "Add call" if those turn out to be a
36 * problem too.
37 */
38public class SmallerHitTargetTouchListener implements View.OnTouchListener {
39    /**
40     * Width of the allowable "hit target" as a percentage of
41     * the total width of this button.
42     */
43    private static final int HIT_TARGET_PERCENT_X = 50;
44
45    /**
46     * Height of the allowable "hit target" as a percentage of
47     * the total height of this button.
48     *
49     * This is larger than HIT_TARGET_PERCENT_X because some of
50     * the onscreen buttons are wide but not very tall and we don't
51     * want to make the vertical hit target *too* small.
52     */
53    private static final int HIT_TARGET_PERCENT_Y = 80;
54
55    // Size (percentage-wise) of the "edge" area that's *not* touch-sensitive.
56    private static final int X_EDGE = (100 - HIT_TARGET_PERCENT_X) / 2;
57    private static final int Y_EDGE = (100 - HIT_TARGET_PERCENT_Y) / 2;
58    // Min/max values (percentage-wise) of the touch-sensitive hit target.
59    private static final int X_HIT_MIN = X_EDGE;
60    private static final int X_HIT_MAX = 100 - X_EDGE;
61    private static final int Y_HIT_MIN = Y_EDGE;
62    private static final int Y_HIT_MAX = 100 - Y_EDGE;
63
64    // True if the most recent DOWN event was a "hit".
65    boolean mDownEventHit;
66
67    /**
68     * Called when a touch event is dispatched to a view. This allows listeners to
69     * get a chance to respond before the target view.
70     *
71     * @return True if the listener has consumed the event, false otherwise.
72     *         (In other words, we return true when the touch is *outside*
73     *         the "smaller hit target", which will prevent the actual
74     *         button from handling these events.)
75     */
76    @Override
77    public boolean onTouch(View v, MotionEvent event) {
78        // if (DBG) log("SmallerHitTargetTouchListener: " + v + ", event " + event);
79
80        if (event.getAction() == MotionEvent.ACTION_DOWN) {
81            // Note that event.getX() and event.getY() are already
82            // translated into the View's coordinates.  (In other words,
83            // "0,0" is a touch on the upper-left-most corner of the view.)
84            int touchX = (int) event.getX();
85            int touchY = (int) event.getY();
86
87            int viewWidth = v.getWidth();
88            int viewHeight = v.getHeight();
89
90            // Touch location as a percentage of the total button width or height.
91            int touchXPercent = (int) ((float) (touchX * 100) / (float) viewWidth);
92            int touchYPercent = (int) ((float) (touchY * 100) / (float) viewHeight);
93            // if (DBG) log("- percentage:  x = " + touchXPercent + ",  y = " + touchYPercent);
94
95            // TODO: user research: add event logging here of the actual
96            // hit location (and button ID), and enable it for dogfooders
97            // for a few days.  That'll give us a good idea of how close
98            // to the center of the button(s) most touch events are, to
99            // help us fine-tune the HIT_TARGET_PERCENT_* constants.
100
101            if (touchXPercent < X_HIT_MIN || touchXPercent > X_HIT_MAX
102                    || touchYPercent < Y_HIT_MIN || touchYPercent > Y_HIT_MAX) {
103                // Missed!
104                // if (DBG) log("  -> MISSED!");
105                mDownEventHit = false;
106                return true;  // Consume this event; don't let the button see it
107            } else {
108                // Hit!
109                // if (DBG) log("  -> HIT!");
110                mDownEventHit = true;
111                return false;  // Let this event through to the actual button
112            }
113        } else {
114            // This is a MOVE, UP or CANCEL event.
115            //
116            // We only do the "smaller hit target" check on DOWN events.
117            // For the subsequent MOVE/UP/CANCEL events, we let them
118            // through to the actual button IFF the previous DOWN event
119            // got through to the actual button (i.e. it was a "hit".)
120            return !mDownEventHit;
121        }
122    }
123}