1/*
2 * Copyright (C) 2007 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.internal.policy.impl;
18
19import android.content.Context;
20import android.content.Intent;
21import android.graphics.Canvas;
22import android.graphics.ColorFilter;
23import android.graphics.PixelFormat;
24import android.graphics.PorterDuff;
25import android.graphics.drawable.Drawable;
26import android.media.AudioManager;
27import android.media.IAudioService;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.telephony.TelephonyManager;
31import android.view.KeyEvent;
32import android.view.View;
33import android.view.Gravity;
34import android.widget.FrameLayout;
35import android.util.AttributeSet;
36import android.util.Log;
37import android.util.Slog;
38
39/**
40 * Base class for keyguard views.  {@link #reset} is where you should
41 * reset the state of your view.  Use the {@link KeyguardViewCallback} via
42 * {@link #getCallback()} to send information back (such as poking the wake lock,
43 * or finishing the keyguard).
44 *
45 * Handles intercepting of media keys that still work when the keyguard is
46 * showing.
47 */
48public abstract class KeyguardViewBase extends FrameLayout {
49
50    private static final int BACKGROUND_COLOR = 0x70000000;
51    private KeyguardViewCallback mCallback;
52    private AudioManager mAudioManager;
53    private TelephonyManager mTelephonyManager = null;
54    // Whether the volume keys should be handled by keyguard. If true, then
55    // they will be handled here for specific media types such as music, otherwise
56    // the audio service will bring up the volume dialog.
57    private static final boolean KEYGUARD_MANAGES_VOLUME = true;
58
59    // This is a faster way to draw the background on devices without hardware acceleration
60    Drawable mBackgroundDrawable = new Drawable() {
61        @Override
62        public void draw(Canvas canvas) {
63            canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC);
64        }
65
66        @Override
67        public void setAlpha(int alpha) {
68        }
69
70        @Override
71        public void setColorFilter(ColorFilter cf) {
72        }
73
74        @Override
75        public int getOpacity() {
76            return PixelFormat.TRANSLUCENT;
77        }
78    };
79
80    public KeyguardViewBase(Context context, KeyguardViewCallback callback) {
81        super(context);
82        mCallback = callback;
83        resetBackground();
84    }
85
86    public void resetBackground() {
87        setBackgroundDrawable(mBackgroundDrawable);
88    }
89
90    public KeyguardViewCallback getCallback() {
91        return mCallback;
92    }
93
94    /**
95     * Called when you need to reset the state of your view.
96     */
97    abstract public void reset();
98
99    /**
100     * Called when the screen turned off.
101     */
102    abstract public void onScreenTurnedOff();
103
104    /**
105     * Called when the screen turned on.
106     */
107    abstract public void onScreenTurnedOn();
108
109    /**
110     * Called when the view needs to be shown.
111     */
112    abstract public void show();
113
114    /**
115     * Called when a key has woken the device to give us a chance to adjust our
116     * state according the the key.  We are responsible for waking the device
117     * (by poking the wake lock) once we are ready.
118     *
119     * The 'Tq' suffix is per the documentation in {@link android.view.WindowManagerPolicy}.
120     * Be sure not to take any action that takes a long time; any significant
121     * action should be posted to a handler.
122     *
123     * @param keyCode The wake key, which may be relevant for configuring the
124     *   keyguard.  May be {@link KeyEvent#KEYCODE_UNKNOWN} if waking for a reason
125     *   other than a key press.
126     */
127    abstract public void wakeWhenReadyTq(int keyCode);
128
129    /**
130     * Verify that the user can get past the keyguard securely.  This is called,
131     * for example, when the phone disables the keyguard but then wants to launch
132     * something else that requires secure access.
133     *
134     * The result will be propogated back via {@link KeyguardViewCallback#keyguardDone(boolean)}
135     */
136    abstract public void verifyUnlock();
137
138    /**
139     * Called before this view is being removed.
140     */
141    abstract public void cleanUp();
142
143    @Override
144    public boolean dispatchKeyEvent(KeyEvent event) {
145        if (shouldEventKeepScreenOnWhileKeyguardShowing(event)) {
146            mCallback.pokeWakelock();
147        }
148
149        if (interceptMediaKey(event)) {
150            return true;
151        }
152        return super.dispatchKeyEvent(event);
153    }
154
155    private boolean shouldEventKeepScreenOnWhileKeyguardShowing(KeyEvent event) {
156        if (event.getAction() != KeyEvent.ACTION_DOWN) {
157            return false;
158        }
159        switch (event.getKeyCode()) {
160            case KeyEvent.KEYCODE_DPAD_DOWN:
161            case KeyEvent.KEYCODE_DPAD_LEFT:
162            case KeyEvent.KEYCODE_DPAD_RIGHT:
163            case KeyEvent.KEYCODE_DPAD_UP:
164                return false;
165            default:
166                return true;
167        }
168    }
169
170    /**
171     * Allows the media keys to work when the keyguard is showing.
172     * The media keys should be of no interest to the actual keyguard view(s),
173     * so intercepting them here should not be of any harm.
174     * @param event The key event
175     * @return whether the event was consumed as a media key.
176     */
177    private boolean interceptMediaKey(KeyEvent event) {
178        final int keyCode = event.getKeyCode();
179        if (event.getAction() == KeyEvent.ACTION_DOWN) {
180            switch (keyCode) {
181                case KeyEvent.KEYCODE_MEDIA_PLAY:
182                case KeyEvent.KEYCODE_MEDIA_PAUSE:
183                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
184                    /* Suppress PLAY/PAUSE toggle when phone is ringing or
185                     * in-call to avoid music playback */
186                    if (mTelephonyManager == null) {
187                        mTelephonyManager = (TelephonyManager) getContext().getSystemService(
188                                Context.TELEPHONY_SERVICE);
189                    }
190                    if (mTelephonyManager != null &&
191                            mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
192                        return true;  // suppress key event
193                    }
194                case KeyEvent.KEYCODE_MUTE:
195                case KeyEvent.KEYCODE_HEADSETHOOK:
196                case KeyEvent.KEYCODE_MEDIA_STOP:
197                case KeyEvent.KEYCODE_MEDIA_NEXT:
198                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
199                case KeyEvent.KEYCODE_MEDIA_REWIND:
200                case KeyEvent.KEYCODE_MEDIA_RECORD:
201                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
202                    handleMediaKeyEvent(event);
203                    return true;
204                }
205
206                case KeyEvent.KEYCODE_VOLUME_UP:
207                case KeyEvent.KEYCODE_VOLUME_DOWN:
208                case KeyEvent.KEYCODE_VOLUME_MUTE: {
209                    if (KEYGUARD_MANAGES_VOLUME) {
210                        synchronized (this) {
211                            if (mAudioManager == null) {
212                                mAudioManager = (AudioManager) getContext().getSystemService(
213                                        Context.AUDIO_SERVICE);
214                            }
215                        }
216                        // Volume buttons should only function for music (local or remote).
217                        // TODO: Actually handle MUTE.
218                        mAudioManager.adjustLocalOrRemoteStreamVolume(
219                                AudioManager.STREAM_MUSIC,
220                                keyCode == KeyEvent.KEYCODE_VOLUME_UP
221                                        ? AudioManager.ADJUST_RAISE
222                                        : AudioManager.ADJUST_LOWER);
223                        // Don't execute default volume behavior
224                        return true;
225                    } else {
226                        return false;
227                    }
228                }
229            }
230        } else if (event.getAction() == KeyEvent.ACTION_UP) {
231            switch (keyCode) {
232                case KeyEvent.KEYCODE_MUTE:
233                case KeyEvent.KEYCODE_HEADSETHOOK:
234                case KeyEvent.KEYCODE_MEDIA_PLAY:
235                case KeyEvent.KEYCODE_MEDIA_PAUSE:
236                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
237                case KeyEvent.KEYCODE_MEDIA_STOP:
238                case KeyEvent.KEYCODE_MEDIA_NEXT:
239                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
240                case KeyEvent.KEYCODE_MEDIA_REWIND:
241                case KeyEvent.KEYCODE_MEDIA_RECORD:
242                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
243                    handleMediaKeyEvent(event);
244                    return true;
245                }
246            }
247        }
248        return false;
249    }
250
251    void handleMediaKeyEvent(KeyEvent keyEvent) {
252        IAudioService audioService = IAudioService.Stub.asInterface(
253                ServiceManager.checkService(Context.AUDIO_SERVICE));
254        if (audioService != null) {
255            try {
256                audioService.dispatchMediaKeyEvent(keyEvent);
257            } catch (RemoteException e) {
258                Log.e("KeyguardViewBase", "dispatchMediaKeyEvent threw exception " + e);
259            }
260        } else {
261            Slog.w("KeyguardViewBase", "Unable to find IAudioService for media key event");
262        }
263    }
264
265    @Override
266    public void dispatchSystemUiVisibilityChanged(int visibility) {
267        super.dispatchSystemUiVisibilityChanged(visibility);
268        setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
269    }
270}
271