blob: 06040e2b0bcc7ca140135653e7f7cc897d179f59 [file] [log] [blame]
Daniel Sandlere97a7762012-03-19 22:56:42 -04001/*
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.systemui.statusbar.policy;
18
Daniel Sandlerc26185b2012-08-29 15:49:53 -040019import android.animation.ObjectAnimator;
Daniel Sandlere97a7762012-03-19 22:56:42 -040020import android.content.Context;
Daniel Sandlerc26185b2012-08-29 15:49:53 -040021import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.os.SystemClock;
Daniel Sandlere97a7762012-03-19 22:56:42 -040024import android.util.AttributeSet;
Daniel Sandler29787f6e2013-11-12 20:33:34 -050025import android.util.Slog;
Daniel Sandlere97a7762012-03-19 22:56:42 -040026import android.view.MotionEvent;
Jorim Jaggi7aaa3d72016-11-28 14:03:11 +010027import android.view.Surface;
Daniel Sandlere97a7762012-03-19 22:56:42 -040028import android.view.View;
29
30import com.android.systemui.R;
Jason Monkc2beef52017-08-29 18:58:00 -040031import com.android.systemui.SysUiServiceProvider;
32import com.android.systemui.statusbar.phone.StatusBar;
Daniel Sandlere97a7762012-03-19 22:56:42 -040033
James Cookf8da2082015-04-09 10:38:05 -070034/**
35 * The "dead zone" consumes unintentional taps along the top edge of the navigation bar.
36 * When users are typing quickly on an IME they may attempt to hit the space bar, overshoot, and
37 * accidentally hit the home button. The DeadZone expands temporarily after each tap in the UI
38 * outside the navigation bar (since this is when accidental taps are more likely), then contracts
39 * back over time (since a later tap might be intended for the top of the bar).
40 */
Daniel Sandlere97a7762012-03-19 22:56:42 -040041public class DeadZone extends View {
Daniel Sandlerc26185b2012-08-29 15:49:53 -040042 public static final String TAG = "DeadZone";
43
44 public static final boolean DEBUG = false;
James Cookf8da2082015-04-09 10:38:05 -070045 public static final int HORIZONTAL = 0; // Consume taps along the top edge.
46 public static final int VERTICAL = 1; // Consume taps along the left edge.
Daniel Sandlerc26185b2012-08-29 15:49:53 -040047
Daniel Sandler25bb8ee2012-08-30 17:10:50 -040048 private static final boolean CHATTY = true; // print to logcat when we eat a click
Jason Monkc2beef52017-08-29 18:58:00 -040049 private final StatusBar mStatusBar;
Daniel Sandler25bb8ee2012-08-30 17:10:50 -040050
Daniel Sandlerc26185b2012-08-29 15:49:53 -040051 private boolean mShouldFlash;
52 private float mFlashFrac = 0f;
53
54 private int mSizeMax;
55 private int mSizeMin;
56 // Upon activity elsewhere in the UI, the dead zone will hold steady for
57 // mHold ms, then move back over the course of mDecay ms
58 private int mHold, mDecay;
59 private boolean mVertical;
60 private long mLastPokeTime;
Jorim Jaggi7aaa3d72016-11-28 14:03:11 +010061 private int mDisplayRotation;
Daniel Sandlerc26185b2012-08-29 15:49:53 -040062
63 private final Runnable mDebugFlash = new Runnable() {
64 @Override
65 public void run() {
66 ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start();
67 }
68 };
69
Daniel Sandlere97a7762012-03-19 22:56:42 -040070 public DeadZone(Context context, AttributeSet attrs) {
71 this(context, attrs, 0);
72 }
73
74 public DeadZone(Context context, AttributeSet attrs, int defStyle) {
75 super(context, attrs);
Daniel Sandlerc26185b2012-08-29 15:49:53 -040076
77 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DeadZone,
78 defStyle, 0);
79
80 mHold = a.getInteger(R.styleable.DeadZone_holdTime, 0);
81 mDecay = a.getInteger(R.styleable.DeadZone_decayTime, 0);
82
83 mSizeMin = a.getDimensionPixelSize(R.styleable.DeadZone_minSize, 0);
84 mSizeMax = a.getDimensionPixelSize(R.styleable.DeadZone_maxSize, 0);
85
86 int index = a.getInt(R.styleable.DeadZone_orientation, -1);
87 mVertical = (index == VERTICAL);
88
89 if (DEBUG)
Daniel Sandler29787f6e2013-11-12 20:33:34 -050090 Slog.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold
Daniel Sandlerc26185b2012-08-29 15:49:53 -040091 + (mVertical ? " vertical" : " horizontal"));
92
Daniel Sandler25bb8ee2012-08-30 17:10:50 -040093 setFlashOnTouchCapture(context.getResources().getBoolean(R.bool.config_dead_zone_flash));
Jason Monkc2beef52017-08-29 18:58:00 -040094 mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
Daniel Sandlerc26185b2012-08-29 15:49:53 -040095 }
96
97 static float lerp(float a, float b, float f) {
98 return (b - a) * f + a;
99 }
100
101 private float getSize(long now) {
102 if (mSizeMax == 0)
103 return 0;
104 long dt = (now - mLastPokeTime);
105 if (dt > mHold + mDecay)
106 return mSizeMin;
107 if (dt < mHold)
108 return mSizeMax;
109 return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay);
110 }
111
112 public void setFlashOnTouchCapture(boolean dbg) {
113 mShouldFlash = dbg;
114 mFlashFrac = 0f;
115 postInvalidate();
Daniel Sandlere97a7762012-03-19 22:56:42 -0400116 }
117
Daniel Sandler25bb8ee2012-08-30 17:10:50 -0400118 // I made you a touch event...
Daniel Sandlere97a7762012-03-19 22:56:42 -0400119 @Override
Daniel Sandlerc26185b2012-08-29 15:49:53 -0400120 public boolean onTouchEvent(MotionEvent event) {
Daniel Sandler25bb8ee2012-08-30 17:10:50 -0400121 if (DEBUG) {
Daniel Sandler29787f6e2013-11-12 20:33:34 -0500122 Slog.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction()));
Daniel Sandler25bb8ee2012-08-30 17:10:50 -0400123 }
Daniel Sandlerc26185b2012-08-29 15:49:53 -0400124
James Cookf8da2082015-04-09 10:38:05 -0700125 // Don't consume events for high precision pointing devices. For this purpose a stylus is
126 // considered low precision (like a finger), so its events may be consumed.
127 if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
128 return false;
129 }
130
Daniel Sandlerc26185b2012-08-29 15:49:53 -0400131 final int action = event.getAction();
132 if (action == MotionEvent.ACTION_OUTSIDE) {
133 poke(event);
Siarhei Vishniakou6ad0e392017-06-02 17:20:34 -0700134 return true;
Daniel Sandlerc26185b2012-08-29 15:49:53 -0400135 } else if (action == MotionEvent.ACTION_DOWN) {
Daniel Sandler25bb8ee2012-08-30 17:10:50 -0400136 if (DEBUG) {
Daniel Sandler29787f6e2013-11-12 20:33:34 -0500137 Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY());
Daniel Sandler25bb8ee2012-08-30 17:10:50 -0400138 }
Jason Monkc2beef52017-08-29 18:58:00 -0400139 if (mStatusBar != null) mStatusBar.touchAutoDim();
Daniel Sandlerc26185b2012-08-29 15:49:53 -0400140 int size = (int) getSize(event.getEventTime());
James Cookf8da2082015-04-09 10:38:05 -0700141 // In the vertical orientation consume taps along the left edge.
142 // In horizontal orientation consume taps along the top edge.
Jorim Jaggi7aaa3d72016-11-28 14:03:11 +0100143 final boolean consumeEvent;
144 if (mVertical) {
145 if (mDisplayRotation == Surface.ROTATION_270) {
146 consumeEvent = event.getX() > getWidth() - size;
147 } else {
148 consumeEvent = event.getX() < size;
149 }
150 } else {
151 consumeEvent = event.getY() < size;
152 }
James Cookf8da2082015-04-09 10:38:05 -0700153 if (consumeEvent) {
Daniel Sandler25bb8ee2012-08-30 17:10:50 -0400154 if (CHATTY) {
Daniel Sandler29787f6e2013-11-12 20:33:34 -0500155 Slog.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")");
Daniel Sandler25bb8ee2012-08-30 17:10:50 -0400156 }
Daniel Sandlerc26185b2012-08-29 15:49:53 -0400157 if (mShouldFlash) {
158 post(mDebugFlash);
159 postInvalidate();
160 }
Daniel Sandler25bb8ee2012-08-30 17:10:50 -0400161 return true; // ...but I eated it
Daniel Sandlerc26185b2012-08-29 15:49:53 -0400162 }
163 }
164 return false;
165 }
166
Siarhei Vishniakou6ad0e392017-06-02 17:20:34 -0700167 private void poke(MotionEvent event) {
Daniel Sandlerc26185b2012-08-29 15:49:53 -0400168 mLastPokeTime = event.getEventTime();
169 if (DEBUG)
Daniel Sandler29787f6e2013-11-12 20:33:34 -0500170 Slog.v(TAG, "poked! size=" + getSize(mLastPokeTime));
Daniel Sandler4f6ec7f2013-01-17 16:02:36 -0500171 if (mShouldFlash) postInvalidate();
Daniel Sandlerc26185b2012-08-29 15:49:53 -0400172 }
173
174 public void setFlash(float f) {
175 mFlashFrac = f;
176 postInvalidate();
177 }
178
179 public float getFlash() {
180 return mFlashFrac;
181 }
182
183 @Override
184 public void onDraw(Canvas can) {
185 if (!mShouldFlash || mFlashFrac <= 0f) {
186 return;
187 }
188
189 final int size = (int) getSize(SystemClock.uptimeMillis());
Jorim Jaggi7aaa3d72016-11-28 14:03:11 +0100190 if (mVertical) {
191 if (mDisplayRotation == Surface.ROTATION_270) {
192 can.clipRect(can.getWidth() - size, 0, can.getWidth(), can.getHeight());
193 } else {
194 can.clipRect(0, 0, size, can.getHeight());
195 }
196 } else {
197 can.clipRect(0, 0, can.getWidth(), size);
198 }
199
Daniel Sandlerc26185b2012-08-29 15:49:53 -0400200 final float frac = DEBUG ? (mFlashFrac - 0.5f) + 0.5f : mFlashFrac;
201 can.drawARGB((int) (frac * 0xFF), 0xDD, 0xEE, 0xAA);
202
203 if (DEBUG && size > mSizeMin)
204 // crazy aggressive redrawing here, for debugging only
205 postInvalidateDelayed(100);
Daniel Sandlere97a7762012-03-19 22:56:42 -0400206 }
Jorim Jaggi7aaa3d72016-11-28 14:03:11 +0100207
208 public void setDisplayRotation(int rotation) {
209 mDisplayRotation = rotation;
210 }
Daniel Sandlere97a7762012-03-19 22:56:42 -0400211}