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 17 package com.android.systemui.statusbar.policy; 18 19 import android.animation.ObjectAnimator; 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.graphics.Canvas; 23 import android.os.SystemClock; 24 import android.util.AttributeSet; 25 import android.util.Slog; 26 import android.view.MotionEvent; 27 import android.view.View; 28 29 import com.android.systemui.R; 30 31 /** 32 * The "dead zone" consumes unintentional taps along the top edge of the navigation bar. 33 * When users are typing quickly on an IME they may attempt to hit the space bar, overshoot, and 34 * accidentally hit the home button. The DeadZone expands temporarily after each tap in the UI 35 * outside the navigation bar (since this is when accidental taps are more likely), then contracts 36 * back over time (since a later tap might be intended for the top of the bar). 37 */ 38 public class DeadZone extends View { 39 public static final String TAG = "DeadZone"; 40 41 public static final boolean DEBUG = false; 42 public static final int HORIZONTAL = 0; // Consume taps along the top edge. 43 public static final int VERTICAL = 1; // Consume taps along the left edge. 44 45 private static final boolean CHATTY = true; // print to logcat when we eat a click 46 47 private boolean mShouldFlash; 48 private float mFlashFrac = 0f; 49 50 private int mSizeMax; 51 private int mSizeMin; 52 // Upon activity elsewhere in the UI, the dead zone will hold steady for 53 // mHold ms, then move back over the course of mDecay ms 54 private int mHold, mDecay; 55 private boolean mVertical; 56 private long mLastPokeTime; 57 58 private final Runnable mDebugFlash = new Runnable() { 59 @Override 60 public void run() { 61 ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start(); 62 } 63 }; 64 65 public DeadZone(Context context, AttributeSet attrs) { 66 this(context, attrs, 0); 67 } 68 69 public DeadZone(Context context, AttributeSet attrs, int defStyle) { 70 super(context, attrs); 71 72 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DeadZone, 73 defStyle, 0); 74 75 mHold = a.getInteger(R.styleable.DeadZone_holdTime, 0); 76 mDecay = a.getInteger(R.styleable.DeadZone_decayTime, 0); 77 78 mSizeMin = a.getDimensionPixelSize(R.styleable.DeadZone_minSize, 0); 79 mSizeMax = a.getDimensionPixelSize(R.styleable.DeadZone_maxSize, 0); 80 81 int index = a.getInt(R.styleable.DeadZone_orientation, -1); 82 mVertical = (index == VERTICAL); 83 84 if (DEBUG) 85 Slog.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold 86 + (mVertical ? " vertical" : " horizontal")); 87 88 setFlashOnTouchCapture(context.getResources().getBoolean(R.bool.config_dead_zone_flash)); 89 } 90 91 static float lerp(float a, float b, float f) { 92 return (b - a) * f + a; 93 } 94 95 private float getSize(long now) { 96 if (mSizeMax == 0) 97 return 0; 98 long dt = (now - mLastPokeTime); 99 if (dt > mHold + mDecay) 100 return mSizeMin; 101 if (dt < mHold) 102 return mSizeMax; 103 return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay); 104 } 105 106 public void setFlashOnTouchCapture(boolean dbg) { 107 mShouldFlash = dbg; 108 mFlashFrac = 0f; 109 postInvalidate(); 110 } 111 112 // I made you a touch event... 113 @Override 114 public boolean onTouchEvent(MotionEvent event) { 115 if (DEBUG) { 116 Slog.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); 117 } 118 119 // Don't consume events for high precision pointing devices. For this purpose a stylus is 120 // considered low precision (like a finger), so its events may be consumed. 121 if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { 122 return false; 123 } 124 125 final int action = event.getAction(); 126 if (action == MotionEvent.ACTION_OUTSIDE) { 127 poke(event); 128 } else if (action == MotionEvent.ACTION_DOWN) { 129 if (DEBUG) { 130 Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); 131 } 132 int size = (int) getSize(event.getEventTime()); 133 // In the vertical orientation consume taps along the left edge. 134 // In horizontal orientation consume taps along the top edge. 135 final boolean consumeEvent = mVertical ? event.getX() < size : event.getY() < size; 136 if (consumeEvent) { 137 if (CHATTY) { 138 Slog.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")"); 139 } 140 if (mShouldFlash) { 141 post(mDebugFlash); 142 postInvalidate(); 143 } 144 return true; // ...but I eated it 145 } 146 } 147 return false; 148 } 149 150 public void poke(MotionEvent event) { 151 mLastPokeTime = event.getEventTime(); 152 if (DEBUG) 153 Slog.v(TAG, "poked! size=" + getSize(mLastPokeTime)); 154 if (mShouldFlash) postInvalidate(); 155 } 156 157 public void setFlash(float f) { 158 mFlashFrac = f; 159 postInvalidate(); 160 } 161 162 public float getFlash() { 163 return mFlashFrac; 164 } 165 166 @Override 167 public void onDraw(Canvas can) { 168 if (!mShouldFlash || mFlashFrac <= 0f) { 169 return; 170 } 171 172 final int size = (int) getSize(SystemClock.uptimeMillis()); 173 can.clipRect(0, 0, mVertical ? size : can.getWidth(), mVertical ? can.getHeight() : size); 174 final float frac = DEBUG ? (mFlashFrac - 0.5f) + 0.5f : mFlashFrac; 175 can.drawARGB((int) (frac * 0xFF), 0xDD, 0xEE, 0xAA); 176 177 if (DEBUG && size > mSizeMin) 178 // crazy aggressive redrawing here, for debugging only 179 postInvalidateDelayed(100); 180 } 181 } 182