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