2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package com.android.camera;
20 import android.animation.Animator;
21 import android.app.ActionBar;
22 import android.app.Activity;
23 import android.app.Dialog;
24 import android.content.ActivityNotFoundException;
25 import android.content.BroadcastReceiver;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.pm.ActivityInfo;
31 import android.content.res.Configuration;
32 import android.graphics.Bitmap;
33 import android.graphics.Matrix;
34 import android.graphics.RectF;
35 import android.graphics.SurfaceTexture;
36 import android.graphics.drawable.ColorDrawable;
37 import android.graphics.drawable.Drawable;
38 import android.net.Uri;
39 import android.nfc.NfcAdapter;
40 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
41 import android.nfc.NfcEvent;
42 import android.os.AsyncTask;
43 import android.os.Build;
44 import android.os.Bundle;
45 import android.os.Handler;
46 import android.os.HandlerThread;
47 import android.os.Looper;
48 import android.os.Message;
49 import android.provider.MediaStore;
50 import android.provider.Settings;
51 import android.text.TextUtils;
52 import android.util.CameraPerformanceTracker;
53 import android.view.ContextMenu;
54 import android.view.ContextMenu.ContextMenuInfo;
55 import android.view.KeyEvent;
56 import android.view.Menu;
57 import android.view.MenuInflater;
58 import android.view.MenuItem;
59 import android.view.MotionEvent;
60 import android.view.View;
61 import android.view.View.OnSystemUiVisibilityChangeListener;
62 import android.view.ViewGroup;
63 import android.view.Window;
64 import android.view.WindowManager;
65 import android.widget.FrameLayout;
66 import android.widget.ImageView;
67 import android.widget.ShareActionProvider;
69 import com.android.camera.app.AppController;
70 import com.android.camera.app.CameraAppUI;
71 import com.android.camera.app.CameraController;
72 import com.android.camera.app.CameraProvider;
73 import com.android.camera.app.CameraServices;
74 import com.android.camera.app.CameraServicesImpl;
75 import com.android.camera.app.FirstRunDialog;
76 import com.android.camera.app.LocationManager;
77 import com.android.camera.app.MemoryManager;
78 import com.android.camera.app.MemoryQuery;
79 import com.android.camera.app.ModuleManager;
80 import com.android.camera.app.ModuleManagerImpl;
81 import com.android.camera.app.MotionManager;
82 import com.android.camera.app.OrientationManager;
83 import com.android.camera.app.OrientationManagerImpl;
84 import com.android.camera.data.CameraFilmstripDataAdapter;
85 import com.android.camera.data.FilmstripContentObserver;
86 import com.android.camera.data.FilmstripItem;
87 import com.android.camera.data.FilmstripItemData;
88 import com.android.camera.data.FilmstripItemType;
89 import com.android.camera.data.FilmstripItemUtils;
90 import com.android.camera.data.FixedLastProxyAdapter;
91 import com.android.camera.data.GlideFilmstripManager;
92 import com.android.camera.data.LocalFilmstripDataAdapter;
93 import com.android.camera.data.LocalFilmstripDataAdapter.FilmstripItemListener;
94 import com.android.camera.data.MediaDetails;
95 import com.android.camera.data.MetadataLoader;
96 import com.android.camera.data.PhotoDataFactory;
97 import com.android.camera.data.PhotoItem;
98 import com.android.camera.data.PhotoItemFactory;
99 import com.android.camera.data.PlaceholderItem;
100 import com.android.camera.data.SessionItem;
101 import com.android.camera.data.VideoDataFactory;
102 import com.android.camera.data.VideoItemFactory;
103 import com.android.camera.debug.Log;
104 import com.android.camera.filmstrip.FilmstripContentPanel;
105 import com.android.camera.filmstrip.FilmstripController;
106 import com.android.camera.module.ModuleController;
107 import com.android.camera.module.ModulesInfo;
108 import com.android.camera.one.OneCameraException;
109 import com.android.camera.one.OneCameraManager;
110 import com.android.camera.session.CaptureSession;
111 import com.android.camera.session.CaptureSessionManager;
112 import com.android.camera.session.CaptureSessionManager.SessionListener;
113 import com.android.camera.settings.AppUpgrader;
114 import com.android.camera.settings.CameraSettingsActivity;
115 import com.android.camera.settings.Keys;
116 import com.android.camera.settings.ResolutionSetting;
117 import com.android.camera.settings.ResolutionUtil;
118 import com.android.camera.settings.SettingsManager;
119 import com.android.camera.stats.UsageStatistics;
120 import com.android.camera.stats.profiler.Profile;
121 import com.android.camera.stats.profiler.Profiler;
122 import com.android.camera.stats.profiler.Profilers;
123 import com.android.camera.tinyplanet.TinyPlanetFragment;
124 import com.android.camera.ui.AbstractTutorialOverlay;
125 import com.android.camera.ui.DetailsDialog;
126 import com.android.camera.ui.MainActivityLayout;
127 import com.android.camera.ui.ModeListView;
128 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
129 import com.android.camera.ui.PreviewStatusListener;
130 import com.android.camera.util.ApiHelper;
131 import com.android.camera.util.Callback;
132 import com.android.camera.util.CameraUtil;
133 import com.android.camera.util.GalleryHelper;
134 import com.android.camera.util.GcamHelper;
135 import com.android.camera.util.GoogleHelpHelper;
136 import com.android.camera.util.IntentHelper;
137 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
138 import com.android.camera.util.QuickActivity;
139 import com.android.camera.util.ReleaseHelper;
140 import com.android.camera.widget.FilmstripView;
141 import com.android.camera.widget.Preloader;
142 import com.android.camera2.R;
143 import com.android.ex.camera2.portability.CameraAgent;
144 import com.android.ex.camera2.portability.CameraAgentFactory;
145 import com.android.ex.camera2.portability.CameraExceptionHandler;
146 import com.android.ex.camera2.portability.CameraSettings;
147 import com.bumptech.glide.Glide;
148 import com.bumptech.glide.GlideBuilder;
149 import com.bumptech.glide.MemoryCategory;
150 import com.bumptech.glide.load.DecodeFormat;
151 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
152 import com.bumptech.glide.load.engine.prefill.PreFillType;
153 import com.google.common.base.Optional;
154 import com.google.common.logging.eventprotos;
155 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
156 import com.google.common.logging.eventprotos.MediaInteraction;
157 import com.google.common.logging.eventprotos.NavigationChange;
160 import java.lang.ref.WeakReference;
161 import java.util.ArrayList;
162 import java.util.HashMap;
163 import java.util.List;
165 public class CameraActivity extends QuickActivity
166 implements AppController, CameraAgent.CameraOpenCallback,
167 ShareActionProvider.OnShareTargetSelectedListener {
169 private static final Log.Tag TAG = new Log.Tag("CameraActivity");
171 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
172 "android.media.action.STILL_IMAGE_CAMERA_SECURE";
173 public static final String ACTION_IMAGE_CAPTURE_SECURE =
174 "android.media.action.IMAGE_CAPTURE_SECURE";
176 // The intent extra for camera from secure lock screen. True if the gallery
177 // should only show newly captured pictures. sSecureAlbumId does not
178 // increment. This is used when switching between camera, camcorder, and
179 // panorama. If the extra is not set, it is in the normal camera mode.
180 public static final String SECURE_CAMERA_EXTRA = "secure_camera";
182 public static final String MODULE_SCOPE_PREFIX = "_preferences_module_";
183 public static final String CAMERA_SCOPE_PREFIX = "_preferences_camera_";
185 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
186 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
187 /** Load metadata for 10 items ahead of our current. */
188 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
190 /** Should be used wherever a context is needed. */
191 private Context mAppContext;
194 * Camera fatal error handling:
195 * 1) Present error dialog to guide users to exit the app.
196 * 2) If users hit home button, onPause should just call finish() to exit the app.
198 private boolean mCameraFatalError = false;
201 * Whether onResume should reset the view to the preview.
203 private boolean mResetToPreviewOnResume = true;
206 * This data adapter is used by FilmStripView.
208 private VideoItemFactory mVideoItemFactory;
209 private PhotoItemFactory mPhotoItemFactory;
210 private LocalFilmstripDataAdapter mDataAdapter;
212 private OneCameraManager mCameraManager;
213 private SettingsManager mSettingsManager;
214 private ResolutionSetting mResolutionSetting;
215 private ModeListView mModeListView;
216 private boolean mModeListVisible = false;
217 private int mCurrentModeIndex;
218 private CameraModule mCurrentModule;
219 private ModuleManagerImpl mModuleManager;
220 private FrameLayout mAboveFilmstripControlLayout;
221 private FilmstripController mFilmstripController;
222 private boolean mFilmstripVisible;
223 /** Whether the filmstrip fully covers the preview. */
224 private boolean mFilmstripCoversPreview = false;
225 private int mResultCodeForTesting;
226 private Intent mResultDataForTesting;
227 private OnScreenHint mStorageHint;
228 private final Object mStorageSpaceLock = new Object();
229 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
230 private boolean mAutoRotateScreen;
231 private boolean mSecureCamera;
232 private OrientationManagerImpl mOrientationManager;
233 private LocationManager mLocationManager;
234 private ButtonManager mButtonManager;
235 private Handler mMainHandler;
236 private PanoramaViewHelper mPanoramaViewHelper;
237 private ActionBar mActionBar;
238 private ViewGroup mUndoDeletionBar;
239 private boolean mIsUndoingDeletion = false;
240 private boolean mIsActivityRunning = false;
242 private final Uri[] mNfcPushUris = new Uri[1];
244 private FilmstripContentObserver mLocalImagesObserver;
245 private FilmstripContentObserver mLocalVideosObserver;
247 private boolean mPendingDeletion = false;
249 private CameraController mCameraController;
250 private boolean mPaused;
251 private CameraAppUI mCameraAppUI;
253 private Intent mGalleryIntent;
254 private long mOnCreateTime;
256 private Menu mActionBarMenu;
257 private Preloader<Integer, AsyncTask> mPreloader;
259 /** Can be used to play custom sounds. */
260 private SoundPlayer mSoundPlayer;
262 private static final int LIGHTS_OUT_DELAY_MS = 4000;
263 private final int BASE_SYS_UI_VISIBILITY =
264 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
265 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
266 private final Runnable mLightsOutRunnable = new Runnable() {
269 getWindow().getDecorView().setSystemUiVisibility(
270 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE);
273 private MemoryManager mMemoryManager;
274 private MotionManager mMotionManager;
275 private final Profiler mProfiler = Profilers.instance().guard();
277 /** First run dialog */
278 private FirstRunDialog mFirstRunDialog;
281 public CameraAppUI getCameraAppUI() {
286 public ModuleManager getModuleManager() {
287 return mModuleManager;
291 * Close activity when secure app passes lock screen or screen turns
294 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
296 public void onReceive(Context context, Intent intent) {
302 * Whether the screen is kept turned on.
304 private boolean mKeepScreenOn;
305 private int mLastLayoutOrientation;
306 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
307 new CameraAppUI.BottomPanel.Listener() {
310 * If the current photo is a photo sphere, this will launch the
311 * Photo Sphere panorama viewer.
314 public void onExternalViewer() {
315 if (mPanoramaViewHelper == null) {
318 final FilmstripItem data = getCurrentLocalData();
320 Log.w(TAG, "Cannot open null data.");
323 final Uri contentUri = data.getData().getUri();
324 if (contentUri == Uri.EMPTY) {
325 Log.w(TAG, "Cannot open empty URL.");
329 if (data.getMetadata().isUsePanoramaViewer()) {
330 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
331 } else if (data.getMetadata().isHasRgbzData()) {
332 mPanoramaViewHelper.showRgbz(contentUri);
333 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
334 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
335 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
336 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false);
337 mCameraAppUI.clearClingForViewer(
338 CameraAppUI.BottomPanel.VIEWER_REFOCUS);
344 public void onEdit() {
345 FilmstripItem data = getCurrentLocalData();
347 Log.w(TAG, "Cannot edit null data.");
350 final int currentDataId = getCurrentDataId();
351 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
353 MediaInteraction.InteractionType.EDIT,
354 NavigationChange.InteractionCause.BUTTON,
355 fileAgeFromAdapterAtIndex(currentDataId));
360 public void onTinyPlanet() {
361 FilmstripItem data = getCurrentLocalData();
363 Log.w(TAG, "Cannot edit tiny planet on null data.");
366 launchTinyPlanetEditor(data);
370 public void onDelete() {
371 final int currentDataId = getCurrentDataId();
372 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
374 MediaInteraction.InteractionType.DELETE,
375 NavigationChange.InteractionCause.BUTTON,
376 fileAgeFromAdapterAtIndex(currentDataId));
377 removeItemAt(currentDataId);
381 public void onShare() {
382 final FilmstripItem data = getCurrentLocalData();
384 Log.w(TAG, "Cannot share null data.");
388 final int currentDataId = getCurrentDataId();
389 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
391 MediaInteraction.InteractionType.SHARE,
392 NavigationChange.InteractionCause.BUTTON,
393 fileAgeFromAdapterAtIndex(currentDataId));
394 // If applicable, show release information before this item
396 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
397 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
398 new Callback<Void>() {
400 public void onCallback(Void result) {
409 private void share(FilmstripItem data) {
410 Intent shareIntent = getShareIntentByData(data);
411 if (shareIntent != null) {
413 launchActivityByIntent(shareIntent);
414 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
415 } catch (ActivityNotFoundException ex) {
421 private int getCurrentDataId() {
422 return mFilmstripController.getCurrentAdapterIndex();
425 private FilmstripItem getCurrentLocalData() {
426 return mDataAdapter.getItemAt(getCurrentDataId());
430 * Sets up the share intent and NFC properly according to the
433 * @param item The data to be shared.
435 private Intent getShareIntentByData(final FilmstripItem item) {
436 Intent intent = null;
437 final Uri contentUri = item.getData().getUri();
438 final String msgShareTo = getResources().getString(R.string.share_to);
440 if (item.getMetadata().isPanorama360() &&
441 item.getData().getUri() != Uri.EMPTY) {
442 intent = new Intent(Intent.ACTION_SEND);
443 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE);
444 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
445 } else if (item.getAttributes().canShare()) {
446 final String mimeType = item.getData().getMimeType();
447 intent = getShareIntentFromType(mimeType);
448 if (intent != null) {
449 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
450 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
452 intent = Intent.createChooser(intent, msgShareTo);
458 * Get the share intent according to the mimeType
460 * @param mimeType The mimeType of current data.
461 * @return the video/image's ShareIntent or null if mimeType is
464 private Intent getShareIntentFromType(String mimeType) {
465 // Lazily create the intent object.
466 Intent intent = new Intent(Intent.ACTION_SEND);
467 if (mimeType.startsWith("video/")) {
468 intent.setType("video/*");
470 if (mimeType.startsWith("image/")) {
471 intent.setType("image/*");
473 Log.w(TAG, "unsupported mimeType " + mimeType);
480 public void onProgressErrorClicked() {
481 FilmstripItem data = getCurrentLocalData();
482 getServices().getCaptureSessionManager().removeErrorMessage(
483 data.getData().getUri());
484 updateBottomControlsByData(data);
489 public void onCameraOpened(CameraAgent.CameraProxy camera) {
490 Log.v(TAG, "onCameraOpened");
492 // We've paused, but just asynchronously opened the camera. Close it
493 // because we should be releasing the camera when paused to allow
494 // other apps to access it.
495 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera");
496 mCameraController.closeCamera(false);
500 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
501 // We shouldn't be here. Just close the camera and leave.
502 mCameraController.closeCamera(false);
503 throw new IllegalStateException("Camera opened but the module shouldn't be " +
506 if (mCurrentModule != null) {
507 resetExposureCompensationToDefault(camera);
508 mCurrentModule.onCameraAvailable(camera);
510 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
512 Log.v(TAG, "invoking onChangeCamera");
513 mCameraAppUI.onChangeCamera();
516 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) {
517 // Reset the exposure compensation before handing the camera to module.
518 CameraSettings cameraSettings = camera.getSettings();
519 cameraSettings.setExposureCompensationIndex(0);
520 camera.applySettings(cameraSettings);
524 public void onCameraDisabled(int cameraId) {
525 UsageStatistics.instance().cameraFailure(
526 eventprotos.CameraFailure.FailureReason.SECURITY, null,
527 UsageStatistics.NONE, UsageStatistics.NONE);
528 Log.w(TAG, "Camera disabled: " + cameraId);
529 CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
533 public void onDeviceOpenFailure(int cameraId, String info) {
534 UsageStatistics.instance().cameraFailure(
535 eventprotos.CameraFailure.FailureReason.OPEN_FAILURE, info,
536 UsageStatistics.NONE, UsageStatistics.NONE);
537 Log.w(TAG, "Camera open failure: " + info);
538 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
542 public void onDeviceOpenedAlready(int cameraId, String info) {
543 Log.w(TAG, "Camera open already: " + cameraId + "," + info);
544 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
548 public void onReconnectionFailure(CameraAgent mgr, String info) {
549 UsageStatistics.instance().cameraFailure(
550 eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE, null,
551 UsageStatistics.NONE, UsageStatistics.NONE);
552 Log.w(TAG, "Camera reconnection failure:" + info);
553 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
556 private static class MainHandler extends Handler {
557 final WeakReference<CameraActivity> mActivity;
559 public MainHandler(CameraActivity activity, Looper looper) {
561 mActivity = new WeakReference<CameraActivity>(activity);
565 public void handleMessage(Message msg) {
566 CameraActivity activity = mActivity.get();
567 if (activity == null) {
572 case MSG_CLEAR_SCREEN_ON_FLAG: {
573 if (!activity.mPaused) {
574 activity.getWindow().clearFlags(
575 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
583 private String fileNameFromAdapterAtIndex(int index) {
584 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
585 if (filmstripItem == null) {
589 File localFile = new File(filmstripItem.getData().getFilePath());
590 return localFile.getName();
593 private float fileAgeFromAdapterAtIndex(int index) {
594 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
595 if (filmstripItem == null) {
599 File localFile = new File(filmstripItem.getData().getFilePath());
600 return 0.001f * (System.currentTimeMillis() - localFile.lastModified());
603 private final FilmstripContentPanel.Listener mFilmstripListener =
604 new FilmstripContentPanel.Listener() {
607 public void onSwipeOut() {
611 public void onSwipeOutBegin() {
613 mCameraAppUI.hideBottomControls();
614 mFilmstripCoversPreview = false;
615 updatePreviewVisibility();
619 public void onFilmstripHidden() {
620 mFilmstripVisible = false;
621 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
622 NavigationChange.InteractionCause.SWIPE_RIGHT);
623 CameraActivity.this.setFilmstripUiVisibility(false);
624 // When the user hide the filmstrip (either swipe out or
625 // tap on back key) we move to the first item so next time
626 // when the user swipe in the filmstrip, the most recent
628 mFilmstripController.goToFirstItem();
632 public void onFilmstripShown() {
633 mFilmstripVisible = true;
634 mCameraAppUI.hideCaptureIndicator();
635 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
636 NavigationChange.InteractionCause.SWIPE_LEFT);
637 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
641 public void onFocusedDataLongPressed(int adapterIndex) {
646 public void onFocusedDataPromoted(int adapterIndex) {
647 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
649 MediaInteraction.InteractionType.DELETE,
650 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex(
652 removeItemAt(adapterIndex);
656 public void onFocusedDataDemoted(int adapterIndex) {
657 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
659 MediaInteraction.InteractionType.DELETE,
660 NavigationChange.InteractionCause.SWIPE_DOWN,
661 fileAgeFromAdapterAtIndex(adapterIndex));
662 removeItemAt(adapterIndex);
666 public void onEnterFullScreenUiShown(int adapterIndex) {
667 if (mFilmstripVisible) {
668 CameraActivity.this.setFilmstripUiVisibility(true);
673 public void onLeaveFullScreenUiShown(int adapterIndex) {
678 public void onEnterFullScreenUiHidden(int adapterIndex) {
679 if (mFilmstripVisible) {
680 CameraActivity.this.setFilmstripUiVisibility(false);
685 public void onLeaveFullScreenUiHidden(int adapterIndex) {
690 public void onEnterFilmstrip(int adapterIndex) {
691 if (mFilmstripVisible) {
692 CameraActivity.this.setFilmstripUiVisibility(true);
697 public void onLeaveFilmstrip(int adapterIndex) {
702 public void onDataReloaded() {
703 if (!mFilmstripVisible) {
706 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
710 public void onDataUpdated(int adapterIndex) {
711 if (!mFilmstripVisible) {
714 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
718 public void onEnterZoomView(int adapterIndex) {
719 if (mFilmstripVisible) {
720 CameraActivity.this.setFilmstripUiVisibility(false);
725 public void onZoomAtIndexChanged(int adapterIndex, float zoom) {
726 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex);
727 long ageMillis = System.currentTimeMillis()
728 - filmstripItem.getData().getLastModifiedDate().getTime();
730 // Do not log if items is to old or does not have a path (which is
731 // being used as a key).
732 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) ||
733 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) {
736 File localFile = new File(filmstripItem.getData().getFilePath());
737 UsageStatistics.instance().mediaView(localFile.getName(),
738 filmstripItem.getData().getLastModifiedDate().getTime(), zoom);
742 public void onDataFocusChanged(final int prevIndex, final int newIndex) {
743 if (!mFilmstripVisible) {
746 // TODO: This callback is UI event callback, should always
747 // happen on UI thread. Find the reason for this
748 // runOnUiThread() and fix it.
749 runOnUiThread(new Runnable() {
752 updateUiByData(newIndex);
758 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
759 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
763 private final FilmstripItemListener mFilmstripItemListener =
764 new FilmstripItemListener() {
766 public void onMetadataUpdated(List<Integer> indexes) {
768 // Callback after the activity is paused.
771 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
772 for (Integer index : indexes) {
773 if (index == currentIndex) {
774 updateBottomControlsByData(mDataAdapter.getItemAt(index));
775 // Currently we have only 1 data can be matched.
776 // No need to look for more, break.
783 public void gotoGallery() {
784 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
785 NavigationChange.InteractionCause.BUTTON);
787 mFilmstripController.goToNextItem();
791 * If 'visible' is false, this hides the action bar. Also maintains
792 * lights-out at all times.
794 * @param visible is false, this hides the action bar and filmstrip bottom
797 private void setFilmstripUiVisibility(boolean visible) {
798 mLightsOutRunnable.run();
799 mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
800 if (visible != mActionBar.isShowing()) {
803 mCameraAppUI.showBottomControls();
806 mCameraAppUI.hideBottomControls();
809 mFilmstripCoversPreview = visible;
810 updatePreviewVisibility();
813 private void hideSessionProgress() {
814 mCameraAppUI.getFilmstripBottomControls().hideProgress();
817 private void showSessionProgress(CharSequence message) {
818 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
819 controls.setProgressText(message);
820 controls.hideControls();
821 controls.hideProgressError();
822 controls.showProgress();
825 private void showProcessError(CharSequence message) {
826 mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
829 private void updateSessionProgress(int progress) {
830 mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
833 private void updateSessionProgressText(CharSequence message) {
834 mCameraAppUI.getFilmstripBottomControls().setProgressText(message);
837 private void setupNfcBeamPush() {
838 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
839 if (adapter == null) {
843 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
845 adapter.setNdefPushMessage(null, CameraActivity.this);
849 adapter.setBeamPushUris(null, CameraActivity.this);
850 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
852 public Uri[] createBeamUris(NfcEvent event) {
855 }, CameraActivity.this);
859 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
860 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
861 if (currentIndex < 0) {
864 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex),
865 MediaInteraction.InteractionType.SHARE,
866 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex));
867 // TODO add intent.getComponent().getPackageName()
871 // Note: All callbacks come back on the main thread.
872 private final SessionListener mSessionListener =
873 new SessionListener() {
875 public void onSessionQueued(final Uri uri) {
876 Log.v(TAG, "onSessionQueued: " + uri);
877 if (!Storage.isSessionUri(uri)) {
880 SessionItem newData = new SessionItem(getApplicationContext(), uri);
881 mDataAdapter.addOrUpdate(newData);
885 public void onSessionUpdated(Uri uri) {
886 Log.v(TAG, "onSessionUpdated: " + uri);
887 mDataAdapter.refresh(uri);
891 public void onSessionDone(final Uri sessionUri) {
892 Log.v(TAG, "onSessionDone:" + sessionUri);
893 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
894 if (contentUri == null) {
895 mDataAdapter.refresh(sessionUri);
898 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri);
900 // This can be null if e.g. a session is canceled (e.g.
901 // through discard panorama). It might be worth adding
902 // onSessionCanceled or the like this interface.
903 if (newData == null) {
904 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri);
908 // Make the PhotoItem aware of the session placeholder, to
909 // allow it to make a smooth transition to its content.
910 newData.setSessionPlaceholderBitmap(
911 Storage.getPlacerHolderForSession(sessionUri));
913 final int pos = mDataAdapter.findByContentUri(sessionUri);
915 // We do not have a placeholder for this image, perhaps
916 // due to the activity crashing or being killed.
917 mDataAdapter.addOrUpdate(newData);
919 mDataAdapter.updateItemAt(pos, newData);
924 public void onSessionProgress(final Uri uri, final int progress) {
926 // Do nothing, there is no task for this URI.
929 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
930 if (currentIndex == -1) {
934 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
935 updateSessionProgress(progress);
940 public void onSessionProgressText(final Uri uri, final CharSequence message) {
941 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
942 if (currentIndex == -1) {
946 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
947 updateSessionProgressText(message);
952 public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
953 // Don't show capture indicator in Photo Sphere.
954 final int photosphereModuleId = getApplicationContext().getResources()
956 R.integer.camera_mode_photosphere);
957 if (mCurrentModeIndex == photosphereModuleId) {
960 indicateCapture(indicator, rotationDegrees);
964 public void onSessionFailed(Uri uri, CharSequence reason) {
965 Log.v(TAG, "onSessionFailed:" + uri);
967 int failedIndex = mDataAdapter.findByContentUri(uri);
968 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
970 if (currentIndex == failedIndex) {
971 updateSessionProgress(0);
972 showProcessError(reason);
975 mDataAdapter.refresh(uri);
980 public Context getAndroidContext() {
985 public Dialog createDialog() {
986 return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
990 public void launchActivityByIntent(Intent intent) {
991 // Starting from L, we prefer not to start edit activity within camera's task.
992 mResetToPreviewOnResume = false;
993 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
995 startActivity(intent);
999 public int getCurrentModuleIndex() {
1000 return mCurrentModeIndex;
1004 public int getCurrentCameraId() {
1005 return mCameraController.getCurrentCameraId();
1009 public String getModuleScope() {
1010 return MODULE_SCOPE_PREFIX + mCurrentModule.getModuleStringIdentifier();
1014 public String getCameraScope() {
1015 int currentCameraId = getCurrentCameraId();
1016 if (currentCameraId < 0) {
1017 // if an unopen camera i.e. negative ID is returned, which we've observed in
1018 // some automated scenarios, just return it as a valid separate scope
1019 // this could cause user issues, so log a stack trace noting the call path
1020 // which resulted in this scenario.
1021 Log.w(TAG, "getting camera scope with no open camera, using id: " + currentCameraId);
1023 return CAMERA_SCOPE_PREFIX + Integer.toString(currentCameraId);
1027 public ModuleController getCurrentModuleController() {
1028 return mCurrentModule;
1032 public int getQuickSwitchToModuleId(int currentModuleIndex) {
1033 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
1038 public SurfaceTexture getPreviewBuffer() {
1039 // TODO: implement this
1044 public void onPreviewReadyToStart() {
1045 mCameraAppUI.onPreviewReadyToStart();
1049 public void onPreviewStarted() {
1050 mCameraAppUI.onPreviewStarted();
1054 public void addPreviewAreaSizeChangedListener(
1055 PreviewStatusListener.PreviewAreaChangedListener listener) {
1056 mCameraAppUI.addPreviewAreaChangedListener(listener);
1060 public void removePreviewAreaSizeChangedListener(
1061 PreviewStatusListener.PreviewAreaChangedListener listener) {
1062 mCameraAppUI.removePreviewAreaChangedListener(listener);
1066 public void setupOneShotPreviewListener() {
1067 mCameraController.setOneShotPreviewCallback(mMainHandler,
1068 new CameraAgent.CameraPreviewDataCallback() {
1070 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) {
1071 mCurrentModule.onPreviewInitialDataReceived();
1072 mCameraAppUI.onNewPreviewFrame();
1079 public void updatePreviewAspectRatio(float aspectRatio) {
1080 mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
1084 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
1085 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio);
1089 public RectF getFullscreenRect() {
1090 return mCameraAppUI.getFullscreenRect();
1094 public void updatePreviewTransform(Matrix matrix) {
1095 mCameraAppUI.updatePreviewTransform(matrix);
1099 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1100 mCameraAppUI.setPreviewStatusListener(previewStatusListener);
1104 public FrameLayout getModuleLayoutRoot() {
1105 return mCameraAppUI.getModuleRootView();
1109 public void setShutterEventsListener(ShutterEventsListener listener) {
1110 // TODO: implement this
1114 public void setShutterEnabled(boolean enabled) {
1115 mCameraAppUI.setShutterButtonEnabled(enabled);
1119 public boolean isShutterEnabled() {
1120 return mCameraAppUI.isShutterButtonEnabled();
1124 public void startFlashAnimation(boolean shortFlash) {
1125 mCameraAppUI.startFlashAnimation(shortFlash);
1129 public void startPreCaptureAnimation() {
1130 // TODO: implement this
1134 public void cancelPreCaptureAnimation() {
1135 // TODO: implement this
1139 public void startPostCaptureAnimation() {
1140 // TODO: implement this
1144 public void startPostCaptureAnimation(Bitmap thumbnail) {
1145 // TODO: implement this
1149 public void cancelPostCaptureAnimation() {
1150 // TODO: implement this
1154 public OrientationManager getOrientationManager() {
1155 return mOrientationManager;
1159 public LocationManager getLocationManager() {
1160 return mLocationManager;
1164 public void lockOrientation() {
1165 if (mOrientationManager != null) {
1166 mOrientationManager.lockOrientation();
1171 public void unlockOrientation() {
1172 if (mOrientationManager != null) {
1173 mOrientationManager.unlockOrientation();
1178 * If not in filmstrip, this shows the capture indicator.
1180 private void indicateCapture(final Bitmap indicator, final int rotationDegrees) {
1181 if (mFilmstripVisible) {
1185 // Don't show capture indicator in Photo Sphere.
1186 // TODO: Don't reach into resources to figure out the current mode.
1187 final int photosphereModuleId = getApplicationContext().getResources().getInteger(
1188 R.integer.camera_mode_photosphere);
1189 if (mCurrentModeIndex == photosphereModuleId) {
1193 mMainHandler.post(new Runnable() {
1196 mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule
1197 .getPeekAccessibilityString());
1198 mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees);
1204 public void notifyNewMedia(Uri uri) {
1205 // TODO: This method is running on the main thread. Also we should get
1206 // rid of that AsyncTask.
1208 updateStorageSpaceAndHint(null);
1209 ContentResolver cr = getContentResolver();
1210 String mimeType = cr.getType(uri);
1211 FilmstripItem newData = null;
1212 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
1213 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1214 newData = mVideoItemFactory.queryContentUri(uri);
1215 if (newData == null) {
1216 Log.e(TAG, "Can't find video data in content resolver:" + uri);
1219 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
1220 CameraUtil.broadcastNewPicture(mAppContext, uri);
1221 newData = mPhotoItemFactory.queryContentUri(uri);
1222 if (newData == null) {
1223 Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1227 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1231 // We are preloading the metadata for new video since we need the
1232 // rotation info for the thumbnail.
1233 new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
1235 protected FilmstripItem doInBackground(FilmstripItem... params) {
1236 FilmstripItem data = params[0];
1237 MetadataLoader.loadMetadata(getAndroidContext(), data);
1242 protected void onPostExecute(final FilmstripItem data) {
1243 // TODO: Figure out why sometimes the data is aleady there.
1244 mDataAdapter.addOrUpdate(data);
1246 // Legacy modules don't use CaptureSession, so we show the capture indicator when
1247 // the item was safed.
1248 if (mCurrentModule instanceof PhotoModule ||
1249 mCurrentModule instanceof VideoModule) {
1250 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1253 final Optional<Bitmap> bitmap = data.generateThumbnail(
1254 mAboveFilmstripControlLayout.getWidth(),
1255 mAboveFilmstripControlLayout.getMeasuredHeight());
1256 if (bitmap.isPresent()) {
1257 indicateCapture(bitmap.get(), 0);
1263 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
1267 public void enableKeepScreenOn(boolean enabled) {
1272 mKeepScreenOn = enabled;
1273 if (mKeepScreenOn) {
1274 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1275 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1277 keepScreenOnForAWhile();
1282 public CameraProvider getCameraProvider() {
1283 return mCameraController;
1287 public OneCameraManager getCameraManager() {
1288 return mCameraManager;
1291 private void removeItemAt(int index) {
1292 mDataAdapter.removeAt(index);
1293 if (mDataAdapter.getTotalNumber() > 1) {
1294 showUndoDeletionBar();
1296 // If camera preview is the only view left in filmstrip,
1297 // no need to show undo bar.
1298 mPendingDeletion = true;
1300 if (mFilmstripVisible) {
1301 mCameraAppUI.getFilmstripContentPanel().animateHide();
1307 public boolean onOptionsItemSelected(MenuItem item) {
1308 // Handle presses on the action bar items
1309 switch (item.getItemId()) {
1310 case android.R.id.home:
1313 case R.id.action_details:
1314 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex());
1316 case R.id.action_help_and_feedback:
1317 mResetToPreviewOnResume = false;
1318 new GoogleHelpHelper(this).launchGoogleHelp();
1321 return super.onOptionsItemSelected(item);
1325 private boolean isCaptureIntent() {
1326 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1327 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1328 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1336 * Note: Make sure this callback is unregistered properly when the activity
1337 * is destroyed since we're otherwise leaking the Activity reference.
1339 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
1340 = new CameraExceptionHandler.CameraExceptionCallback() {
1342 public void onCameraError(int errorCode) {
1343 // Not a fatal error. only do Log.e().
1344 Log.e(TAG, "Camera error callback. error=" + errorCode);
1347 public void onCameraException(
1348 RuntimeException ex, String commandHistory, int action, int state) {
1349 Log.e(TAG, "Camera Exception", ex);
1350 UsageStatistics.instance().cameraFailure(
1351 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
1352 commandHistory, action, state);
1356 public void onDispatchThreadException(RuntimeException ex) {
1357 Log.e(TAG, "DispatchThread Exception", ex);
1358 UsageStatistics.instance().cameraFailure(
1359 eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
1360 null, UsageStatistics.NONE, UsageStatistics.NONE);
1363 private void onFatalError() {
1364 if (mCameraFatalError) {
1367 mCameraFatalError = true;
1369 // If the activity receives exception during onPause, just exit the app.
1370 if (mPaused && !isFinishing()) {
1371 Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
1374 CameraUtil.showErrorAndFinish(CameraActivity.this,
1375 R.string.cannot_connect_camera);
1381 public void onNewIntentTasks(Intent intent) {
1382 onModeSelected(getModeIndex());
1386 public void onCreateTasks(Bundle state) {
1387 Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start();
1388 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1389 mOnCreateTime = System.currentTimeMillis();
1390 mAppContext = getApplicationContext();
1391 mMainHandler = new MainHandler(this, getMainLooper());
1392 mLocationManager = new LocationManager(mAppContext);
1393 mOrientationManager = new OrientationManagerImpl(this, mMainHandler);
1394 mSettingsManager = getServices().getSettingsManager();
1395 mSoundPlayer = new SoundPlayer(mAppContext);
1398 if (!Glide.isSetup()) {
1399 Context context = getAndroidContext();
1400 Glide.setup(new GlideBuilder(context)
1401 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888)
1402 .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
1404 Glide glide = Glide.get(context);
1406 // As a camera we will use a large amount of memory
1407 // for displaying images.
1408 glide.setMemoryCategory(MemoryCategory.HIGH);
1410 // Prefill glides bitmap pool to prevent excessive jank
1411 // when loading large images.
1412 glide.preFillBitmapPool(
1413 new PreFillType.Builder(GlideFilmstripManager.MAXIMUM_TEXTURE_SIZE)
1415 // It's more important for jank and GC to have
1416 // A larger weight of max texture size images than
1417 // media store sized images.
1418 new PreFillType.Builder(
1419 GlideFilmstripManager.MEDIASTORE_THUMB_WIDTH,
1420 GlideFilmstripManager.MEDIASTORE_THUMB_HEIGHT));
1422 profile.mark("Glide.setup");
1426 mCameraManager = OneCameraManager.get(this, ResolutionUtil.getDisplayMetrics(this));
1427 } catch (OneCameraException e) {
1428 // Log error and continue. Modules requiring OneCamera should check
1429 // and handle if null by showing error dialog or other treatment.
1430 Log.e(TAG, "Creating camera manager failed.", e);
1431 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
1433 profile.mark("OneCameraManager.get");
1434 mCameraController = new CameraController(mAppContext, this, mMainHandler,
1435 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1436 CameraAgentFactory.CameraApi.API_1),
1437 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1438 CameraAgentFactory.CameraApi.AUTO));
1439 mCameraController.setCameraExceptionHandler(
1440 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
1442 // TODO: Try to move all the resources allocation to happen as soon as
1443 // possible so we can call module.init() at the earliest time.
1444 mModuleManager = new ModuleManagerImpl();
1446 GcamHelper.init(getContentResolver());
1447 ModulesInfo.setupModules(mAppContext, mModuleManager);
1449 AppUpgrader appUpgrader = new AppUpgrader(this);
1450 appUpgrader.upgrade(mSettingsManager);
1451 Keys.setDefaults(mSettingsManager, mAppContext);
1453 mResolutionSetting = new ResolutionSetting(mSettingsManager, mCameraManager);
1455 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1456 // We suppress this flag via theme when drawing the system preview
1457 // background, but once we create activity here, reactivate to the
1458 // default value. The default is important for L, we don't want to
1459 // change app behavior, just starting background drawable layout.
1460 if (ApiHelper.isLOrHigher()) {
1461 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1465 setContentView(R.layout.activity_main);
1466 profile.mark("setContentView()");
1467 // A window background is set in styles.xml for the system to show a
1468 // drawable background with gray color and camera icon before the
1469 // activity is created. We set the background to null here to prevent
1470 // overdraw, all views must take care of drawing backgrounds if
1471 // necessary. This call to setBackgroundDrawable must occur after
1472 // setContentView, otherwise a background may be set again from the
1474 getWindow().setBackgroundDrawable(null);
1476 mActionBar = getActionBar();
1477 // set actionbar background to 100% or 50% transparent
1478 if (ApiHelper.isLOrHigher()) {
1479 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000));
1481 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
1484 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1485 mModeListView.init(mModuleManager.getSupportedModeIndexList());
1486 if (ApiHelper.HAS_ROTATION_ANIMATION) {
1487 setRotationAnimation();
1489 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1491 public void onVisibilityChanged(boolean visible) {
1492 mModeListVisible = visible;
1493 mCameraAppUI.setShutterButtonImportantToA11y(!visible);
1494 updatePreviewVisibility();
1498 // Check if this is in the secure camera mode.
1499 Intent intent = getIntent();
1500 String action = intent.getAction();
1501 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1502 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1503 mSecureCamera = true;
1505 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1508 if (mSecureCamera) {
1509 // Change the window flags so that secure camera can show when
1511 Window win = getWindow();
1512 WindowManager.LayoutParams params = win.getAttributes();
1513 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1514 win.setAttributes(params);
1516 // Filter for screen off so that we can finish secure camera
1517 // activity when screen is off.
1518 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1519 registerReceiver(mShutdownReceiver, filter_screen_off);
1521 // Filter for phone unlock so that we can finish secure camera
1522 // via this UI path:
1523 // 1. from secure lock screen, user starts secure camera
1524 // 2. user presses home button
1525 // 3. user unlocks phone
1526 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
1527 registerReceiver(mShutdownReceiver, filter_user_unlock);
1529 mCameraAppUI = new CameraAppUI(this,
1530 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1532 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1534 mAboveFilmstripControlLayout =
1535 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1537 // Add the session listener so we can track the session progress
1539 getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1540 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1541 mFilmstripController.setImageGap(
1542 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1543 profile.mark("Configure Camera UI");
1545 mPanoramaViewHelper = new PanoramaViewHelper(this);
1546 mPanoramaViewHelper.onCreate();
1548 ContentResolver appContentResolver = mAppContext.getContentResolver();
1549 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
1550 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
1551 new PhotoDataFactory());
1552 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
1553 new VideoDataFactory());
1554 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
1555 mPhotoItemFactory, mVideoItemFactory);
1556 mDataAdapter.setLocalDataListener(mFilmstripItemListener);
1558 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1561 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1562 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1563 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1564 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1567 setModuleFromModeIndex(getModeIndex());
1570 mCameraAppUI.prepareModuleUI();
1571 profile.mark("Init Current Module UI");
1572 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1573 profile.mark("Init CurrentModule");
1575 if (!mSecureCamera) {
1576 mFilmstripController.setDataAdapter(mDataAdapter);
1577 if (!isCaptureIntent()) {
1578 mDataAdapter.requestLoad(new Callback<Void>() {
1580 public void onCallback(Void result) {
1581 fillTemporarySessions();
1586 // Put a lock placeholder as the last image by setting its date to
1588 ImageView v = (ImageView) getLayoutInflater().inflate(
1589 R.layout.secure_album_placeholder, null);
1590 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
1591 v.setOnClickListener(new View.OnClickListener() {
1593 public void onClick(View view) {
1594 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1595 NavigationChange.InteractionCause.BUTTON);
1600 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1601 mDataAdapter = new FixedLastProxyAdapter(
1604 new PlaceholderItem(
1606 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
1607 v.getDrawable().getIntrinsicWidth(),
1608 v.getDrawable().getIntrinsicHeight()));
1609 // Flush out all the original data.
1610 mDataAdapter.clear();
1611 mFilmstripController.setDataAdapter(mDataAdapter);
1616 mLocalImagesObserver = new FilmstripContentObserver();
1617 mLocalVideosObserver = new FilmstripContentObserver();
1619 getContentResolver().registerContentObserver(
1620 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1621 mLocalImagesObserver);
1622 getContentResolver().registerContentObserver(
1623 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1624 mLocalVideosObserver);
1626 mMemoryManager = getServices().getMemoryManager();
1628 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1631 HashMap memoryData = mMemoryManager.queryMemory();
1632 UsageStatistics.instance().reportMemoryConsumed(memoryData,
1633 MemoryQuery.REPORT_LABEL_LAUNCH);
1637 mMotionManager = getServices().getMotionManager();
1639 mFirstRunDialog = new FirstRunDialog(this, new FirstRunDialog.FirstRunDialogListener() {
1641 public void onFirstRunStateReady() {
1642 // Run normal resume tasks.
1647 public void onCameraAccessException() {
1648 CameraUtil.showErrorAndFinish(CameraActivity.this, R.string.cannot_connect_camera);
1655 * Get the current mode index from the Intent or from persistent
1658 public int getModeIndex() {
1660 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1661 int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1662 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1663 int captureIntentIndex =
1664 getResources().getInteger(R.integer.camera_mode_capture_intent);
1665 String intentAction = getIntent().getAction();
1666 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction)
1667 || MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) {
1668 modeIndex = videoIndex;
1669 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction)
1670 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1672 modeIndex = captureIntentIndex;
1673 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction)
1674 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction)
1675 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1676 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1677 Keys.KEY_CAMERA_MODULE_LAST_USED);
1679 // For upgraders who have not seen the aspect ratio selection screen,
1680 // we need to drop them back in the photo module and have them select
1682 // TODO: Move this to SettingsManager as an upgrade procedure.
1683 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1684 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
1685 modeIndex = photoIndex;
1688 // If the activity has not been started using an explicit intent,
1689 // read the module index from the last time the user changed modes
1690 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1691 Keys.KEY_STARTUP_MODULE_INDEX);
1692 if ((modeIndex == gcamIndex &&
1693 !GcamHelper.hasGcamAsSeparateModule()) || modeIndex < 0) {
1694 modeIndex = photoIndex;
1701 * Call this whenever the mode drawer or filmstrip change the visibility
1704 private void updatePreviewVisibility() {
1705 if (mCurrentModule == null) {
1709 int visibility = getPreviewVisibility();
1710 mCameraAppUI.onPreviewVisiblityChanged(visibility);
1711 updatePreviewRendering(visibility);
1712 mCurrentModule.onPreviewVisibilityChanged(visibility);
1715 private void updatePreviewRendering(int visibility) {
1716 if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1717 mCameraAppUI.pausePreviewRendering();
1719 mCameraAppUI.resumePreviewRendering();
1723 private int getPreviewVisibility() {
1724 if (mFilmstripCoversPreview) {
1725 return ModuleController.VISIBILITY_HIDDEN;
1726 } else if (mModeListVisible){
1727 return ModuleController.VISIBILITY_COVERED;
1729 return ModuleController.VISIBILITY_VISIBLE;
1733 private void setRotationAnimation() {
1734 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1735 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1736 Window win = getWindow();
1737 WindowManager.LayoutParams winParams = win.getAttributes();
1738 winParams.rotationAnimation = rotationAnimation;
1739 win.setAttributes(winParams);
1743 public void onUserInteraction() {
1744 super.onUserInteraction();
1745 if (!isFinishing()) {
1746 keepScreenOnForAWhile();
1751 public boolean dispatchTouchEvent(MotionEvent ev) {
1752 boolean result = super.dispatchTouchEvent(ev);
1753 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1754 // Real deletion is postponed until the next user interaction after
1755 // the gesture that triggers deletion. Until real deletion is
1756 // performed, users can click the undo button to bring back the
1757 // image that they chose to delete.
1758 if (mPendingDeletion && !mIsUndoingDeletion) {
1766 public void onPauseTasks() {
1767 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1768 Profile profile = mProfiler.create("CameraActivity.onPause").start();
1771 * Save the last module index after all secure camera and icon launches,
1772 * not just on mode switches.
1774 * Right now we exclude capture intents from this logic, because we also
1775 * ignore the cross-Activity recovery logic in onStart for capture intents.
1777 if (!isCaptureIntent()) {
1778 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
1779 Keys.KEY_STARTUP_MODULE_INDEX,
1784 mCameraAppUI.hideCaptureIndicator();
1785 mFirstRunDialog.dismiss();
1787 // Delete photos that are pending deletion
1789 mCurrentModule.pause();
1790 mOrientationManager.pause();
1791 mPanoramaViewHelper.onPause();
1793 mLocalImagesObserver.setForegroundChangeListener(null);
1794 mLocalImagesObserver.setActivityPaused(true);
1795 mLocalVideosObserver.setActivityPaused(true);
1796 mPreloader.cancelAllLoads();
1799 mMotionManager.stop();
1801 // Always stop recording location when paused. Resume will start
1802 // location recording again if the location setting is on.
1803 mLocationManager.recordLocation(false);
1805 UsageStatistics.instance().backgrounded();
1807 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
1808 // button. Let's just kill the process.
1809 if (mCameraFatalError && !isFinishing()) {
1810 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
1813 // Close the camera and wait for the operation done.
1814 Log.v(TAG, "onPause closing camera");
1815 mCameraController.closeCamera(true);
1822 public void onResumeTasks() {
1825 // Show the dialog if necessary. The rest resume logic will be invoked
1826 // at the onFirstRunStateReady() callback.
1827 mFirstRunDialog.showIfNecessary();
1830 private void resume() {
1831 Profile profile = mProfiler.create("CameraActivity.resume").start();
1832 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1833 Log.v(TAG, "Build info: " + Build.DISPLAY);
1835 updateStorageSpaceAndHint(null);
1837 mLastLayoutOrientation = getResources().getConfiguration().orientation;
1839 // TODO: Handle this in OrientationManager.
1841 if (Settings.System.getInt(getContentResolver(),
1842 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1843 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1844 mAutoRotateScreen = false;
1846 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1847 mAutoRotateScreen = true;
1850 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and
1851 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
1852 // lockscreen onResume->onPause->onResume sequence.
1854 String action = getIntent().getAction();
1855 if (action == null) {
1856 source = ForegroundSource.UNKNOWN_SOURCE;
1859 case MediaStore.ACTION_IMAGE_CAPTURE:
1860 source = ForegroundSource.ACTION_IMAGE_CAPTURE;
1862 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
1863 // was UNKNOWN_SOURCE in Fishlake.
1864 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
1866 case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
1867 // was UNKNOWN_SOURCE in Fishlake.
1868 source = ForegroundSource.ACTION_VIDEO_CAMERA;
1870 case MediaStore.ACTION_VIDEO_CAPTURE:
1871 source = ForegroundSource.ACTION_VIDEO_CAPTURE;
1873 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
1874 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
1875 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
1877 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
1878 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
1880 case Intent.ACTION_MAIN:
1881 source = ForegroundSource.ACTION_MAIN;
1884 source = ForegroundSource.UNKNOWN_SOURCE;
1888 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode());
1890 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
1891 if (ApiHelper.isLOrHigher()) {
1892 // hide the up affordance for L devices, it's not very Materially
1893 mActionBar.setDisplayShowHomeEnabled(false);
1896 mOrientationManager.resume();
1898 mCurrentModule.hardResetSettings(mSettingsManager);
1901 mCurrentModule.resume();
1902 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
1903 NavigationChange.InteractionCause.BUTTON);
1904 setSwipingEnabled(true);
1905 profile.mark("mCurrentModule.resume");
1907 if (!mResetToPreviewOnResume) {
1908 FilmstripItem item = mDataAdapter.getItemAt(
1909 mFilmstripController.getCurrentAdapterIndex());
1911 mDataAdapter.refresh(item.getData().getUri());
1915 // The share button might be disabled to avoid double tapping.
1916 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1917 // Default is showing the preview, unless disabled by explicitly
1918 // starting an activity we want to return from to the filmstrip rather
1919 // than the preview.
1920 mResetToPreviewOnResume = true;
1922 if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1923 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1924 if (!mSecureCamera) {
1925 // If it's secure camera, requestLoad() should not be called
1926 // as it will load all the data.
1927 if (!mFilmstripVisible) {
1928 mDataAdapter.requestLoad(new Callback<Void>() {
1930 public void onCallback(Void result) {
1931 fillTemporarySessions();
1935 mDataAdapter.requestLoadNewPhotos();
1939 mLocalImagesObserver.setActivityPaused(false);
1940 mLocalVideosObserver.setActivityPaused(false);
1941 if (!mSecureCamera) {
1942 mLocalImagesObserver.setForegroundChangeListener(
1943 new FilmstripContentObserver.ChangeListener() {
1945 public void onChange() {
1946 mDataAdapter.requestLoadNewPhotos();
1951 keepScreenOnForAWhile();
1953 // Lights-out mode at all times.
1954 final View rootView = findViewById(R.id.activity_root_view);
1955 mLightsOutRunnable.run();
1956 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
1957 new OnSystemUiVisibilityChangeListener() {
1959 public void onSystemUiVisibilityChange(int visibility) {
1960 mMainHandler.removeCallbacks(mLightsOutRunnable);
1961 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
1966 mPanoramaViewHelper.onResume();
1967 profile.mark("mPanoramaViewHelper.onResume()");
1969 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1970 // Enable location recording if the setting is on.
1971 final boolean locationRecordingEnabled =
1972 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
1973 mLocationManager.recordLocation(locationRecordingEnabled);
1975 final int previewVisibility = getPreviewVisibility();
1976 updatePreviewRendering(previewVisibility);
1978 mMotionManager.start();
1982 private void fillTemporarySessions() {
1983 if (mSecureCamera) {
1986 // There might be sessions still in flight (processed by our service).
1987 // Make sure they're added to the filmstrip.
1988 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
1992 public void onStartTasks() {
1993 mIsActivityRunning = true;
1994 mPanoramaViewHelper.onStart();
1997 * If we're starting after launching a different Activity (lockscreen),
1998 * we need to use the last mode used in the other Activity, and
1999 * not the old one from this Activity.
2001 * This needs to happen before CameraAppUI.resume() in order to set the
2002 * mode cover icon to the actual last mode used.
2004 * Right now we exclude capture intents from this logic.
2006 int modeIndex = getModeIndex();
2007 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
2008 onModeSelected(modeIndex);
2011 if (mResetToPreviewOnResume) {
2012 mCameraAppUI.resume();
2013 mResetToPreviewOnResume = false;
2018 protected void onStopTasks() {
2019 mIsActivityRunning = false;
2020 mPanoramaViewHelper.onStop();
2022 mLocationManager.disconnect();
2026 public void onDestroyTasks() {
2027 if (mSecureCamera) {
2028 unregisterReceiver(mShutdownReceiver);
2031 mSettingsManager.removeAllListeners();
2032 mCameraController.removeCallbackReceiver();
2033 mCameraController.setCameraExceptionHandler(null);
2034 getContentResolver().unregisterContentObserver(mLocalImagesObserver);
2035 getContentResolver().unregisterContentObserver(mLocalVideosObserver);
2036 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
2037 mCameraAppUI.onDestroy();
2038 mModeListView.setVisibilityChangedListener(null);
2039 mCameraController = null;
2040 mSettingsManager = null;
2041 mOrientationManager = null;
2042 mButtonManager = null;
2043 mSoundPlayer.release();
2044 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
2045 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
2049 public void onConfigurationChanged(Configuration config) {
2050 super.onConfigurationChanged(config);
2051 Log.v(TAG, "onConfigurationChanged");
2052 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
2056 if (mLastLayoutOrientation != config.orientation) {
2057 mLastLayoutOrientation = config.orientation;
2058 mCurrentModule.onLayoutOrientationChanged(
2059 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
2064 public boolean onKeyDown(int keyCode, KeyEvent event) {
2065 if (!mFilmstripVisible) {
2066 if (mCurrentModule.onKeyDown(keyCode, event)) {
2069 // Prevent software keyboard or voice search from showing up.
2070 if (keyCode == KeyEvent.KEYCODE_SEARCH
2071 || keyCode == KeyEvent.KEYCODE_MENU) {
2072 if (event.isLongPress()) {
2078 return super.onKeyDown(keyCode, event);
2082 public boolean onKeyUp(int keyCode, KeyEvent event) {
2083 if (!mFilmstripVisible) {
2084 // If a module is in the middle of capture, it should
2085 // consume the key event.
2086 if (mCurrentModule.onKeyUp(keyCode, event)) {
2088 } else if (keyCode == KeyEvent.KEYCODE_MENU
2089 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2090 // Let the mode list view consume the event.
2091 mCameraAppUI.openModeList();
2093 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2094 mCameraAppUI.showFilmstrip();
2098 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2099 mFilmstripController.goToNextItem();
2101 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2102 boolean wentToPrevious = mFilmstripController.goToPreviousItem();
2103 if (!wentToPrevious) {
2104 // at beginning of filmstrip, hide and go back to preview
2105 mCameraAppUI.hideFilmstrip();
2110 return super.onKeyUp(keyCode, event);
2114 public void onBackPressed() {
2115 if (!mCameraAppUI.onBackPressed()) {
2116 if (!mCurrentModule.onBackPressed()) {
2117 super.onBackPressed();
2123 public boolean isAutoRotateScreen() {
2124 // TODO: Move to OrientationManager.
2125 return mAutoRotateScreen;
2129 public boolean onCreateOptionsMenu(Menu menu) {
2130 MenuInflater inflater = getMenuInflater();
2131 inflater.inflate(R.menu.filmstrip_menu, menu);
2132 mActionBarMenu = menu;
2134 // add a button for launching the gallery
2135 if (mGalleryIntent != null) {
2136 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
2137 if (appName != null) {
2138 MenuItem menuItem = menu.add(appName);
2139 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2140 menuItem.setIntent(mGalleryIntent);
2142 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
2143 if (galleryLogo != null) {
2144 menuItem.setIcon(galleryLogo);
2149 return super.onCreateOptionsMenu(menu);
2153 public boolean onPrepareOptionsMenu(Menu menu) {
2154 if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
2155 // Compatibility pre-L: launching new activities right above
2156 // lockscreen does not reliably work, only show help if not secure
2157 menu.removeItem(R.id.action_help_and_feedback);
2160 return super.onPrepareOptionsMenu(menu);
2163 protected long getStorageSpaceBytes() {
2164 synchronized (mStorageSpaceLock) {
2165 return mStorageSpaceBytes;
2169 protected interface OnStorageUpdateDoneListener {
2170 public void onStorageUpdateDone(long bytes);
2173 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
2175 * We execute disk operations on a background thread in order to
2176 * free up the UI thread. Synchronizing on the lock below ensures
2177 * that when getStorageSpaceBytes is called, the main thread waits
2178 * until this method has completed.
2180 * However, .execute() does not ensure this execution block will be
2181 * run right away (.execute() schedules this AsyncTask for sometime
2182 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
2183 * tries to execute the task in parellel with other AsyncTasks, but
2184 * there's still no guarantee).
2185 * e.g. don't call this then immediately call getStorageSpaceBytes().
2186 * Instead, pass in an OnStorageUpdateDoneListener.
2188 (new AsyncTask<Void, Void, Long>() {
2190 protected Long doInBackground(Void ... arg) {
2191 synchronized (mStorageSpaceLock) {
2192 mStorageSpaceBytes = Storage.getAvailableSpace();
2193 return mStorageSpaceBytes;
2198 protected void onPostExecute(Long bytes) {
2199 updateStorageHint(bytes);
2200 // This callback returns after I/O to check disk, so we could be
2201 // pausing and shutting down. If so, don't bother invoking.
2202 if (callback != null && !mPaused) {
2203 callback.onStorageUpdateDone(bytes);
2205 Log.v(TAG, "ignoring storage callback after activity pause");
2208 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2211 protected void updateStorageHint(long storageSpace) {
2212 if (!mIsActivityRunning) {
2216 String message = null;
2217 if (storageSpace == Storage.UNAVAILABLE) {
2218 message = getString(R.string.no_storage);
2219 } else if (storageSpace == Storage.PREPARING) {
2220 message = getString(R.string.preparing_sd);
2221 } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2222 message = getString(R.string.access_sd_fail);
2223 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2224 message = getString(R.string.spaceIsLow_content);
2227 if (message != null) {
2228 Log.w(TAG, "Storage warning: " + message);
2229 if (mStorageHint == null) {
2230 mStorageHint = OnScreenHint.makeText(message);
2232 mStorageHint.setText(message);
2234 mStorageHint.show();
2235 UsageStatistics.instance().storageWarning(storageSpace);
2237 // Disable all user interactions,
2238 mCameraAppUI.setDisableAllUserInteractions(true);
2239 } else if (mStorageHint != null) {
2240 mStorageHint.cancel();
2241 mStorageHint = null;
2243 // Re-enable all user interactions.
2244 mCameraAppUI.setDisableAllUserInteractions(false);
2248 protected void setResultEx(int resultCode) {
2249 mResultCodeForTesting = resultCode;
2250 setResult(resultCode);
2253 protected void setResultEx(int resultCode, Intent data) {
2254 mResultCodeForTesting = resultCode;
2255 mResultDataForTesting = data;
2256 setResult(resultCode, data);
2259 public int getResultCode() {
2260 return mResultCodeForTesting;
2263 public Intent getResultData() {
2264 return mResultDataForTesting;
2267 public boolean isSecureCamera() {
2268 return mSecureCamera;
2272 public boolean isPaused() {
2277 public int getPreferredChildModeIndex(int modeIndex) {
2278 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2279 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2280 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule()) {
2281 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2288 public void onModeSelected(int modeIndex) {
2289 if (mCurrentModeIndex == modeIndex) {
2293 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2294 // Record last used camera mode for quick switching
2295 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2296 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2297 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2298 Keys.KEY_CAMERA_MODULE_LAST_USED,
2302 closeModule(mCurrentModule);
2304 // Select the correct module index from the mode switcher index.
2305 modeIndex = getPreferredChildModeIndex(modeIndex);
2306 setModuleFromModeIndex(modeIndex);
2308 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2309 mCameraAppUI.addShutterListener(mCurrentModule);
2310 openModule(mCurrentModule);
2311 // Store the module index so we can use it the next time the Camera
2313 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2314 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2318 * Shows the settings dialog.
2321 public void onSettingsSelected() {
2322 UsageStatistics.instance().controlUsed(
2323 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2324 Intent intent = new Intent(this, CameraSettingsActivity.class);
2325 startActivity(intent);
2329 public void freezeScreenUntilPreviewReady() {
2330 mCameraAppUI.freezeScreenUntilPreviewReady();
2334 public int getModuleId(int modeIndex) {
2335 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2336 if (agent == null) {
2339 return agent.getModuleId();
2343 * Sets the mCurrentModuleIndex, creates a new module instance for the given
2344 * index an sets it as mCurrentModule.
2346 private void setModuleFromModeIndex(int modeIndex) {
2347 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2348 if (agent == null) {
2351 if (!agent.requestAppForCamera()) {
2352 mCameraController.closeCamera(true);
2354 mCurrentModeIndex = agent.getModuleId();
2355 mCurrentModule = (CameraModule) agent.createModule(this, getIntent());
2359 public SettingsManager getSettingsManager() {
2360 return mSettingsManager;
2364 public ResolutionSetting getResolutionSetting() {
2365 return mResolutionSetting;
2369 public CameraServices getServices() {
2370 return CameraServicesImpl.instance();
2373 public List<String> getSupportedModeNames() {
2374 List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2375 List<String> supported = new ArrayList<String>();
2377 for (Integer modeIndex : indices) {
2378 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2379 if (name != null && !name.equals("")) {
2380 supported.add(name);
2387 public ButtonManager getButtonManager() {
2388 if (mButtonManager == null) {
2389 mButtonManager = new ButtonManager(this);
2391 return mButtonManager;
2395 public SoundPlayer getSoundPlayer() {
2396 return mSoundPlayer;
2400 * Launches an ACTION_EDIT intent for the given local data item. If
2401 * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2402 * the user start either the tiny planet editor or another photo editor.
2404 * @param data The data item to edit.
2406 public void launchEditor(FilmstripItem data) {
2407 Intent intent = new Intent(Intent.ACTION_EDIT)
2408 .setDataAndType(data.getData().getUri(), data.getData().getMimeType())
2409 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2411 launchActivityByIntent(intent);
2412 } catch (ActivityNotFoundException e) {
2413 final String msgEditWith = getResources().getString(R.string.edit_with);
2414 launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2419 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2420 super.onCreateContextMenu(menu, v, menuInfo);
2422 MenuInflater inflater = getMenuInflater();
2423 inflater.inflate(R.menu.filmstrip_context_menu, menu);
2427 public boolean onContextItemSelected(MenuItem item) {
2428 switch (item.getItemId()) {
2429 case R.id.tiny_planet_editor:
2430 mMyFilmstripBottomControlListener.onTinyPlanet();
2432 case R.id.photo_editor:
2433 mMyFilmstripBottomControlListener.onEdit();
2440 * Launch the tiny planet editor.
2442 * @param data The data must be a 360 degree stereographically mapped
2443 * panoramic image. It will not be modified, instead a new item
2444 * with the result will be added to the filmstrip.
2446 public void launchTinyPlanetEditor(FilmstripItem data) {
2447 TinyPlanetFragment fragment = new TinyPlanetFragment();
2448 Bundle bundle = new Bundle();
2449 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString());
2450 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle());
2451 fragment.setArguments(bundle);
2452 fragment.show(getFragmentManager(), "tiny_planet");
2456 * Returns what UI mode (capture mode or filmstrip) we are in.
2457 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2459 private int currentUserInterfaceMode() {
2460 int mode = NavigationChange.Mode.UNKNOWN_MODE;
2461 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2462 mode = NavigationChange.Mode.PHOTO_CAPTURE;
2464 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2465 mode = NavigationChange.Mode.VIDEO_CAPTURE;
2467 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2468 mode = NavigationChange.Mode.LENS_BLUR;
2470 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2471 mode = NavigationChange.Mode.HDR_PLUS;
2473 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2474 mode = NavigationChange.Mode.PHOTO_SPHERE;
2476 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2477 mode = NavigationChange.Mode.PANORAMA;
2479 if (mFilmstripVisible) {
2480 mode = NavigationChange.Mode.FILMSTRIP;
2485 private void openModule(CameraModule module) {
2486 module.init(this, isSecureCamera(), isCaptureIntent());
2487 module.hardResetSettings(mSettingsManager);
2490 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2491 NavigationChange.InteractionCause.BUTTON);
2492 updatePreviewVisibility();
2496 private void closeModule(CameraModule module) {
2498 mCameraAppUI.clearModuleUI();
2501 private void performDeletion() {
2502 if (!mPendingDeletion) {
2505 hideUndoDeletionBar(false);
2506 mDataAdapter.executeDeletion();
2509 public void showUndoDeletionBar() {
2510 if (mPendingDeletion) {
2513 Log.v(TAG, "showing undo bar");
2514 mPendingDeletion = true;
2515 if (mUndoDeletionBar == null) {
2516 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2517 mAboveFilmstripControlLayout, true);
2518 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2519 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2520 button.setOnClickListener(new View.OnClickListener() {
2522 public void onClick(View view) {
2523 mDataAdapter.undoDeletion();
2524 hideUndoDeletionBar(true);
2527 // Setting undo bar clickable to avoid touch events going through
2528 // the bar to the buttons (eg. edit button, etc) underneath the bar.
2529 mUndoDeletionBar.setClickable(true);
2530 // When there is user interaction going on with the undo button, we
2531 // do not want to hide the undo bar.
2532 button.setOnTouchListener(new View.OnTouchListener() {
2534 public boolean onTouch(View v, MotionEvent event) {
2535 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2536 mIsUndoingDeletion = true;
2537 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2538 mIsUndoingDeletion = false;
2544 mUndoDeletionBar.setAlpha(0f);
2545 mUndoDeletionBar.setVisibility(View.VISIBLE);
2546 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2549 private void hideUndoDeletionBar(boolean withAnimation) {
2550 Log.v(TAG, "Hiding undo deletion bar");
2551 mPendingDeletion = false;
2552 if (mUndoDeletionBar != null) {
2553 if (withAnimation) {
2554 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2555 .setListener(new Animator.AnimatorListener() {
2557 public void onAnimationStart(Animator animation) {
2562 public void onAnimationEnd(Animator animation) {
2563 mUndoDeletionBar.setVisibility(View.GONE);
2567 public void onAnimationCancel(Animator animation) {
2572 public void onAnimationRepeat(Animator animation) {
2577 mUndoDeletionBar.setVisibility(View.GONE);
2583 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2586 * @param enable {@code true} to enable swipe.
2588 public void setSwipingEnabled(boolean enable) {
2589 // TODO: Bring back the functionality.
2590 if (isCaptureIntent()) {
2591 // lockPreview(true);
2593 // lockPreview(!enable);
2597 // Accessor methods for getting latency times used in performance testing
2598 public long getFirstPreviewTime() {
2599 if (mCurrentModule instanceof PhotoModule) {
2600 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2601 if (coverHiddenTime != -1) {
2602 return coverHiddenTime - mOnCreateTime;
2608 public long getAutoFocusTime() {
2609 return (mCurrentModule instanceof PhotoModule) ?
2610 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2613 public long getShutterLag() {
2614 return (mCurrentModule instanceof PhotoModule) ?
2615 ((PhotoModule) mCurrentModule).mShutterLag : -1;
2618 public long getShutterToPictureDisplayedTime() {
2619 return (mCurrentModule instanceof PhotoModule) ?
2620 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2623 public long getPictureDisplayedToJpegCallbackTime() {
2624 return (mCurrentModule instanceof PhotoModule) ?
2625 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2628 public long getJpegCallbackFinishTime() {
2629 return (mCurrentModule instanceof PhotoModule) ?
2630 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2633 public long getCaptureStartTime() {
2634 return (mCurrentModule instanceof PhotoModule) ?
2635 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2638 public boolean isRecording() {
2639 return (mCurrentModule instanceof VideoModule) ?
2640 ((VideoModule) mCurrentModule).isRecording() : false;
2643 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2644 return mCameraController;
2647 // For debugging purposes only.
2648 public CameraModule getCurrentModule() {
2649 return mCurrentModule;
2653 public void showTutorial(AbstractTutorialOverlay tutorial) {
2654 mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2658 public void showErrorAndFinish(int messageId) {
2659 CameraUtil.showErrorAndFinish(this, messageId);
2663 public void finishActivityWithIntentCompleted(Intent resultIntent) {
2664 finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent);
2668 public void finishActivityWithIntentCanceled() {
2669 finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent());
2672 private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) {
2673 mResultCodeForTesting = resultCode;
2674 mResultDataForTesting = resultIntent;
2675 setResult(resultCode, resultIntent);
2679 private void keepScreenOnForAWhile() {
2680 if (mKeepScreenOn) {
2683 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2684 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2685 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2688 private void resetScreenOn() {
2689 mKeepScreenOn = false;
2690 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2691 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2695 * @return {@code true} if the Gallery is launched successfully.
2697 private boolean startGallery() {
2698 if (mGalleryIntent == null) {
2702 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2703 NavigationChange.InteractionCause.BUTTON);
2704 Intent startGalleryIntent = new Intent(mGalleryIntent);
2705 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
2706 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex);
2707 if (currentFilmstripItem != null) {
2708 GalleryHelper.setContentUri(startGalleryIntent,
2709 currentFilmstripItem.getData().getUri());
2711 launchActivityByIntent(startGalleryIntent);
2712 } catch (ActivityNotFoundException e) {
2713 Log.w(TAG, "Failed to launch gallery activity, closing");
2718 private void setNfcBeamPushUriFromData(FilmstripItem data) {
2719 final Uri uri = data.getData().getUri();
2720 if (uri != Uri.EMPTY) {
2721 mNfcPushUris[0] = uri;
2723 mNfcPushUris[0] = null;
2728 * Updates the visibility of the filmstrip bottom controls and action bar.
2730 private void updateUiByData(final int index) {
2731 final FilmstripItem currentData = mDataAdapter.getItemAt(index);
2732 if (currentData == null) {
2733 Log.w(TAG, "Current data ID not found.");
2734 hideSessionProgress();
2737 updateActionBarMenu(currentData);
2739 /* Bottom controls. */
2740 updateBottomControlsByData(currentData);
2742 if (isSecureCamera()) {
2743 // We cannot show buttons in secure camera since go to other
2744 // activities might create a security hole.
2745 mCameraAppUI.getFilmstripBottomControls().hideControls();
2749 setNfcBeamPushUriFromData(currentData);
2751 if (!mDataAdapter.isMetadataUpdatedAt(index)) {
2752 mDataAdapter.updateMetadataAt(index);
2757 * Updates the bottom controls based on the data.
2759 private void updateBottomControlsByData(final FilmstripItem currentData) {
2761 final CameraAppUI.BottomPanel filmstripBottomPanel =
2762 mCameraAppUI.getFilmstripBottomControls();
2763 filmstripBottomPanel.showControls();
2764 filmstripBottomPanel.setEditButtonVisibility(
2765 currentData.getAttributes().canEdit());
2766 filmstripBottomPanel.setShareButtonVisibility(
2767 currentData.getAttributes().canShare());
2768 filmstripBottomPanel.setDeleteButtonVisibility(
2769 currentData.getAttributes().canDelete());
2773 Uri contentUri = currentData.getData().getUri();
2774 CaptureSessionManager sessionManager = getServices()
2775 .getCaptureSessionManager();
2777 if (sessionManager.hasErrorMessage(contentUri)) {
2778 showProcessError(sessionManager.getErrorMessage(contentUri));
2780 filmstripBottomPanel.hideProgressError();
2781 CaptureSession session = sessionManager.getSession(contentUri);
2783 if (session != null) {
2784 int sessionProgress = session.getProgress();
2786 if (sessionProgress < 0) {
2787 hideSessionProgress();
2789 CharSequence progressMessage = session.getProgressMessage();
2790 showSessionProgress(progressMessage);
2791 updateSessionProgress(sessionProgress);
2794 hideSessionProgress();
2800 // We need to add this to a separate DB.
2801 final int viewButtonVisibility;
2802 if (currentData.getMetadata().isUsePanoramaViewer()) {
2803 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2804 } else if (currentData.getMetadata().isHasRgbzData()) {
2805 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2807 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2810 filmstripBottomPanel.setTinyPlanetEnabled(
2811 currentData.getMetadata().isPanorama360());
2812 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2815 private void showDetailsDialog(int index) {
2816 final FilmstripItem data = mDataAdapter.getItemAt(index);
2820 Optional<MediaDetails> details = data.getMediaDetails();
2821 if (!details.isPresent()) {
2824 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get());
2825 detailDialog.show();
2826 UsageStatistics.instance().mediaInteraction(
2827 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS,
2828 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index));
2832 * Show or hide action bar items depending on current data type.
2834 private void updateActionBarMenu(FilmstripItem data) {
2835 if (mActionBarMenu == null) {
2839 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2840 if (detailsMenuItem == null) {
2844 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo();
2845 detailsMenuItem.setVisible(showDetails);