/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera.util;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import com.android.camera.debug.Log;
import javax.annotation.Nullable;
/**
* Workaround for lockscreen double-onResume() bug:
*
* We track 3 startup situations:
*
* - Normal startup -- e.g. from GEL.
* - Secure lock screen startup -- e.g. with a keycode.
* - Non-secure lock screen startup -- e.g. with just a swipe.
*
* The KeyguardManager service can be queried to determine which state we are in.
* If started from the lock screen, the activity may be quickly started,
* resumed, paused, stopped, and then started and resumed again. This is
* problematic for launch time from the lock screen because we typically open the
* camera in onResume() and close it in onPause(). These camera operations take
* a long time to complete. To workaround it, this class filters out
* high-frequency onResume()->onPause() sequences if the KeyguardManager
* indicates that we have started from the lock screen.
*
*
* Subclasses should override the appropriate on[Create|Start...]Tasks() method
* in place of the original.
*
*
* Sequences of onResume() followed quickly by onPause(), when the activity is
* started from a lockscreen will result in a quick no-op.
*
*/
public abstract class QuickActivity extends Activity {
private static final Log.Tag TAG = new Log.Tag("QuickActivity");
/** onResume tasks delay from secure lockscreen. */
private static final long ON_RESUME_DELAY_SECURE_MILLIS = 30;
/** onResume tasks delay from non-secure lockscreen. */
private static final long ON_RESUME_DELAY_NON_SECURE_MILLIS = 15;
/** A reference to the main handler on which to run lifecycle methods. */
private Handler mMainHandler;
/**
* True if onResume tasks have been skipped, and made false again once they
* are executed within the onResume() method or from a delayed Runnable.
*/
private boolean mSkippedFirstOnResume = false;
/** When application execution started in SystemClock.elapsedRealtimeNanos(). */
protected long mExecutionStartNanoTime = 0;
/** Was this session started with onCreate(). */
protected boolean mStartupOnCreate = false;
/** Handle to Keyguard service. */
@Nullable
private KeyguardManager mKeyguardManager = null;
/**
* A runnable for deferring tasks to be performed in onResume() if starting
* from the lockscreen.
*/
private final Runnable mOnResumeTasks = new Runnable() {
@Override
public void run() {
if (mSkippedFirstOnResume) {
Log.v(TAG, "delayed Runnable --> onResumeTasks()");
// Doing the tasks, can set to false.
mSkippedFirstOnResume = false;
onResumeTasks();
}
}
};
@Override
protected final void onNewIntent(Intent intent) {
logLifecycle("onNewIntent", true);
Log.v(TAG, "Intent Action = " + intent.getAction());
setIntent(intent);
super.onNewIntent(intent);
onNewIntentTasks(intent);
logLifecycle("onNewIntent", false);
}
@Override
protected final void onCreate(Bundle bundle) {
mExecutionStartNanoTime = SystemClock.elapsedRealtimeNanos();
logLifecycle("onCreate", true);
mStartupOnCreate = true;
super.onCreate(bundle);
mMainHandler = new Handler(getMainLooper());
onCreateTasks(bundle);
logLifecycle("onCreate", false);
}
@Override
protected final void onStart() {
logLifecycle("onStart", true);
onStartTasks();
super.onStart();
logLifecycle("onStart", false);
}
@Override
protected final void onResume() {
logLifecycle("onResume", true);
// For lockscreen launch, there are two possible flows:
// 1. onPause() does not occur before mOnResumeTasks is executed:
// Runnable mOnResumeTasks sets mSkippedFirstOnResume to false
// 2. onPause() occurs within ON_RESUME_DELAY_*_MILLIS:
// a. Runnable mOnResumeTasks is removed
// b. onPauseTasks() is skipped, mSkippedFirstOnResume remains true
// c. next onResume() will immediately execute onResumeTasks()
// and set mSkippedFirstOnResume to false
Log.v(TAG, "onResume(): isKeyguardLocked() = " + isKeyguardLocked());
mMainHandler.removeCallbacks(mOnResumeTasks);
if (isKeyguardLocked() && mSkippedFirstOnResume == false) {
// Skipping onResumeTasks; set to true.
mSkippedFirstOnResume = true;
long delay = isKeyguardSecure() ? ON_RESUME_DELAY_SECURE_MILLIS :
ON_RESUME_DELAY_NON_SECURE_MILLIS;
Log.v(TAG, "onResume() --> postDelayed(mOnResumeTasks," + delay + ")");
mMainHandler.postDelayed(mOnResumeTasks, delay);
} else {
Log.v(TAG, "onResume --> onResumeTasks()");
// Doing the tasks, can set to false.
mSkippedFirstOnResume = false;
onResumeTasks();
}
super.onResume();
logLifecycle("onResume", false);
}
@Override
protected final void onPause() {
logLifecycle("onPause", true);
mMainHandler.removeCallbacks(mOnResumeTasks);
// Only run onPauseTasks if we have not skipped onResumeTasks in a
// first call to onResume. If we did skip onResumeTasks (note: we
// just killed any delayed Runnable), we also skip onPauseTasks to
// adhere to lifecycle state machine.
if (mSkippedFirstOnResume == false) {
Log.v(TAG, "onPause --> onPauseTasks()");
onPauseTasks();
}
super.onPause();
mStartupOnCreate = false;
logLifecycle("onPause", false);
}
@Override
protected final void onStop() {
if (isChangingConfigurations()) {
Log.v(TAG, "changing configurations");
}
logLifecycle("onStop", true);
onStopTasks();
super.onStop();
logLifecycle("onStop", false);
}
@Override
protected final void onRestart() {
logLifecycle("onRestart", true);
super.onRestart();
// TODO Support onRestartTasks() and handle the workaround for that too.
logLifecycle("onRestart", false);
}
@Override
protected final void onDestroy() {
logLifecycle("onDestroy", true);
onDestroyTasks();
super.onDestroy();
logLifecycle("onDestroy", false);
}
private void logLifecycle(String methodName, boolean start) {
String prefix = start ? "START" : "END";
Log.v(TAG, prefix + " " + methodName + ": Activity = " + toString());
}
protected boolean isKeyguardLocked() {
if (mKeyguardManager == null) {
mKeyguardManager = AndroidServices.instance().provideKeyguardManager();
}
if (mKeyguardManager != null) {
return mKeyguardManager.isKeyguardLocked();
}
return false;
}
protected boolean isKeyguardSecure() {
if (mKeyguardManager == null) {
mKeyguardManager = AndroidServices.instance().provideKeyguardManager();
}
if (mKeyguardManager != null) {
return mKeyguardManager.isKeyguardSecure();
}
return false;
}
protected void requestDismissKeyguard(Activity activity,
@Nullable KeyguardManager.KeyguardDismissCallback callback) {
if (mKeyguardManager == null) {
mKeyguardManager = AndroidServices.instance().provideKeyguardManager();
}
if (mKeyguardManager != null) {
mKeyguardManager.requestDismissKeyguard(activity, callback);
}
}
/**
* Subclasses should override this in place of {@link Activity#onNewIntent}.
*/
protected void onNewIntentTasks(Intent newIntent) {
}
/**
* Subclasses should override this in place of {@link Activity#onCreate}.
*/
protected void onCreateTasks(Bundle savedInstanceState) {
}
/**
* Subclasses should override this in place of {@link Activity#onStart}.
*/
protected void onStartTasks() {
}
/**
* Subclasses should override this in place of {@link Activity#onResume}.
*/
protected void onResumeTasks() {
}
/**
* Subclasses should override this in place of {@link Activity#onPause}.
*/
protected void onPauseTasks() {
}
/**
* Subclasses should override this in place of {@link Activity#onStop}.
*/
protected void onStopTasks() {
}
/**
* Subclasses should override this in place of {@link Activity#onDestroy}.
*/
protected void onDestroyTasks() {
}
}