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.Surface; 28 import android.view.View; 29 30 import com.android.systemui.R; 31 import com.android.systemui.SysUiServiceProvider; 32 import com.android.systemui.statusbar.phone.StatusBar; 33 34 /** 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 */ 41 public class DeadZone extends View { 42 public static final String TAG = "DeadZone"; 43 44 public static final boolean DEBUG = false; 45 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. 47 48 private static final boolean CHATTY = true; // print to logcat when we eat a click 49 private final StatusBar mStatusBar; 50 51 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; 61 private int mDisplayRotation; 62 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 70 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); 76 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) 90 Slog.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold 91 + (mVertical ? " vertical" : " horizontal")); 92 93 setFlashOnTouchCapture(context.getResources().getBoolean(R.bool.config_dead_zone_flash)); 94 mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class); 95 } 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(); 116 } 117 118 // I made you a touch event... 119 @Override 120 public boolean onTouchEvent(MotionEvent event) { 121 if (DEBUG) { 122 Slog.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); 123 } 124 125 // 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 131 final int action = event.getAction(); 132 if (action == MotionEvent.ACTION_OUTSIDE) { 133 poke(event); 134 return true; 135 } else if (action == MotionEvent.ACTION_DOWN) { 136 if (DEBUG) { 137 Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); 138 } 139 if (mStatusBar != null) mStatusBar.touchAutoDim(); 140 int size = (int) getSize(event.getEventTime()); 141 // In the vertical orientation consume taps along the left edge. 142 // In horizontal orientation consume taps along the top edge. 143 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 } 153 if (consumeEvent) { 154 if (CHATTY) { 155 Slog.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")"); 156 } 157 if (mShouldFlash) { 158 post(mDebugFlash); 159 postInvalidate(); 160 } 161 return true; // ...but I eated it 162 } 163 } 164 return false; 165 } 166 167 private void poke(MotionEvent event) { 168 mLastPokeTime = event.getEventTime(); 169 if (DEBUG) 170 Slog.v(TAG, "poked! size=" + getSize(mLastPokeTime)); 171 if (mShouldFlash) postInvalidate(); 172 } 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()); 190 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 200 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); 206 } 207 208 public void setDisplayRotation(int rotation) { 209 mDisplayRotation = rotation; 210 } 211 } 212