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 */
16package com.android.keyguard;
17
18import android.content.Context;
19import android.graphics.drawable.Drawable;
20import android.os.PowerManager;
21import android.os.RemoteException;
22import android.os.ServiceManager;
23import android.telephony.TelephonyManager;
24import android.util.AttributeSet;
25import android.util.Log;
26import android.view.IRotationWatcher;
27import android.view.IWindowManager;
28import android.view.View;
29import android.widget.ImageButton;
30import android.widget.LinearLayout;
31
32import com.android.internal.widget.LockPatternUtils;
33
34import java.lang.Math;
35
36public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecurityView {
37
38    private static final String TAG = "FULKeyguardFaceUnlockView";
39    private static final boolean DEBUG = false;
40    private KeyguardSecurityCallback mKeyguardSecurityCallback;
41    private LockPatternUtils mLockPatternUtils;
42    private BiometricSensorUnlock mBiometricUnlock;
43    private View mFaceUnlockAreaView;
44    private ImageButton mCancelButton;
45    private SecurityMessageDisplay mSecurityMessageDisplay;
46    private View mEcaView;
47    private Drawable mBouncerFrame;
48
49    private boolean mIsShowing = false;
50    private final Object mIsShowingLock = new Object();
51
52    private int mLastRotation;
53    private boolean mWatchingRotation;
54    private final IWindowManager mWindowManager =
55            IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
56
57    private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() {
58        public void onRotationChanged(int rotation) {
59            if (DEBUG) Log.d(TAG, "onRotationChanged(): " + mLastRotation + "->" + rotation);
60
61            // If the difference between the new rotation value and the previous rotation value is
62            // equal to 2, the rotation change was 180 degrees.  This stops the biometric unlock
63            // and starts it in the new position.  This is not performed for 90 degree rotations
64            // since a 90 degree rotation is a configuration change, which takes care of this for
65            // us.
66            if (Math.abs(rotation - mLastRotation) == 2) {
67                if (mBiometricUnlock != null) {
68                    mBiometricUnlock.stop();
69                    maybeStartBiometricUnlock();
70                }
71            }
72            mLastRotation = rotation;
73        }
74    };
75
76    public KeyguardFaceUnlockView(Context context) {
77        this(context, null);
78    }
79
80    public KeyguardFaceUnlockView(Context context, AttributeSet attrs) {
81        super(context, attrs);
82    }
83
84    @Override
85    protected void onFinishInflate() {
86        super.onFinishInflate();
87
88        initializeBiometricUnlockView();
89
90        mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
91        mEcaView = findViewById(R.id.keyguard_selector_fade_container);
92        View bouncerFrameView = findViewById(R.id.keyguard_bouncer_frame);
93        if (bouncerFrameView != null) {
94            mBouncerFrame = bouncerFrameView.getBackground();
95        }
96    }
97
98    @Override
99    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
100        mKeyguardSecurityCallback = callback;
101        // TODO: formalize this in the interface or factor it out
102        ((FaceUnlock)mBiometricUnlock).setKeyguardCallback(callback);
103    }
104
105    @Override
106    public void setLockPatternUtils(LockPatternUtils utils) {
107        mLockPatternUtils = utils;
108    }
109
110    @Override
111    public void reset() {
112
113    }
114
115    @Override
116    public void onDetachedFromWindow() {
117        if (DEBUG) Log.d(TAG, "onDetachedFromWindow()");
118        if (mBiometricUnlock != null) {
119            mBiometricUnlock.stop();
120        }
121        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateCallback);
122        if (mWatchingRotation) {
123            try {
124                mWindowManager.removeRotationWatcher(mRotationWatcher);
125                mWatchingRotation = false;
126            } catch (RemoteException e) {
127                Log.e(TAG, "Remote exception when removing rotation watcher");
128            }
129        }
130    }
131
132    @Override
133    public void onPause() {
134        if (DEBUG) Log.d(TAG, "onPause()");
135        if (mBiometricUnlock != null) {
136            mBiometricUnlock.stop();
137        }
138        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateCallback);
139        if (mWatchingRotation) {
140            try {
141                mWindowManager.removeRotationWatcher(mRotationWatcher);
142                mWatchingRotation = false;
143            } catch (RemoteException e) {
144                Log.e(TAG, "Remote exception when removing rotation watcher");
145            }
146        }
147    }
148
149    @Override
150    public void onResume(int reason) {
151        if (DEBUG) Log.d(TAG, "onResume()");
152        mIsShowing = KeyguardUpdateMonitor.getInstance(mContext).isKeyguardVisible();
153        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
154          maybeStartBiometricUnlock();
155        }
156        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateCallback);
157
158        // Registers a callback which handles stopping the biometric unlock and restarting it in
159        // the new position for a 180 degree rotation change.
160        if (!mWatchingRotation) {
161            try {
162                mLastRotation = mWindowManager.watchRotation(mRotationWatcher);
163                mWatchingRotation = true;
164            } catch (RemoteException e) {
165                Log.e(TAG, "Remote exception when adding rotation watcher");
166            }
167        }
168    }
169
170    @Override
171    public boolean needsInput() {
172        return false;
173    }
174
175    @Override
176    public KeyguardSecurityCallback getCallback() {
177        return mKeyguardSecurityCallback;
178    }
179
180    @Override
181    protected void onLayout(boolean changed, int l, int t, int r, int b) {
182        super.onLayout(changed, l, t, r, b);
183        mBiometricUnlock.initializeView(mFaceUnlockAreaView);
184    }
185
186    private void initializeBiometricUnlockView() {
187        if (DEBUG) Log.d(TAG, "initializeBiometricUnlockView()");
188        mFaceUnlockAreaView = findViewById(R.id.face_unlock_area_view);
189        if (mFaceUnlockAreaView != null) {
190            mBiometricUnlock = new FaceUnlock(mContext);
191
192            mCancelButton = (ImageButton) findViewById(R.id.face_unlock_cancel_button);
193            mCancelButton.setOnClickListener(new OnClickListener() {
194                @Override
195                public void onClick(View v) {
196                    mBiometricUnlock.stopAndShowBackup();
197                }
198            });
199        } else {
200            Log.w(TAG, "Couldn't find biometric unlock view");
201        }
202    }
203
204    /**
205     * Starts the biometric unlock if it should be started based on a number of factors.  If it
206     * should not be started, it either goes to the back up, or remains showing to prepare for
207     * it being started later.
208     */
209    private void maybeStartBiometricUnlock() {
210        if (DEBUG) Log.d(TAG, "maybeStartBiometricUnlock()");
211        if (mBiometricUnlock != null) {
212            KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
213            final boolean backupIsTimedOut = (
214                    monitor.getFailedUnlockAttempts() >=
215                    LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT);
216            PowerManager powerManager = (PowerManager) mContext.getSystemService(
217                    Context.POWER_SERVICE);
218
219            boolean isShowing;
220            synchronized(mIsShowingLock) {
221                isShowing = mIsShowing;
222            }
223
224            // Don't start it if the screen is off or if it's not showing, but keep this view up
225            // because we want it here and ready for when the screen turns on or when it does start
226            // showing.
227            if (!powerManager.isScreenOn() || !isShowing) {
228                mBiometricUnlock.stop(); // It shouldn't be running but calling this can't hurt.
229                return;
230            }
231
232            // Although these same conditions are handled in KeyguardSecurityModel, they are still
233            // necessary here.  When a tablet is rotated 90 degrees, a configuration change is
234            // triggered and everything is torn down and reconstructed.  That means
235            // KeyguardSecurityModel gets a chance to take care of the logic and doesn't even
236            // reconstruct KeyguardFaceUnlockView if the biometric unlock should be suppressed.
237            // However, for a 180 degree rotation, no configuration change is triggered, so only
238            // the logic here is capable of suppressing Face Unlock.
239            if (monitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE
240                    && monitor.isAlternateUnlockEnabled()
241                    && !monitor.getMaxBiometricUnlockAttemptsReached()
242                    && !backupIsTimedOut) {
243                mBiometricUnlock.start();
244            } else {
245                mBiometricUnlock.stopAndShowBackup();
246            }
247        }
248    }
249
250    KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
251        // We need to stop the biometric unlock when a phone call comes in
252        @Override
253        public void onPhoneStateChanged(int phoneState) {
254            if (DEBUG) Log.d(TAG, "onPhoneStateChanged(" + phoneState + ")");
255            if (phoneState == TelephonyManager.CALL_STATE_RINGING) {
256                if (mBiometricUnlock != null) {
257                    mBiometricUnlock.stopAndShowBackup();
258                }
259            }
260        }
261
262        @Override
263        public void onUserSwitching(int userId) {
264            if (DEBUG) Log.d(TAG, "onUserSwitched(" + userId + ")");
265            if (mBiometricUnlock != null) {
266                mBiometricUnlock.stop();
267            }
268            // No longer required; static value set by KeyguardViewMediator
269            // mLockPatternUtils.setCurrentUser(userId);
270        }
271
272        @Override
273        public void onUserSwitchComplete(int userId) {
274            if (DEBUG) Log.d(TAG, "onUserSwitchComplete(" + userId + ")");
275            if (mBiometricUnlock != null) {
276                maybeStartBiometricUnlock();
277            }
278        }
279
280        @Override
281        public void onKeyguardVisibilityChanged(boolean showing) {
282            if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")");
283            boolean wasShowing = false;
284            synchronized(mIsShowingLock) {
285                wasShowing = mIsShowing;
286                mIsShowing = showing;
287            }
288            PowerManager powerManager = (PowerManager) mContext.getSystemService(
289                    Context.POWER_SERVICE);
290            if (mBiometricUnlock != null) {
291                if (!showing && wasShowing) {
292                    mBiometricUnlock.stop();
293                } else if (showing && powerManager.isScreenOn() && !wasShowing) {
294                    maybeStartBiometricUnlock();
295                }
296            }
297        }
298
299        @Override
300        public void onEmergencyCallAction() {
301            if (mBiometricUnlock != null) {
302                mBiometricUnlock.stop();
303            }
304        }
305    };
306
307    @Override
308    public void showUsabilityHint() {
309    }
310
311    @Override
312    public void showBouncer(int duration) {
313        KeyguardSecurityViewHelper.
314                showBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
315    }
316
317    @Override
318    public void hideBouncer(int duration) {
319        KeyguardSecurityViewHelper.
320                hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
321    }
322
323}
324