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