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.internal.policy.impl;
18
19import com.android.internal.R;
20import com.android.internal.policy.impl.KeyguardUpdateMonitor.InfoCallbackImpl;
21import com.android.internal.policy.impl.KeyguardUpdateMonitor.SimStateCallback;
22import com.android.internal.telephony.IccCard.State;
23import com.android.internal.widget.LockPatternUtils;
24import com.android.internal.widget.SlidingTab;
25import com.android.internal.widget.WaveView;
26import com.android.internal.widget.multiwaveview.GlowPadView;
27
28import android.app.ActivityManager;
29import android.app.ActivityManagerNative;
30import android.app.SearchManager;
31import android.content.ActivityNotFoundException;
32import android.content.ComponentName;
33import android.content.Context;
34import android.content.Intent;
35import android.content.res.Configuration;
36import android.content.res.Resources;
37import android.os.Vibrator;
38import android.view.KeyEvent;
39import android.view.LayoutInflater;
40import android.view.View;
41import android.view.ViewGroup;
42import android.widget.*;
43import android.util.Log;
44import android.util.Slog;
45import android.media.AudioManager;
46import android.os.RemoteException;
47import android.provider.MediaStore;
48
49import java.io.File;
50
51/**
52 * The screen within {@link LockPatternKeyguardView} that shows general
53 * information about the device depending on its state, and how to get
54 * past it, as applicable.
55 */
56class LockScreen extends LinearLayout implements KeyguardScreen {
57
58    private static final int ON_RESUME_PING_DELAY = 500; // delay first ping until the screen is on
59    private static final boolean DBG = false;
60    private static final String TAG = "LockScreen";
61    private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
62    private static final int WAIT_FOR_ANIMATION_TIMEOUT = 0;
63    private static final int STAY_ON_WHILE_GRABBED_TIMEOUT = 30000;
64    private static final String ASSIST_ICON_METADATA_NAME =
65            "com.android.systemui.action_assist_icon";
66
67    private LockPatternUtils mLockPatternUtils;
68    private KeyguardUpdateMonitor mUpdateMonitor;
69    private KeyguardScreenCallback mCallback;
70
71    // set to 'true' to show the ring/silence target when camera isn't available
72    private boolean mEnableRingSilenceFallback = false;
73
74    // current configuration state of keyboard and display
75    private int mCreationOrientation;
76
77    private boolean mSilentMode;
78    private AudioManager mAudioManager;
79    private boolean mEnableMenuKeyInLockScreen;
80
81    private KeyguardStatusViewManager mStatusViewManager;
82    private UnlockWidgetCommonMethods mUnlockWidgetMethods;
83    private View mUnlockWidget;
84    private boolean mCameraDisabled;
85    private boolean mSearchDisabled;
86    // Is there a vibrator
87    private final boolean mHasVibrator;
88
89    InfoCallbackImpl mInfoCallback = new InfoCallbackImpl() {
90
91        @Override
92        public void onRingerModeChanged(int state) {
93            boolean silent = AudioManager.RINGER_MODE_NORMAL != state;
94            if (silent != mSilentMode) {
95                mSilentMode = silent;
96                mUnlockWidgetMethods.updateResources();
97            }
98        }
99
100        @Override
101        public void onDevicePolicyManagerStateChanged() {
102            updateTargets();
103        }
104
105    };
106
107    SimStateCallback mSimStateCallback = new SimStateCallback() {
108        public void onSimStateChanged(State simState) {
109            updateTargets();
110        }
111    };
112
113    private interface UnlockWidgetCommonMethods {
114        // Update resources based on phone state
115        public void updateResources();
116
117        // Get the view associated with this widget
118        public View getView();
119
120        // Reset the view
121        public void reset(boolean animate);
122
123        // Animate the widget if it supports ping()
124        public void ping();
125
126        // Enable or disable a target. ResourceId is the id of the *drawable* associated with the
127        // target.
128        public void setEnabled(int resourceId, boolean enabled);
129
130        // Get the target position for the given resource. Returns -1 if not found.
131        public int getTargetPosition(int resourceId);
132
133        // Clean up when this widget is going away
134        public void cleanUp();
135    }
136
137    class SlidingTabMethods implements SlidingTab.OnTriggerListener, UnlockWidgetCommonMethods {
138        private final SlidingTab mSlidingTab;
139
140        SlidingTabMethods(SlidingTab slidingTab) {
141            mSlidingTab = slidingTab;
142        }
143
144        public void updateResources() {
145            boolean vibe = mSilentMode
146                && (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE);
147
148            mSlidingTab.setRightTabResources(
149                    mSilentMode ? ( vibe ? R.drawable.ic_jog_dial_vibrate_on
150                                         : R.drawable.ic_jog_dial_sound_off )
151                                : R.drawable.ic_jog_dial_sound_on,
152                    mSilentMode ? R.drawable.jog_tab_target_yellow
153                                : R.drawable.jog_tab_target_gray,
154                    mSilentMode ? R.drawable.jog_tab_bar_right_sound_on
155                                : R.drawable.jog_tab_bar_right_sound_off,
156                    mSilentMode ? R.drawable.jog_tab_right_sound_on
157                                : R.drawable.jog_tab_right_sound_off);
158        }
159
160        /** {@inheritDoc} */
161        public void onTrigger(View v, int whichHandle) {
162            if (whichHandle == SlidingTab.OnTriggerListener.LEFT_HANDLE) {
163                mCallback.goToUnlockScreen();
164            } else if (whichHandle == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
165                toggleRingMode();
166                mCallback.pokeWakelock();
167            }
168        }
169
170        /** {@inheritDoc} */
171        public void onGrabbedStateChange(View v, int grabbedState) {
172            if (grabbedState == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
173                mSilentMode = isSilentMode();
174                mSlidingTab.setRightHintText(mSilentMode ? R.string.lockscreen_sound_on_label
175                        : R.string.lockscreen_sound_off_label);
176            }
177            // Don't poke the wake lock when returning to a state where the handle is
178            // not grabbed since that can happen when the system (instead of the user)
179            // cancels the grab.
180            if (grabbedState != SlidingTab.OnTriggerListener.NO_HANDLE) {
181                mCallback.pokeWakelock();
182            }
183        }
184
185        public View getView() {
186            return mSlidingTab;
187        }
188
189        public void reset(boolean animate) {
190            mSlidingTab.reset(animate);
191        }
192
193        public void ping() {
194        }
195
196        public void setEnabled(int resourceId, boolean enabled) {
197            // Not used
198        }
199
200        public int getTargetPosition(int resourceId) {
201            return -1; // Not supported
202        }
203
204        public void cleanUp() {
205            mSlidingTab.setOnTriggerListener(null);
206        }
207    }
208
209    class WaveViewMethods implements WaveView.OnTriggerListener, UnlockWidgetCommonMethods {
210
211        private final WaveView mWaveView;
212
213        WaveViewMethods(WaveView waveView) {
214            mWaveView = waveView;
215        }
216        /** {@inheritDoc} */
217        public void onTrigger(View v, int whichHandle) {
218            if (whichHandle == WaveView.OnTriggerListener.CENTER_HANDLE) {
219                requestUnlockScreen();
220            }
221        }
222
223        /** {@inheritDoc} */
224        public void onGrabbedStateChange(View v, int grabbedState) {
225            // Don't poke the wake lock when returning to a state where the handle is
226            // not grabbed since that can happen when the system (instead of the user)
227            // cancels the grab.
228            if (grabbedState == WaveView.OnTriggerListener.CENTER_HANDLE) {
229                mCallback.pokeWakelock(STAY_ON_WHILE_GRABBED_TIMEOUT);
230            }
231        }
232
233        public void updateResources() {
234        }
235
236        public View getView() {
237            return mWaveView;
238        }
239        public void reset(boolean animate) {
240            mWaveView.reset();
241        }
242        public void ping() {
243        }
244        public void setEnabled(int resourceId, boolean enabled) {
245            // Not used
246        }
247        public int getTargetPosition(int resourceId) {
248            return -1; // Not supported
249        }
250        public void cleanUp() {
251            mWaveView.setOnTriggerListener(null);
252        }
253    }
254
255    class GlowPadViewMethods implements GlowPadView.OnTriggerListener,
256            UnlockWidgetCommonMethods {
257        private final GlowPadView mGlowPadView;
258
259        GlowPadViewMethods(GlowPadView glowPadView) {
260            mGlowPadView = glowPadView;
261        }
262
263        public boolean isTargetPresent(int resId) {
264            return mGlowPadView.getTargetPosition(resId) != -1;
265        }
266
267        public void updateResources() {
268            int resId;
269            if (mCameraDisabled && mEnableRingSilenceFallback) {
270                // Fall back to showing ring/silence if camera is disabled...
271                resId = mSilentMode ? R.array.lockscreen_targets_when_silent
272                    : R.array.lockscreen_targets_when_soundon;
273            } else {
274                resId = R.array.lockscreen_targets_with_camera;
275            }
276            if (mGlowPadView.getTargetResourceId() != resId) {
277                mGlowPadView.setTargetResources(resId);
278            }
279
280            // Update the search icon with drawable from the search .apk
281            if (!mSearchDisabled) {
282                Intent intent = SearchManager.getAssistIntent(mContext);
283                if (intent != null) {
284                    // XXX Hack. We need to substitute the icon here but haven't formalized
285                    // the public API. The "_google" metadata will be going away, so
286                    // DON'T USE IT!
287                    ComponentName component = intent.getComponent();
288                    boolean replaced = mGlowPadView.replaceTargetDrawablesIfPresent(component,
289                            ASSIST_ICON_METADATA_NAME + "_google",
290                            com.android.internal.R.drawable.ic_action_assist_generic);
291
292                    if (!replaced && !mGlowPadView.replaceTargetDrawablesIfPresent(component,
293                                ASSIST_ICON_METADATA_NAME,
294                                com.android.internal.R.drawable.ic_action_assist_generic)) {
295                            Slog.w(TAG, "Couldn't grab icon from package " + component);
296                    }
297                }
298            }
299
300            setEnabled(com.android.internal.R.drawable.ic_lockscreen_camera, !mCameraDisabled);
301            setEnabled(com.android.internal.R.drawable.ic_action_assist_generic, !mSearchDisabled);
302        }
303
304        public void onGrabbed(View v, int handle) {
305
306        }
307
308        public void onReleased(View v, int handle) {
309
310        }
311
312        public void onTrigger(View v, int target) {
313            final int resId = mGlowPadView.getResourceIdForTarget(target);
314            switch (resId) {
315                case com.android.internal.R.drawable.ic_action_assist_generic:
316                    Intent assistIntent = SearchManager.getAssistIntent(mContext);
317                    if (assistIntent != null) {
318                        launchActivity(assistIntent);
319                    } else {
320                        Log.w(TAG, "Failed to get intent for assist activity");
321                    }
322                    mCallback.pokeWakelock();
323                    break;
324
325                case com.android.internal.R.drawable.ic_lockscreen_camera:
326                    launchActivity(new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA));
327                    mCallback.pokeWakelock();
328                    break;
329
330                case com.android.internal.R.drawable.ic_lockscreen_silent:
331                    toggleRingMode();
332                    mCallback.pokeWakelock();
333                break;
334
335                case com.android.internal.R.drawable.ic_lockscreen_unlock_phantom:
336                case com.android.internal.R.drawable.ic_lockscreen_unlock:
337                    mCallback.goToUnlockScreen();
338                break;
339            }
340        }
341
342        private void launchActivity(Intent intent) {
343            intent.setFlags(
344                    Intent.FLAG_ACTIVITY_NEW_TASK
345                    | Intent.FLAG_ACTIVITY_SINGLE_TOP
346                    | Intent.FLAG_ACTIVITY_CLEAR_TOP);
347            try {
348                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
349            } catch (RemoteException e) {
350                Log.w(TAG, "can't dismiss keyguard on launch");
351            }
352            try {
353                mContext.startActivity(intent);
354            } catch (ActivityNotFoundException e) {
355                Log.w(TAG, "Activity not found for intent + " + intent.getAction());
356            }
357        }
358
359        public void onGrabbedStateChange(View v, int handle) {
360            // Don't poke the wake lock when returning to a state where the handle is
361            // not grabbed since that can happen when the system (instead of the user)
362            // cancels the grab.
363            if (handle != GlowPadView.OnTriggerListener.NO_HANDLE) {
364                mCallback.pokeWakelock();
365            }
366        }
367
368        public View getView() {
369            return mGlowPadView;
370        }
371
372        public void reset(boolean animate) {
373            mGlowPadView.reset(animate);
374        }
375
376        public void ping() {
377            mGlowPadView.ping();
378        }
379
380        public void setEnabled(int resourceId, boolean enabled) {
381            mGlowPadView.setEnableTarget(resourceId, enabled);
382        }
383
384        public int getTargetPosition(int resourceId) {
385            return mGlowPadView.getTargetPosition(resourceId);
386        }
387
388        public void cleanUp() {
389            mGlowPadView.setOnTriggerListener(null);
390        }
391
392        public void onFinishFinalAnimation() {
393
394        }
395    }
396
397    private void requestUnlockScreen() {
398        // Delay hiding lock screen long enough for animation to finish
399        postDelayed(new Runnable() {
400            public void run() {
401                mCallback.goToUnlockScreen();
402            }
403        }, WAIT_FOR_ANIMATION_TIMEOUT);
404    }
405
406    private void toggleRingMode() {
407        // toggle silent mode
408        mSilentMode = !mSilentMode;
409        if (mSilentMode) {
410            mAudioManager.setRingerMode(mHasVibrator
411                ? AudioManager.RINGER_MODE_VIBRATE
412                : AudioManager.RINGER_MODE_SILENT);
413        } else {
414            mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
415        }
416    }
417
418    /**
419     * In general, we enable unlocking the insecure key guard with the menu key. However, there are
420     * some cases where we wish to disable it, notably when the menu button placement or technology
421     * is prone to false positives.
422     *
423     * @return true if the menu key should be enabled
424     */
425    private boolean shouldEnableMenuKey() {
426        final Resources res = getResources();
427        final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
428        final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
429        final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
430        return !configDisabled || isTestHarness || fileOverride;
431    }
432
433    /**
434     * @param context Used to setup the view.
435     * @param configuration The current configuration. Used to use when selecting layout, etc.
436     * @param lockPatternUtils Used to know the state of the lock pattern settings.
437     * @param updateMonitor Used to register for updates on various keyguard related
438     *    state, and query the initial state at setup.
439     * @param callback Used to communicate back to the host keyguard view.
440     */
441    LockScreen(Context context, Configuration configuration, LockPatternUtils lockPatternUtils,
442            KeyguardUpdateMonitor updateMonitor,
443            KeyguardScreenCallback callback) {
444        super(context);
445        mLockPatternUtils = lockPatternUtils;
446        mUpdateMonitor = updateMonitor;
447        mCallback = callback;
448        mEnableMenuKeyInLockScreen = shouldEnableMenuKey();
449        mCreationOrientation = configuration.orientation;
450
451        if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
452            Log.v(TAG, "***** CREATING LOCK SCREEN", new RuntimeException());
453            Log.v(TAG, "Cur orient=" + mCreationOrientation
454                    + " res orient=" + context.getResources().getConfiguration().orientation);
455        }
456
457        final LayoutInflater inflater = LayoutInflater.from(context);
458        if (DBG) Log.v(TAG, "Creation orientation = " + mCreationOrientation);
459        if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) {
460            inflater.inflate(R.layout.keyguard_screen_tab_unlock, this, true);
461        } else {
462            inflater.inflate(R.layout.keyguard_screen_tab_unlock_land, this, true);
463        }
464
465        mStatusViewManager = new KeyguardStatusViewManager(this, mUpdateMonitor, mLockPatternUtils,
466                mCallback, false);
467
468        setFocusable(true);
469        setFocusableInTouchMode(true);
470        setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
471
472        Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
473        mHasVibrator = vibrator == null ? false : vibrator.hasVibrator();
474        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
475        mSilentMode = isSilentMode();
476        mUnlockWidget = findViewById(R.id.unlock_widget);
477        mUnlockWidgetMethods = createUnlockMethods(mUnlockWidget);
478
479        if (DBG) Log.v(TAG, "*** LockScreen accel is "
480                + (mUnlockWidget.isHardwareAccelerated() ? "on":"off"));
481    }
482
483    private UnlockWidgetCommonMethods createUnlockMethods(View unlockWidget) {
484        if (unlockWidget instanceof SlidingTab) {
485            SlidingTab slidingTabView = (SlidingTab) unlockWidget;
486            slidingTabView.setHoldAfterTrigger(true, false);
487            slidingTabView.setLeftHintText(R.string.lockscreen_unlock_label);
488            slidingTabView.setLeftTabResources(
489                    R.drawable.ic_jog_dial_unlock,
490                    R.drawable.jog_tab_target_green,
491                    R.drawable.jog_tab_bar_left_unlock,
492                    R.drawable.jog_tab_left_unlock);
493            SlidingTabMethods slidingTabMethods = new SlidingTabMethods(slidingTabView);
494            slidingTabView.setOnTriggerListener(slidingTabMethods);
495            return slidingTabMethods;
496        } else if (unlockWidget instanceof WaveView) {
497            WaveView waveView = (WaveView) unlockWidget;
498            WaveViewMethods waveViewMethods = new WaveViewMethods(waveView);
499            waveView.setOnTriggerListener(waveViewMethods);
500            return waveViewMethods;
501        } else if (unlockWidget instanceof GlowPadView) {
502            GlowPadView glowPadView = (GlowPadView) unlockWidget;
503            GlowPadViewMethods glowPadViewMethods = new GlowPadViewMethods(glowPadView);
504            glowPadView.setOnTriggerListener(glowPadViewMethods);
505            return glowPadViewMethods;
506        } else {
507            throw new IllegalStateException("Unrecognized unlock widget: " + unlockWidget);
508        }
509    }
510
511    private void updateTargets() {
512        boolean disabledByAdmin = mLockPatternUtils.getDevicePolicyManager()
513                .getCameraDisabled(null);
514        boolean disabledBySimState = mUpdateMonitor.isSimLocked();
515        boolean cameraTargetPresent = (mUnlockWidgetMethods instanceof GlowPadViewMethods)
516                ? ((GlowPadViewMethods) mUnlockWidgetMethods)
517                        .isTargetPresent(com.android.internal.R.drawable.ic_lockscreen_camera)
518                        : false;
519        boolean searchTargetPresent = (mUnlockWidgetMethods instanceof GlowPadViewMethods)
520                ? ((GlowPadViewMethods) mUnlockWidgetMethods)
521                        .isTargetPresent(com.android.internal.R.drawable.ic_action_assist_generic)
522                        : false;
523
524        if (disabledByAdmin) {
525            Log.v(TAG, "Camera disabled by Device Policy");
526        } else if (disabledBySimState) {
527            Log.v(TAG, "Camera disabled by Sim State");
528        }
529        boolean searchActionAvailable = SearchManager.getAssistIntent(mContext) != null;
530        mCameraDisabled = disabledByAdmin || disabledBySimState || !cameraTargetPresent;
531        mSearchDisabled = disabledBySimState || !searchActionAvailable || !searchTargetPresent;
532        mUnlockWidgetMethods.updateResources();
533    }
534
535    private boolean isSilentMode() {
536        return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
537    }
538
539    @Override
540    public boolean onKeyDown(int keyCode, KeyEvent event) {
541        if (keyCode == KeyEvent.KEYCODE_MENU && mEnableMenuKeyInLockScreen) {
542            mCallback.goToUnlockScreen();
543        }
544        return false;
545    }
546
547    void updateConfiguration() {
548        Configuration newConfig = getResources().getConfiguration();
549        if (newConfig.orientation != mCreationOrientation) {
550            mCallback.recreateMe(newConfig);
551        }
552    }
553
554    @Override
555    protected void onAttachedToWindow() {
556        super.onAttachedToWindow();
557        if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
558            Log.v(TAG, "***** LOCK ATTACHED TO WINDOW");
559            Log.v(TAG, "Cur orient=" + mCreationOrientation
560                    + ", new config=" + getResources().getConfiguration());
561        }
562        updateConfiguration();
563    }
564
565    /** {@inheritDoc} */
566    @Override
567    protected void onConfigurationChanged(Configuration newConfig) {
568        super.onConfigurationChanged(newConfig);
569        if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
570            Log.w(TAG, "***** LOCK CONFIG CHANGING", new RuntimeException());
571            Log.v(TAG, "Cur orient=" + mCreationOrientation
572                    + ", new config=" + newConfig);
573        }
574        updateConfiguration();
575    }
576
577    /** {@inheritDoc} */
578    public boolean needsInput() {
579        return false;
580    }
581
582    /** {@inheritDoc} */
583    public void onPause() {
584        mUpdateMonitor.removeCallback(mInfoCallback);
585        mUpdateMonitor.removeCallback(mSimStateCallback);
586        mStatusViewManager.onPause();
587        mUnlockWidgetMethods.reset(false);
588    }
589
590    private final Runnable mOnResumePing = new Runnable() {
591        public void run() {
592            mUnlockWidgetMethods.ping();
593        }
594    };
595
596    /** {@inheritDoc} */
597    public void onResume() {
598        // We don't want to show the camera target if SIM state prevents us from
599        // launching the camera. So watch for SIM changes...
600        mUpdateMonitor.registerSimStateCallback(mSimStateCallback);
601        mUpdateMonitor.registerInfoCallback(mInfoCallback);
602
603        mStatusViewManager.onResume();
604        postDelayed(mOnResumePing, ON_RESUME_PING_DELAY);
605    }
606
607    /** {@inheritDoc} */
608    public void cleanUp() {
609        mUpdateMonitor.removeCallback(mInfoCallback); // this must be first
610        mUpdateMonitor.removeCallback(mSimStateCallback);
611        mUnlockWidgetMethods.cleanUp();
612        mLockPatternUtils = null;
613        mUpdateMonitor = null;
614        mCallback = null;
615    }
616}
617