android:clearTaskOnLaunch="true"
android:configChanges="orientation|screenSize|keyboardHidden"
android:label="@string/app_name"
+ android:launchMode="singleTask"
android:taskAffinity="com.android.camera.CameraActivity"
android:theme="@style/Theme.Camera"
android:windowSoftInputMode="stateAlwaysHidden|adjustPan" >
import android.animation.Animator;
import android.annotation.TargetApi;
import android.app.ActionBar;
-import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import com.android.camera.util.GoogleHelpHelper;
import com.android.camera.util.IntentHelper;
import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
+import com.android.camera.util.QuickActivity;
import com.android.camera.util.ReleaseDialogHelper;
import com.android.camera.util.UsageStatistics;
import com.android.camera.widget.FilmstripView;
import java.util.List;
import java.util.concurrent.TimeUnit;
-public class CameraActivity extends Activity
+public class CameraActivity extends QuickActivity
implements AppController, CameraAgent.CameraOpenCallback,
ShareActionProvider.OnShareTargetSelectedListener,
OrientationManager.OnOrientationChangeListener {
};
@Override
- public void onCreate(Bundle state) {
+ public void onNewIntentTasks(Intent intent) {
+ onModeSelected(getModeIndex());
+ }
+
+ @Override
+ public void onCreateTasks(Bundle state) {
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
- super.onCreate(state);
if (!Glide.isSetup()) {
Glide.setup(new GlideBuilder(this)
.setResizeService(new FifoPriorityThreadPoolExecutor(2)));
}
@Override
- public void onPause() {
+ public void onPauseTasks() {
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
/*
finish();
}
}
-
- super.onPause();
}
@Override
- public void onResume() {
+ public void onResumeTasks() {
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
Log.v(TAG, "Build info: " + Build.DISPLAY);
}
mOrientationManager.resume();
- super.onResume();
mPeekAnimationThread = new HandlerThread("Peek animation");
mPeekAnimationThread.start();
mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
}
@Override
- public void onStart() {
- super.onStart();
+ public void onStartTasks() {
mIsActivityRunning = true;
mPanoramaViewHelper.onStart();
}
@Override
- protected void onStop() {
+ protected void onStopTasks() {
mIsActivityRunning = false;
mPanoramaViewHelper.onStop();
mLocationManager.disconnect();
- super.onStop();
}
@Override
- public void onDestroy() {
+ public void onDestroyTasks() {
if (mSecureCamera) {
unregisterReceiver(mScreenOffReceiver);
}
} catch (RuntimeException e) {
Log.e(TAG, "CameraAgentFactory exception during destroy", e);
}
- super.onDestroy();
}
@Override
private void openModule(CameraModule module) {
module.init(this, isSecureCamera(), isCaptureIntent());
module.hardResetSettings(mSettingsManager);
- module.resume();
- UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
- NavigationChange.InteractionCause.BUTTON);
- updatePreviewVisibility();
+ if (!mPaused) {
+ module.resume();
+ UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
+ NavigationChange.InteractionCause.BUTTON);
+ updatePreviewVisibility();
+ }
}
private void closeModule(CameraModule module) {
private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
/** Enable additional debug output. */
private static final boolean DEBUG = true;
- /**
- * This is the delay before we execute onResume tasks when coming from the
- * lock screen, to allow time for onPause to execute.
- * <p>
- * TODO: Make sure this value is in sync with what we see on L.
- */
- private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
/** Timeout for camera open/close operations. */
private static final int CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS = 2500;
/** Whether the module is paused right now. */
private boolean mPaused;
- /** Whether this module was resumed from lockscreen capture intent. */
- private boolean mIsResumeFromLockScreen = false;
-
- private final Runnable mResumeTaskRunnable = new Runnable() {
- @Override
- public void run() {
- onResumeTasks();
- }
- };
-
/** Main thread handler. */
private Handler mMainHandler;
/** Handler thread for camera-related operations. */
@Override
public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
Log.d(TAG, "init");
- mIsResumeFromLockScreen = isResumeFromLockscreen(activity);
mMainHandler = new Handler(activity.getMainLooper());
HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler");
thread.start();
@Override
public void resume() {
- // Add delay on resume from lock screen only, in order to to speed up
- // the onResume --> onPause --> onResume cycle from lock screen.
- // Don't do always because letting go of thread can cause delay.
- if (mIsResumeFromLockScreen) {
- Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis());
- // Note: onPauseAfterSuper() will delete this runnable, so we will
- // at most have 1 copy queued up.
- mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
- } else {
- onResumeTasks();
- }
- }
-
- private void onResumeTasks() {
- Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis());
mPaused = false;
mAppController.getCameraAppUI().onChangeCamera();
mAppController.addPreviewAreaSizeChangedListener(this);
private static final int UPDATE_PARAM_PREFERENCE = 4;
private static final int UPDATE_PARAM_ALL = -1;
- // This is the delay before we execute onResume tasks when coming
- // from the lock screen, to allow time for onPause to execute.
- private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
-
private static final String DEBUG_IMAGE_PREFIX = "DEBUG_";
private CameraActivity mActivity;
};
private boolean mShouldResizeTo16x9 = false;
- private final Runnable mResumeTaskRunnable = new Runnable() {
- @Override
- public void run() {
- onResumeTasks();
- }
- };
-
/**
* We keep the flash setting before entering scene modes (HDR)
* and restore it after HDR is off.
focusAndCapture();
}
- private void onResumeTasks() {
- if (mPaused) {
- return;
- }
- Log.v(TAG, "Executing onResumeTasks.");
+ @Override
+ public void resume() {
+ mPaused = false;
mCountdownSoundPlayer.loadSound(R.raw.timer_final_second);
mCountdownSoundPlayer.loadSound(R.raw.timer_increment);
}
@Override
- public void resume() {
- mPaused = false;
-
- // Add delay on resume from lock screen only, in order to to speed up
- // the onResume --> onPause --> onResume cycle from lock screen.
- // Don't do always because letting go of thread can cause delay.
- if (isResumeFromLockscreen()) {
- Log.v(TAG, "On resume, from lock screen.");
- // Note: onPauseAfterSuper() will delete this runnable, so we will
- // at most have 1 copy queued up.
- mHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
- } else {
- Log.v(TAG, "On resume.");
- onResumeTasks();
- }
- }
-
- @Override
public void pause() {
mPaused = true;
- mHandler.removeCallbacks(mResumeTaskRunnable);
getServices().getRemoteShutterListener().onModuleExit();
SessionStatsCollector.instance().sessionActive(false);
--- /dev/null
+/*
+ * 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.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.MediaStore;
+
+import com.android.camera.debug.Log;
+
+/**
+ * Workaround for lockscreen double-onResume() bug:
+ * <p>
+ * If started from the lockscreen, the activity may be quickly started, resumed,
+ * paused, stopped, and then started and resumed again. This is problematic for
+ * launch time from the lockscreen 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 current intent indicates that we have
+ * started from the lockscreen.
+ * </p>
+ * <p>
+ * Subclasses should override the appropriate on[Create|Start...]Tasks() method
+ * in place of the original.
+ * </p>
+ * <p>
+ * Sequences of onResume() followed quickly by onPause(), when the activity is
+ * started from an unsecure lockscreen will result in a quick no-op.<br>
+ * </p>
+ */
+public abstract class QuickActivity extends Activity {
+ private static final Log.Tag TAG = new Log.Tag("QuickActivity");
+
+ /**
+ * The amount of time to wait before running onResumeTasks when started from
+ * the lockscreen.
+ */
+ private static final long ON_RESUME_DELAY_MILLIS = 20;
+ /** A reference to the main handler on which to run lifecycle methods. */
+ private Handler mMainHandler;
+ private boolean mPaused;
+ private boolean mStopped;
+
+ /**
+ * 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() {
+ logLifecycle("onResumeTasks", true);
+ if (mStopped) {
+ onStartTasks();
+ mStopped = false;
+ }
+ if (mPaused) {
+ onResumeTasks();
+ mPaused = false;
+ }
+ logLifecycle("onResumeTasks", false);
+ }
+ };
+
+ @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) {
+ logLifecycle("onCreate", true);
+ Log.v(TAG, "Intent Action = " + getIntent().getAction());
+ super.onCreate(bundle);
+
+ mMainHandler = new Handler(getMainLooper());
+
+ onCreateTasks(bundle);
+
+ mPaused = true;
+ mStopped = true;
+
+ logLifecycle("onCreate", false);
+ }
+
+ @Override
+ protected final void onStart() {
+ logLifecycle("onStart", true);
+ if (delayOnResumeOnStart()) {
+ // Do nothing, instead wait for onResume() to be called and then
+ // execute onStartTasks() in mOnResumeTasks.
+ } else {
+ if (mStopped) {
+ onStartTasks();
+ mStopped = false;
+ }
+ }
+ super.onStart();
+ logLifecycle("onStart", false);
+ }
+
+ @Override
+ protected final void onResume() {
+ logLifecycle("onResume", true);
+ mMainHandler.removeCallbacks(mOnResumeTasks);
+ if (delayOnResumeOnStart()) {
+ mMainHandler.postDelayed(mOnResumeTasks, ON_RESUME_DELAY_MILLIS);
+ } else {
+ if (mPaused) {
+ onResumeTasks();
+ mPaused = false;
+ }
+ }
+ super.onResume();
+ logLifecycle("onResume", false);
+ }
+
+ @Override
+ protected final void onPause() {
+ logLifecycle("onPause", true);
+ mMainHandler.removeCallbacks(mOnResumeTasks);
+ if (!mPaused) {
+ onPauseTasks();
+ mPaused = true;
+ }
+ super.onPause();
+ logLifecycle("onPause", false);
+ }
+
+ @Override
+ protected final void onStop() {
+ if (isChangingConfigurations()) {
+ Log.v(TAG, "changing configurations");
+ }
+ logLifecycle("onStop", true);
+ mMainHandler.removeCallbacks(mOnResumeTasks);
+ if (!mStopped) {
+ onStopTasks();
+ mStopped = true;
+ }
+ 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());
+ }
+
+ private boolean delayOnResumeOnStart() {
+ String action = getIntent().getAction();
+ boolean isLockscreenCamera =
+ action.equals(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
+ boolean isSecureLockscreenCamera =
+ action.equals(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
+ return isLockscreenCamera || isSecureLockscreenCamera;
+ }
+
+ /**
+ * 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() {
+ }
+}