1/*
2 * Copyright (C) 2014 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.phone;
18
19import android.content.Context;
20import android.view.Choreographer;
21import android.view.KeyEvent;
22import android.view.LayoutInflater;
23import android.view.View;
24import android.view.ViewGroup;
25import android.view.accessibility.AccessibilityEvent;
26
27import com.android.internal.widget.LockPatternUtils;
28import com.android.keyguard.KeyguardHostView;
29import com.android.keyguard.KeyguardSecurityView;
30import com.android.keyguard.R;
31import com.android.keyguard.ViewMediatorCallback;
32import com.android.systemui.DejankUtils;
33
34import static com.android.keyguard.KeyguardHostView.OnDismissAction;
35import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
36
37/**
38 * A class which manages the bouncer on the lockscreen.
39 */
40public class KeyguardBouncer {
41
42    private Context mContext;
43    private ViewMediatorCallback mCallback;
44    private LockPatternUtils mLockPatternUtils;
45    private ViewGroup mContainer;
46    private StatusBarWindowManager mWindowManager;
47    private KeyguardHostView mKeyguardView;
48    private ViewGroup mRoot;
49    private boolean mShowingSoon;
50    private int mBouncerPromptReason;
51
52    public KeyguardBouncer(Context context, ViewMediatorCallback callback,
53            LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager,
54            ViewGroup container) {
55        mContext = context;
56        mCallback = callback;
57        mLockPatternUtils = lockPatternUtils;
58        mContainer = container;
59        mWindowManager = windowManager;
60    }
61
62    public void show(boolean resetSecuritySelection) {
63        ensureView();
64        if (resetSecuritySelection) {
65            // showPrimarySecurityScreen() updates the current security method. This is needed in
66            // case we are already showing and the current security method changed.
67            mKeyguardView.showPrimarySecurityScreen();
68        }
69        if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) {
70            return;
71        }
72
73        // Try to dismiss the Keyguard. If no security pattern is set, this will dismiss the whole
74        // Keyguard. If we need to authenticate, show the bouncer.
75        if (!mKeyguardView.dismiss()) {
76            mShowingSoon = true;
77
78            // Split up the work over multiple frames.
79            DejankUtils.postAfterTraversal(mShowRunnable);
80        }
81    }
82
83    private final Runnable mShowRunnable = new Runnable() {
84        @Override
85        public void run() {
86            mRoot.setVisibility(View.VISIBLE);
87            mKeyguardView.onResume();
88            showPromptReason(mBouncerPromptReason);
89            mKeyguardView.startAppearAnimation();
90            mShowingSoon = false;
91            mKeyguardView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
92        }
93    };
94
95    /**
96     * Show a string explaining why the security view needs to be solved.
97     *
98     * @param reason a flag indicating which string should be shown, see
99     *               {@link KeyguardSecurityView#PROMPT_REASON_NONE}
100     *               and {@link KeyguardSecurityView#PROMPT_REASON_RESTART}
101     */
102    public void showPromptReason(int reason) {
103        mKeyguardView.showPromptReason(reason);
104    }
105
106    private void cancelShowRunnable() {
107        DejankUtils.removeCallbacks(mShowRunnable);
108        mShowingSoon = false;
109    }
110
111    public void showWithDismissAction(OnDismissAction r, Runnable cancelAction) {
112        ensureView();
113        mKeyguardView.setOnDismissAction(r, cancelAction);
114        show(false /* resetSecuritySelection */);
115    }
116
117    public void hide(boolean destroyView) {
118        cancelShowRunnable();
119         if (mKeyguardView != null) {
120            mKeyguardView.cancelDismissAction();
121            mKeyguardView.cleanUp();
122        }
123        if (destroyView) {
124            removeView();
125        } else if (mRoot != null) {
126            mRoot.setVisibility(View.INVISIBLE);
127        }
128    }
129
130    /**
131     * See {@link StatusBarKeyguardViewManager#startPreHideAnimation}.
132     */
133    public void startPreHideAnimation(Runnable runnable) {
134        if (mKeyguardView != null) {
135            mKeyguardView.startDisappearAnimation(runnable);
136        } else if (runnable != null) {
137            runnable.run();
138        }
139    }
140
141    /**
142     * Reset the state of the view.
143     */
144    public void reset() {
145        cancelShowRunnable();
146        inflateView();
147    }
148
149    public void onScreenTurnedOff() {
150        if (mKeyguardView != null && mRoot != null && mRoot.getVisibility() == View.VISIBLE) {
151            mKeyguardView.onPause();
152        }
153    }
154
155    public boolean isShowing() {
156        return mShowingSoon || (mRoot != null && mRoot.getVisibility() == View.VISIBLE);
157    }
158
159    public void prepare() {
160        boolean wasInitialized = mRoot != null;
161        ensureView();
162        if (wasInitialized) {
163            mKeyguardView.showPrimarySecurityScreen();
164        }
165        mBouncerPromptReason = mCallback.getBouncerPromptReason();
166    }
167
168    private void ensureView() {
169        if (mRoot == null) {
170            inflateView();
171        }
172    }
173
174    private void inflateView() {
175        removeView();
176        mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
177        mKeyguardView = (KeyguardHostView) mRoot.findViewById(R.id.keyguard_host_view);
178        mKeyguardView.setLockPatternUtils(mLockPatternUtils);
179        mKeyguardView.setViewMediatorCallback(mCallback);
180        mContainer.addView(mRoot, mContainer.getChildCount());
181        mRoot.setVisibility(View.INVISIBLE);
182        mRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME);
183    }
184
185    private void removeView() {
186        if (mRoot != null && mRoot.getParent() == mContainer) {
187            mContainer.removeView(mRoot);
188            mRoot = null;
189        }
190    }
191
192    public boolean onBackPressed() {
193        return mKeyguardView != null && mKeyguardView.handleBackKey();
194    }
195
196    /**
197     * @return True if and only if the security method should be shown before showing the
198     * notifications on Keyguard, like SIM PIN/PUK.
199     */
200    public boolean needsFullscreenBouncer() {
201        ensureView();
202        if (mKeyguardView != null) {
203            SecurityMode mode = mKeyguardView.getSecurityMode();
204            return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
205        }
206        return false;
207    }
208
209    /**
210     * Like {@link #needsFullscreenBouncer}, but uses the currently visible security method, which
211     * makes this method much faster.
212     */
213    public boolean isFullscreenBouncer() {
214        if (mKeyguardView != null) {
215            SecurityMode mode = mKeyguardView.getCurrentSecurityMode();
216            return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
217        }
218        return false;
219    }
220
221    /**
222     * WARNING: This method might cause Binder calls.
223     */
224    public boolean isSecure() {
225        return mKeyguardView == null || mKeyguardView.getSecurityMode() != SecurityMode.None;
226    }
227
228    public boolean onMenuPressed() {
229        ensureView();
230        if (mKeyguardView.handleMenuKey()) {
231
232            // We need to show it in case it is secure. If not, it will get dismissed in any case.
233            mRoot.setVisibility(View.VISIBLE);
234            mKeyguardView.requestFocus();
235            mKeyguardView.onResume();
236            return true;
237        } else {
238            return false;
239        }
240    }
241
242    public boolean interceptMediaKey(KeyEvent event) {
243        ensureView();
244        return mKeyguardView.interceptMediaKey(event);
245    }
246
247    public void notifyKeyguardAuthenticated() {
248        ensureView();
249        mKeyguardView.finish();
250    }
251}
252