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.camera.util;
18
19import android.app.Activity;
20import android.app.KeyguardManager;
21import android.content.Intent;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.SystemClock;
25
26import com.android.camera.debug.Log;
27import javax.annotation.Nullable;
28
29/**
30 * Workaround for lockscreen double-onResume() bug:
31 * <p>
32 * We track 3 startup situations:
33 * <ul>
34 * <li>Normal startup -- e.g. from GEL.</li>
35 * <li>Secure lock screen startup -- e.g. with a keycode.</li>
36 * <li>Non-secure lock screen startup -- e.g. with just a swipe.</li>
37 * </ul>
38 * The KeyguardManager service can be queried to determine which state we are in.
39 * If started from the lock screen, the activity may be quickly started,
40 * resumed, paused, stopped, and then started and resumed again. This is
41 * problematic for launch time from the lock screen because we typically open the
42 * camera in onResume() and close it in onPause(). These camera operations take
43 * a long time to complete. To workaround it, this class filters out
44 * high-frequency onResume()->onPause() sequences if the KeyguardManager
45 * indicates that we have started from the lock screen.
46 * </p>
47 * <p>
48 * Subclasses should override the appropriate on[Create|Start...]Tasks() method
49 * in place of the original.
50 * </p>
51 * <p>
52 * Sequences of onResume() followed quickly by onPause(), when the activity is
53 * started from a lockscreen will result in a quick no-op.<br>
54 * </p>
55 */
56public abstract class QuickActivity extends Activity {
57    private static final Log.Tag TAG = new Log.Tag("QuickActivity");
58
59    /** onResume tasks delay from secure lockscreen. */
60    private static final long ON_RESUME_DELAY_SECURE_MILLIS = 30;
61    /** onResume tasks delay from non-secure lockscreen. */
62    private static final long ON_RESUME_DELAY_NON_SECURE_MILLIS = 15;
63
64    /** A reference to the main handler on which to run lifecycle methods. */
65    private Handler mMainHandler;
66
67    /**
68     * True if onResume tasks have been skipped, and made false again once they
69     * are executed within the onResume() method or from a delayed Runnable.
70     */
71    private boolean mSkippedFirstOnResume = false;
72
73    /** When application execution started in SystemClock.elapsedRealtimeNanos(). */
74    protected long mExecutionStartNanoTime = 0;
75    /** Was this session started with onCreate(). */
76    protected boolean mStartupOnCreate = false;
77
78    /** Handle to Keyguard service. */
79    @Nullable
80    private KeyguardManager mKeyguardManager = null;
81    /**
82     * A runnable for deferring tasks to be performed in onResume() if starting
83     * from the lockscreen.
84     */
85    private final Runnable mOnResumeTasks = new Runnable() {
86            @Override
87        public void run() {
88            if (mSkippedFirstOnResume) {
89                Log.v(TAG, "delayed Runnable --> onResumeTasks()");
90                // Doing the tasks, can set to false.
91                mSkippedFirstOnResume = false;
92                onResumeTasks();
93            }
94        }
95    };
96
97    @Override
98    protected final void onNewIntent(Intent intent) {
99        logLifecycle("onNewIntent", true);
100        Log.v(TAG, "Intent Action = " + intent.getAction());
101        setIntent(intent);
102        super.onNewIntent(intent);
103        onNewIntentTasks(intent);
104        logLifecycle("onNewIntent", false);
105    }
106
107    @Override
108    protected final void onCreate(Bundle bundle) {
109        mExecutionStartNanoTime = SystemClock.elapsedRealtimeNanos();
110        logLifecycle("onCreate", true);
111        mStartupOnCreate = true;
112        super.onCreate(bundle);
113        mMainHandler = new Handler(getMainLooper());
114        onCreateTasks(bundle);
115        logLifecycle("onCreate", false);
116    }
117
118    @Override
119    protected final void onStart() {
120        logLifecycle("onStart", true);
121        onStartTasks();
122        super.onStart();
123        logLifecycle("onStart", false);
124    }
125
126    @Override
127    protected final void onResume() {
128        logLifecycle("onResume", true);
129
130        // For lockscreen launch, there are two possible flows:
131        // 1. onPause() does not occur before mOnResumeTasks is executed:
132        //      Runnable mOnResumeTasks sets mSkippedFirstOnResume to false
133        // 2. onPause() occurs within ON_RESUME_DELAY_*_MILLIS:
134        //     a. Runnable mOnResumeTasks is removed
135        //     b. onPauseTasks() is skipped, mSkippedFirstOnResume remains true
136        //     c. next onResume() will immediately execute onResumeTasks()
137        //        and set mSkippedFirstOnResume to false
138
139        Log.v(TAG, "onResume(): isKeyguardLocked() = " + isKeyguardLocked());
140        mMainHandler.removeCallbacks(mOnResumeTasks);
141        if (isKeyguardLocked() && mSkippedFirstOnResume == false) {
142            // Skipping onResumeTasks; set to true.
143            mSkippedFirstOnResume = true;
144            long delay = isKeyguardSecure() ? ON_RESUME_DELAY_SECURE_MILLIS :
145                    ON_RESUME_DELAY_NON_SECURE_MILLIS;
146            Log.v(TAG, "onResume() --> postDelayed(mOnResumeTasks," + delay + ")");
147            mMainHandler.postDelayed(mOnResumeTasks, delay);
148        } else {
149            Log.v(TAG, "onResume --> onResumeTasks()");
150            // Doing the tasks, can set to false.
151            mSkippedFirstOnResume = false;
152            onResumeTasks();
153        }
154        super.onResume();
155        logLifecycle("onResume", false);
156    }
157
158    @Override
159    protected final void onPause() {
160        logLifecycle("onPause", true);
161        mMainHandler.removeCallbacks(mOnResumeTasks);
162        // Only run onPauseTasks if we have not skipped onResumeTasks in a
163        // first call to onResume.  If we did skip onResumeTasks (note: we
164        // just killed any delayed Runnable), we also skip onPauseTasks to
165        // adhere to lifecycle state machine.
166        if (mSkippedFirstOnResume == false) {
167            Log.v(TAG, "onPause --> onPauseTasks()");
168            onPauseTasks();
169        }
170        super.onPause();
171        mStartupOnCreate = false;
172        logLifecycle("onPause", false);
173    }
174
175    @Override
176    protected final void onStop() {
177        if (isChangingConfigurations()) {
178            Log.v(TAG, "changing configurations");
179        }
180        logLifecycle("onStop", true);
181        onStopTasks();
182        super.onStop();
183        logLifecycle("onStop", false);
184    }
185
186    @Override
187    protected final void onRestart() {
188        logLifecycle("onRestart", true);
189        super.onRestart();
190        // TODO Support onRestartTasks() and handle the workaround for that too.
191        logLifecycle("onRestart", false);
192    }
193
194    @Override
195    protected final void onDestroy() {
196        logLifecycle("onDestroy", true);
197        onDestroyTasks();
198        super.onDestroy();
199        logLifecycle("onDestroy", false);
200    }
201
202    private void logLifecycle(String methodName, boolean start) {
203        String prefix = start ? "START" : "END";
204        Log.v(TAG, prefix + " " + methodName + ": Activity = " + toString());
205    }
206
207    protected boolean isKeyguardLocked() {
208        if (mKeyguardManager == null) {
209            mKeyguardManager = AndroidServices.instance().provideKeyguardManager();
210        }
211        if (mKeyguardManager != null) {
212            return mKeyguardManager.isKeyguardLocked();
213        }
214        return false;
215    }
216
217    protected boolean isKeyguardSecure() {
218        if (mKeyguardManager == null) {
219            mKeyguardManager = AndroidServices.instance().provideKeyguardManager();
220        }
221        if (mKeyguardManager != null) {
222            return mKeyguardManager.isKeyguardSecure();
223        }
224        return false;
225    }
226
227    /**
228     * Subclasses should override this in place of {@link Activity#onNewIntent}.
229     */
230    protected void onNewIntentTasks(Intent newIntent) {
231    }
232
233    /**
234     * Subclasses should override this in place of {@link Activity#onCreate}.
235     */
236    protected void onCreateTasks(Bundle savedInstanceState) {
237    }
238
239    /**
240     * Subclasses should override this in place of {@link Activity#onStart}.
241     */
242    protected void onStartTasks() {
243    }
244
245    /**
246     * Subclasses should override this in place of {@link Activity#onResume}.
247     */
248    protected void onResumeTasks() {
249    }
250
251    /**
252     * Subclasses should override this in place of {@link Activity#onPause}.
253     */
254    protected void onPauseTasks() {
255    }
256
257    /**
258     * Subclasses should override this in place of {@link Activity#onStop}.
259     */
260    protected void onStopTasks() {
261    }
262
263    /**
264     * Subclasses should override this in place of {@link Activity#onDestroy}.
265     */
266    protected void onDestroyTasks() {
267    }
268}
269