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 
17 package com.android.camera.util;
18 
19 import android.app.Activity;
20 import android.app.KeyguardManager;
21 import android.content.Intent;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.SystemClock;
25 
26 import com.android.camera.debug.Log;
27 import 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  */
56 public 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
onNewIntent(Intent intent)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
onCreate(Bundle bundle)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
onStart()119     protected final void onStart() {
120         logLifecycle("onStart", true);
121         onStartTasks();
122         super.onStart();
123         logLifecycle("onStart", false);
124     }
125 
126     @Override
onResume()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
onPause()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
onStop()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
onRestart()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
onDestroy()195     protected final void onDestroy() {
196         logLifecycle("onDestroy", true);
197         onDestroyTasks();
198         super.onDestroy();
199         logLifecycle("onDestroy", false);
200     }
201 
logLifecycle(String methodName, boolean start)202     private void logLifecycle(String methodName, boolean start) {
203         String prefix = start ? "START" : "END";
204         Log.v(TAG, prefix + " " + methodName + ": Activity = " + toString());
205     }
206 
isKeyguardLocked()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 
isKeyguardSecure()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      */
onNewIntentTasks(Intent newIntent)230     protected void onNewIntentTasks(Intent newIntent) {
231     }
232 
233     /**
234      * Subclasses should override this in place of {@link Activity#onCreate}.
235      */
onCreateTasks(Bundle savedInstanceState)236     protected void onCreateTasks(Bundle savedInstanceState) {
237     }
238 
239     /**
240      * Subclasses should override this in place of {@link Activity#onStart}.
241      */
onStartTasks()242     protected void onStartTasks() {
243     }
244 
245     /**
246      * Subclasses should override this in place of {@link Activity#onResume}.
247      */
onResumeTasks()248     protected void onResumeTasks() {
249     }
250 
251     /**
252      * Subclasses should override this in place of {@link Activity#onPause}.
253      */
onPauseTasks()254     protected void onPauseTasks() {
255     }
256 
257     /**
258      * Subclasses should override this in place of {@link Activity#onStop}.
259      */
onStopTasks()260     protected void onStopTasks() {
261     }
262 
263     /**
264      * Subclasses should override this in place of {@link Activity#onDestroy}.
265      */
onDestroyTasks()266     protected void onDestroyTasks() {
267     }
268 }
269