1/*
2 * Copyright (C) 2008 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.app.ActivityManager;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.content.res.TypedArray;
23import android.graphics.drawable.Drawable;
24import android.graphics.drawable.Icon;
25import android.hardware.input.InputManager;
26import android.media.AudioManager;
27import android.os.AsyncTask;
28import android.os.Bundle;
29import android.os.SystemClock;
30import android.util.AttributeSet;
31import android.util.TypedValue;
32import android.view.HapticFeedbackConstants;
33import android.view.InputDevice;
34import android.view.KeyCharacterMap;
35import android.view.KeyEvent;
36import android.view.MotionEvent;
37import android.view.SoundEffectConstants;
38import android.view.View;
39import android.view.ViewConfiguration;
40import android.view.accessibility.AccessibilityEvent;
41import android.view.accessibility.AccessibilityNodeInfo;
42import android.widget.ImageView;
43
44import com.android.systemui.R;
45
46import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
47import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
48
49public class KeyButtonView extends ImageView {
50
51    private int mContentDescriptionRes;
52    private long mDownTime;
53    private int mCode;
54    private int mTouchSlop;
55    private boolean mSupportsLongpress = true;
56    private AudioManager mAudioManager;
57    private boolean mGestureAborted;
58    private boolean mLongClicked;
59
60    private final Runnable mCheckLongPress = new Runnable() {
61        public void run() {
62            if (isPressed()) {
63                // Log.d("KeyButtonView", "longpressed: " + this);
64                if (isLongClickable()) {
65                    // Just an old-fashioned ImageView
66                    performLongClick();
67                    mLongClicked = true;
68                } else if (mSupportsLongpress) {
69                    sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
70                    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
71                    mLongClicked = true;
72                }
73            }
74        }
75    };
76
77    public KeyButtonView(Context context, AttributeSet attrs) {
78        this(context, attrs, 0);
79    }
80
81    public KeyButtonView(Context context, AttributeSet attrs, int defStyle) {
82        super(context, attrs);
83
84        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView,
85                defStyle, 0);
86
87        mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0);
88
89        mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
90
91        TypedValue value = new TypedValue();
92        if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) {
93            mContentDescriptionRes = value.resourceId;
94        }
95
96        a.recycle();
97
98
99        setClickable(true);
100        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
101        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
102        setBackground(new KeyButtonRipple(context, this));
103    }
104
105    public void setCode(int code) {
106        mCode = code;
107    }
108
109    public void loadAsync(String uri) {
110        new AsyncTask<String, Void, Drawable>() {
111            @Override
112            protected Drawable doInBackground(String... params) {
113                return Icon.createWithContentUri(params[0]).loadDrawable(mContext);
114            }
115
116            @Override
117            protected void onPostExecute(Drawable drawable) {
118                setImageDrawable(drawable);
119            }
120        }.execute(uri);
121    }
122
123    @Override
124    protected void onConfigurationChanged(Configuration newConfig) {
125        super.onConfigurationChanged(newConfig);
126
127        if (mContentDescriptionRes != 0) {
128            setContentDescription(mContext.getString(mContentDescriptionRes));
129        }
130    }
131
132    @Override
133    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
134        super.onInitializeAccessibilityNodeInfo(info);
135        if (mCode != 0) {
136            info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
137            if (mSupportsLongpress || isLongClickable()) {
138                info.addAction(
139                        new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
140            }
141        }
142    }
143
144    @Override
145    protected void onWindowVisibilityChanged(int visibility) {
146        super.onWindowVisibilityChanged(visibility);
147        if (visibility != View.VISIBLE) {
148            jumpDrawablesToCurrentState();
149        }
150    }
151
152    @Override
153    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
154        if (action == ACTION_CLICK && mCode != 0) {
155            sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis());
156            sendEvent(KeyEvent.ACTION_UP, 0);
157            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
158            playSoundEffect(SoundEffectConstants.CLICK);
159            return true;
160        } else if (action == ACTION_LONG_CLICK && mCode != 0) {
161            sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
162            sendEvent(KeyEvent.ACTION_UP, 0);
163            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
164            return true;
165        }
166        return super.performAccessibilityActionInternal(action, arguments);
167    }
168
169    public boolean onTouchEvent(MotionEvent ev) {
170        final int action = ev.getAction();
171        int x, y;
172        if (action == MotionEvent.ACTION_DOWN) {
173            mGestureAborted = false;
174        }
175        if (mGestureAborted) {
176            return false;
177        }
178
179        switch (action) {
180            case MotionEvent.ACTION_DOWN:
181                mDownTime = SystemClock.uptimeMillis();
182                mLongClicked = false;
183                setPressed(true);
184                if (mCode != 0) {
185                    sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
186                } else {
187                    // Provide the same haptic feedback that the system offers for virtual keys.
188                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
189                }
190                removeCallbacks(mCheckLongPress);
191                postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
192                break;
193            case MotionEvent.ACTION_MOVE:
194                x = (int)ev.getX();
195                y = (int)ev.getY();
196                setPressed(x >= -mTouchSlop
197                        && x < getWidth() + mTouchSlop
198                        && y >= -mTouchSlop
199                        && y < getHeight() + mTouchSlop);
200                break;
201            case MotionEvent.ACTION_CANCEL:
202                setPressed(false);
203                if (mCode != 0) {
204                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
205                }
206                removeCallbacks(mCheckLongPress);
207                break;
208            case MotionEvent.ACTION_UP:
209                final boolean doIt = isPressed() && !mLongClicked;
210                setPressed(false);
211                if (mCode != 0) {
212                    if (doIt) {
213                        sendEvent(KeyEvent.ACTION_UP, 0);
214                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
215                        playSoundEffect(SoundEffectConstants.CLICK);
216                    } else {
217                        sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
218                    }
219                } else {
220                    // no key code, just a regular ImageView
221                    if (doIt) {
222                        performClick();
223                    }
224                }
225                removeCallbacks(mCheckLongPress);
226                break;
227        }
228
229        return true;
230    }
231
232    public void playSoundEffect(int soundConstant) {
233        mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser());
234    };
235
236    public void sendEvent(int action, int flags) {
237        sendEvent(action, flags, SystemClock.uptimeMillis());
238    }
239
240    void sendEvent(int action, int flags, long when) {
241        final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
242        final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
243                0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
244                flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
245                InputDevice.SOURCE_KEYBOARD);
246        InputManager.getInstance().injectInputEvent(ev,
247                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
248    }
249
250    public void abortCurrentGesture() {
251        setPressed(false);
252        mGestureAborted = true;
253    }
254}
255
256
257