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