Home | History | Annotate | Download | only in policy
      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