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.Looper;
47 import android.os.Message;
48 import android.provider.MediaStore;
49 import android.provider.Settings;
50 import android.text.TextUtils;
51 import android.util.CameraPerformanceTracker;
52 import android.view.ContextMenu;
53 import android.view.ContextMenu.ContextMenuInfo;
54 import android.view.KeyEvent;
55 import android.view.Menu;
56 import android.view.MenuInflater;
57 import android.view.MenuItem;
58 import android.view.MotionEvent;
59 import android.view.View;
60 import android.view.View.OnSystemUiVisibilityChangeListener;
61 import android.view.ViewGroup;
62 import android.view.Window;
63 import android.view.WindowManager;
64 import android.widget.FrameLayout;
65 import android.widget.ImageView;
66 import android.widget.ShareActionProvider;
68 import com.android.camera.app.AppController;
69 import com.android.camera.app.CameraAppUI;
70 import com.android.camera.app.CameraController;
71 import com.android.camera.app.CameraProvider;
72 import com.android.camera.app.CameraServices;
73 import com.android.camera.app.CameraServicesImpl;
74 import com.android.camera.app.FirstRunDialog;
75 import com.android.camera.app.LocationManager;
76 import com.android.camera.app.MemoryManager;
77 import com.android.camera.app.MemoryQuery;
78 import com.android.camera.app.ModuleManager;
79 import com.android.camera.app.ModuleManager.ModuleAgent;
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.device.ActiveCameraDeviceTracker;
105 import com.android.camera.filmstrip.FilmstripContentPanel;
106 import com.android.camera.filmstrip.FilmstripController;
107 import com.android.camera.module.ModuleController;
108 import com.android.camera.module.ModulesInfo;
109 import com.android.camera.one.OneCameraException;
110 import com.android.camera.one.OneCameraManager;
111 import com.android.camera.one.OneCameraModule;
112 import com.android.camera.one.OneCameraOpener;
113 import com.android.camera.one.config.OneCameraFeatureConfig;
114 import com.android.camera.one.config.OneCameraFeatureConfigCreator;
115 import com.android.camera.session.CaptureSession;
116 import com.android.camera.session.CaptureSessionManager;
117 import com.android.camera.session.CaptureSessionManager.SessionListener;
118 import com.android.camera.settings.AppUpgrader;
119 import com.android.camera.settings.CameraSettingsActivity;
120 import com.android.camera.settings.Keys;
121 import com.android.camera.settings.PictureSizeLoader;
122 import com.android.camera.settings.ResolutionSetting;
123 import com.android.camera.settings.ResolutionUtil;
124 import com.android.camera.settings.SettingsManager;
125 import com.android.camera.stats.UsageStatistics;
126 import com.android.camera.stats.profiler.Profile;
127 import com.android.camera.stats.profiler.Profiler;
128 import com.android.camera.stats.profiler.Profilers;
129 import com.android.camera.tinyplanet.TinyPlanetFragment;
130 import com.android.camera.ui.AbstractTutorialOverlay;
131 import com.android.camera.ui.DetailsDialog;
132 import com.android.camera.ui.MainActivityLayout;
133 import com.android.camera.ui.ModeListView;
134 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
135 import com.android.camera.ui.PreviewStatusListener;
136 import com.android.camera.util.ApiHelper;
137 import com.android.camera.util.Callback;
138 import com.android.camera.util.CameraSettingsActivityHelper;
139 import com.android.camera.util.CameraUtil;
140 import com.android.camera.util.GalleryHelper;
141 import com.android.camera.util.GcamHelper;
142 import com.android.camera.util.GoogleHelpHelper;
143 import com.android.camera.util.IntentHelper;
144 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
145 import com.android.camera.util.QuickActivity;
146 import com.android.camera.util.ReleaseHelper;
147 import com.android.camera.util.Size;
148 import com.android.camera.widget.FilmstripView;
149 import com.android.camera.widget.Preloader;
150 import com.android.camera2.R;
151 import com.android.ex.camera2.portability.CameraAgent;
152 import com.android.ex.camera2.portability.CameraAgentFactory;
153 import com.android.ex.camera2.portability.CameraExceptionHandler;
154 import com.android.ex.camera2.portability.CameraSettings;
155 import com.bumptech.glide.Glide;
156 import com.bumptech.glide.GlideBuilder;
157 import com.bumptech.glide.MemoryCategory;
158 import com.bumptech.glide.load.DecodeFormat;
159 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
160 import com.bumptech.glide.load.engine.prefill.PreFillType;
161 import com.google.common.base.Optional;
162 import com.google.common.logging.eventprotos;
163 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
164 import com.google.common.logging.eventprotos.MediaInteraction;
165 import com.google.common.logging.eventprotos.NavigationChange;
168 import java.lang.ref.WeakReference;
169 import java.util.ArrayList;
170 import java.util.HashMap;
171 import java.util.List;
173 public class CameraActivity extends QuickActivity
174 implements AppController, CameraAgent.CameraOpenCallback,
175 ShareActionProvider.OnShareTargetSelectedListener {
177 private static final Log.Tag TAG = new Log.Tag("CameraActivity");
179 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
180 "android.media.action.STILL_IMAGE_CAMERA_SECURE";
181 public static final String ACTION_IMAGE_CAPTURE_SECURE =
182 "android.media.action.IMAGE_CAPTURE_SECURE";
184 // The intent extra for camera from secure lock screen. True if the gallery
185 // should only show newly captured pictures. sSecureAlbumId does not
186 // increment. This is used when switching between camera, camcorder, and
187 // panorama. If the extra is not set, it is in the normal camera mode.
188 public static final String SECURE_CAMERA_EXTRA = "secure_camera";
190 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
191 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
192 /** Load metadata for 10 items ahead of our current. */
193 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
195 /** Should be used wherever a context is needed. */
196 private Context mAppContext;
199 * Camera fatal error handling:
200 * 1) Present error dialog to guide users to exit the app.
201 * 2) If users hit home button, onPause should just call finish() to exit the app.
203 private boolean mCameraFatalError = false;
206 * Whether onResume should reset the view to the preview.
208 private boolean mResetToPreviewOnResume = true;
211 * This data adapter is used by FilmStripView.
213 private VideoItemFactory mVideoItemFactory;
214 private PhotoItemFactory mPhotoItemFactory;
215 private LocalFilmstripDataAdapter mDataAdapter;
217 private ActiveCameraDeviceTracker mActiveCameraDeviceTracker;
218 private OneCameraOpener mOneCameraOpener;
219 private OneCameraManager mOneCameraManager;
220 private SettingsManager mSettingsManager;
221 private ResolutionSetting mResolutionSetting;
222 private ModeListView mModeListView;
223 private boolean mModeListVisible = false;
224 private int mCurrentModeIndex;
225 private CameraModule mCurrentModule;
226 private ModuleManagerImpl mModuleManager;
227 private FrameLayout mAboveFilmstripControlLayout;
228 private FilmstripController mFilmstripController;
229 private boolean mFilmstripVisible;
230 /** Whether the filmstrip fully covers the preview. */
231 private boolean mFilmstripCoversPreview = false;
232 private int mResultCodeForTesting;
233 private Intent mResultDataForTesting;
234 private OnScreenHint mStorageHint;
235 private final Object mStorageSpaceLock = new Object();
236 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
237 private boolean mAutoRotateScreen;
238 private boolean mSecureCamera;
239 private OrientationManagerImpl mOrientationManager;
240 private LocationManager mLocationManager;
241 private ButtonManager mButtonManager;
242 private Handler mMainHandler;
243 private PanoramaViewHelper mPanoramaViewHelper;
244 private ActionBar mActionBar;
245 private ViewGroup mUndoDeletionBar;
246 private boolean mIsUndoingDeletion = false;
247 private boolean mIsActivityRunning = false;
248 private FatalErrorHandler mFatalErrorHandler;
250 private final Uri[] mNfcPushUris = new Uri[1];
252 private FilmstripContentObserver mLocalImagesObserver;
253 private FilmstripContentObserver mLocalVideosObserver;
255 private boolean mPendingDeletion = false;
257 private CameraController mCameraController;
258 private boolean mPaused;
259 private CameraAppUI mCameraAppUI;
261 private Intent mGalleryIntent;
262 private long mOnCreateTime;
264 private Menu mActionBarMenu;
265 private Preloader<Integer, AsyncTask> mPreloader;
267 /** Can be used to play custom sounds. */
268 private SoundPlayer mSoundPlayer;
270 /** Holds configuration for various OneCamera features. */
271 private OneCameraFeatureConfig mFeatureConfig;
273 private static final int LIGHTS_OUT_DELAY_MS = 4000;
274 private final int BASE_SYS_UI_VISIBILITY =
275 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
276 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
277 private final Runnable mLightsOutRunnable = new Runnable() {
280 getWindow().getDecorView().setSystemUiVisibility(
281 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE);
284 private MemoryManager mMemoryManager;
285 private MotionManager mMotionManager;
286 private final Profiler mProfiler = Profilers.instance().guard();
288 /** First run dialog */
289 private FirstRunDialog mFirstRunDialog;
292 public CameraAppUI getCameraAppUI() {
297 public ModuleManager getModuleManager() {
298 return mModuleManager;
302 * Close activity when secure app passes lock screen or screen turns
305 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
307 public void onReceive(Context context, Intent intent) {
313 * Whether the screen is kept turned on.
315 private boolean mKeepScreenOn;
316 private int mLastLayoutOrientation;
317 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
318 new CameraAppUI.BottomPanel.Listener() {
321 * If the current photo is a photo sphere, this will launch the
322 * Photo Sphere panorama viewer.
325 public void onExternalViewer() {
326 if (mPanoramaViewHelper == null) {
329 final FilmstripItem data = getCurrentLocalData();
331 Log.w(TAG, "Cannot open null data.");
334 final Uri contentUri = data.getData().getUri();
335 if (contentUri == Uri.EMPTY) {
336 Log.w(TAG, "Cannot open empty URL.");
340 if (data.getMetadata().isUsePanoramaViewer()) {
341 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
342 } else if (data.getMetadata().isHasRgbzData()) {
343 mPanoramaViewHelper.showRgbz(contentUri);
344 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
345 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
346 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
347 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false);
348 mCameraAppUI.clearClingForViewer(
349 CameraAppUI.BottomPanel.VIEWER_REFOCUS);
355 public void onEdit() {
356 FilmstripItem data = getCurrentLocalData();
358 Log.w(TAG, "Cannot edit null data.");
361 final int currentDataId = getCurrentDataId();
362 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
364 MediaInteraction.InteractionType.EDIT,
365 NavigationChange.InteractionCause.BUTTON,
366 fileAgeFromAdapterAtIndex(currentDataId));
371 public void onTinyPlanet() {
372 FilmstripItem data = getCurrentLocalData();
374 Log.w(TAG, "Cannot edit tiny planet on null data.");
377 launchTinyPlanetEditor(data);
381 public void onDelete() {
382 final int currentDataId = getCurrentDataId();
383 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
385 MediaInteraction.InteractionType.DELETE,
386 NavigationChange.InteractionCause.BUTTON,
387 fileAgeFromAdapterAtIndex(currentDataId));
388 removeItemAt(currentDataId);
392 public void onShare() {
393 final FilmstripItem data = getCurrentLocalData();
395 Log.w(TAG, "Cannot share null data.");
399 final int currentDataId = getCurrentDataId();
400 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
402 MediaInteraction.InteractionType.SHARE,
403 NavigationChange.InteractionCause.BUTTON,
404 fileAgeFromAdapterAtIndex(currentDataId));
405 // If applicable, show release information before this item
407 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
408 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
409 new Callback<Void>() {
411 public void onCallback(Void result) {
420 private void share(FilmstripItem data) {
421 Intent shareIntent = getShareIntentByData(data);
422 if (shareIntent != null) {
424 launchActivityByIntent(shareIntent);
425 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
426 } catch (ActivityNotFoundException ex) {
432 private int getCurrentDataId() {
433 return mFilmstripController.getCurrentAdapterIndex();
436 private FilmstripItem getCurrentLocalData() {
437 return mDataAdapter.getItemAt(getCurrentDataId());
441 * Sets up the share intent and NFC properly according to the
444 * @param item The data to be shared.
446 private Intent getShareIntentByData(final FilmstripItem item) {
447 Intent intent = null;
448 final Uri contentUri = item.getData().getUri();
449 final String msgShareTo = getResources().getString(R.string.share_to);
451 if (item.getMetadata().isPanorama360() &&
452 item.getData().getUri() != Uri.EMPTY) {
453 intent = new Intent(Intent.ACTION_SEND);
454 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE);
455 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
456 } else if (item.getAttributes().canShare()) {
457 final String mimeType = item.getData().getMimeType();
458 intent = getShareIntentFromType(mimeType);
459 if (intent != null) {
460 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
461 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
463 intent = Intent.createChooser(intent, msgShareTo);
469 * Get the share intent according to the mimeType
471 * @param mimeType The mimeType of current data.
472 * @return the video/image's ShareIntent or null if mimeType is
475 private Intent getShareIntentFromType(String mimeType) {
476 // Lazily create the intent object.
477 Intent intent = new Intent(Intent.ACTION_SEND);
478 if (mimeType.startsWith("video/")) {
479 intent.setType("video/*");
481 if (mimeType.startsWith("image/")) {
482 intent.setType("image/*");
484 Log.w(TAG, "unsupported mimeType " + mimeType);
491 public void onProgressErrorClicked() {
492 FilmstripItem data = getCurrentLocalData();
493 getServices().getCaptureSessionManager().removeErrorMessage(
494 data.getData().getUri());
495 updateBottomControlsByData(data);
500 public void onCameraOpened(CameraAgent.CameraProxy camera) {
501 Log.v(TAG, "onCameraOpened");
503 // We've paused, but just asynchronously opened the camera. Close it
504 // because we should be releasing the camera when paused to allow
505 // other apps to access it.
506 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera");
507 mCameraController.closeCamera(false);
511 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
512 // We shouldn't be here. Just close the camera and leave.
513 mCameraController.closeCamera(false);
514 throw new IllegalStateException("Camera opened but the module shouldn't be " +
517 if (mCurrentModule != null) {
518 resetExposureCompensationToDefault(camera);
519 mCurrentModule.onCameraAvailable(camera);
521 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
523 Log.v(TAG, "invoking onChangeCamera");
524 mCameraAppUI.onChangeCamera();
527 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) {
528 // Reset the exposure compensation before handing the camera to module.
529 CameraSettings cameraSettings = camera.getSettings();
530 cameraSettings.setExposureCompensationIndex(0);
531 camera.applySettings(cameraSettings);
535 public void onCameraDisabled(int cameraId) {
536 Log.w(TAG, "Camera disabled: " + cameraId);
537 mFatalErrorHandler.onCameraDisabledFailure();
541 public void onDeviceOpenFailure(int cameraId, String info) {
542 Log.w(TAG, "Camera open failure: " + info);
543 mFatalErrorHandler.onCameraOpenFailure();
547 public void onDeviceOpenedAlready(int cameraId, String info) {
548 Log.w(TAG, "Camera open already: " + cameraId + "," + info);
549 mFatalErrorHandler.onGenericCameraAccessFailure();
553 public void onReconnectionFailure(CameraAgent mgr, String info) {
554 Log.w(TAG, "Camera reconnection failure:" + info);
555 mFatalErrorHandler.onCameraReconnectFailure();
558 private static class MainHandler extends Handler {
559 final WeakReference<CameraActivity> mActivity;
561 public MainHandler(CameraActivity activity, Looper looper) {
563 mActivity = new WeakReference<CameraActivity>(activity);
567 public void handleMessage(Message msg) {
568 CameraActivity activity = mActivity.get();
569 if (activity == null) {
574 case MSG_CLEAR_SCREEN_ON_FLAG: {
575 if (!activity.mPaused) {
576 activity.getWindow().clearFlags(
577 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
585 private String fileNameFromAdapterAtIndex(int index) {
586 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
587 if (filmstripItem == null) {
591 File localFile = new File(filmstripItem.getData().getFilePath());
592 return localFile.getName();
595 private float fileAgeFromAdapterAtIndex(int index) {
596 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
597 if (filmstripItem == null) {
601 File localFile = new File(filmstripItem.getData().getFilePath());
602 return 0.001f * (System.currentTimeMillis() - localFile.lastModified());
605 private final FilmstripContentPanel.Listener mFilmstripListener =
606 new FilmstripContentPanel.Listener() {
609 public void onSwipeOut() {
613 public void onSwipeOutBegin() {
615 mCameraAppUI.hideBottomControls();
616 mFilmstripCoversPreview = false;
617 updatePreviewVisibility();
621 public void onFilmstripHidden() {
622 mFilmstripVisible = false;
623 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
624 NavigationChange.InteractionCause.SWIPE_RIGHT);
625 CameraActivity.this.setFilmstripUiVisibility(false);
626 // When the user hide the filmstrip (either swipe out or
627 // tap on back key) we move to the first item so next time
628 // when the user swipe in the filmstrip, the most recent
630 mFilmstripController.goToFirstItem();
634 public void onFilmstripShown() {
635 mFilmstripVisible = true;
636 mCameraAppUI.hideCaptureIndicator();
637 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
638 NavigationChange.InteractionCause.SWIPE_LEFT);
639 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
643 public void onFocusedDataLongPressed(int adapterIndex) {
648 public void onFocusedDataPromoted(int adapterIndex) {
649 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
651 MediaInteraction.InteractionType.DELETE,
652 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex(
654 removeItemAt(adapterIndex);
658 public void onFocusedDataDemoted(int adapterIndex) {
659 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
661 MediaInteraction.InteractionType.DELETE,
662 NavigationChange.InteractionCause.SWIPE_DOWN,
663 fileAgeFromAdapterAtIndex(adapterIndex));
664 removeItemAt(adapterIndex);
668 public void onEnterFullScreenUiShown(int adapterIndex) {
669 if (mFilmstripVisible) {
670 CameraActivity.this.setFilmstripUiVisibility(true);
675 public void onLeaveFullScreenUiShown(int adapterIndex) {
680 public void onEnterFullScreenUiHidden(int adapterIndex) {
681 if (mFilmstripVisible) {
682 CameraActivity.this.setFilmstripUiVisibility(false);
687 public void onLeaveFullScreenUiHidden(int adapterIndex) {
692 public void onEnterFilmstrip(int adapterIndex) {
693 if (mFilmstripVisible) {
694 CameraActivity.this.setFilmstripUiVisibility(true);
699 public void onLeaveFilmstrip(int adapterIndex) {
704 public void onDataReloaded() {
705 if (!mFilmstripVisible) {
708 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
712 public void onDataUpdated(int adapterIndex) {
713 if (!mFilmstripVisible) {
716 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
720 public void onEnterZoomView(int adapterIndex) {
721 if (mFilmstripVisible) {
722 CameraActivity.this.setFilmstripUiVisibility(false);
727 public void onZoomAtIndexChanged(int adapterIndex, float zoom) {
728 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex);
729 long ageMillis = System.currentTimeMillis()
730 - filmstripItem.getData().getLastModifiedDate().getTime();
732 // Do not log if items is to old or does not have a path (which is
733 // being used as a key).
734 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) ||
735 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) {
738 File localFile = new File(filmstripItem.getData().getFilePath());
739 UsageStatistics.instance().mediaView(localFile.getName(),
740 filmstripItem.getData().getLastModifiedDate().getTime(), zoom);
744 public void onDataFocusChanged(final int prevIndex, final int newIndex) {
745 if (!mFilmstripVisible) {
748 // TODO: This callback is UI event callback, should always
749 // happen on UI thread. Find the reason for this
750 // runOnUiThread() and fix it.
751 runOnUiThread(new Runnable() {
754 updateUiByData(newIndex);
760 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
761 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
765 private final FilmstripItemListener mFilmstripItemListener =
766 new FilmstripItemListener() {
768 public void onMetadataUpdated(List<Integer> indexes) {
770 // Callback after the activity is paused.
773 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
774 for (Integer index : indexes) {
775 if (index == currentIndex) {
776 updateBottomControlsByData(mDataAdapter.getItemAt(index));
777 // Currently we have only 1 data can be matched.
778 // No need to look for more, break.
785 public void gotoGallery() {
786 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
787 NavigationChange.InteractionCause.BUTTON);
789 mFilmstripController.goToNextItem();
793 * If 'visible' is false, this hides the action bar. Also maintains
794 * lights-out at all times.
796 * @param visible is false, this hides the action bar and filmstrip bottom
799 private void setFilmstripUiVisibility(boolean visible) {
800 mLightsOutRunnable.run();
801 mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
802 if (visible != mActionBar.isShowing()) {
805 mCameraAppUI.showBottomControls();
808 mCameraAppUI.hideBottomControls();
811 mFilmstripCoversPreview = visible;
812 updatePreviewVisibility();
815 private void hideSessionProgress() {
816 mCameraAppUI.getFilmstripBottomControls().hideProgress();
819 private void showSessionProgress(int messageId) {
820 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
821 controls.setProgressText(messageId > 0 ? getString(messageId) : "");
822 controls.hideControls();
823 controls.hideProgressError();
824 controls.showProgress();
827 private void showProcessError(int messageId) {
828 mCameraAppUI.getFilmstripBottomControls().showProgressError(
829 messageId > 0 ? getString(messageId) : "");
832 private void updateSessionProgress(int progress) {
833 mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
836 private void updateSessionProgressText(int messageId) {
837 mCameraAppUI.getFilmstripBottomControls().setProgressText(
838 messageId > 0 ? getString(messageId) : "");
841 private void setupNfcBeamPush() {
842 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
843 if (adapter == null) {
847 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
849 adapter.setNdefPushMessage(null, CameraActivity.this);
853 adapter.setBeamPushUris(null, CameraActivity.this);
854 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
856 public Uri[] createBeamUris(NfcEvent event) {
859 }, CameraActivity.this);
863 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
864 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
865 if (currentIndex < 0) {
868 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex),
869 MediaInteraction.InteractionType.SHARE,
870 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex));
871 // TODO add intent.getComponent().getPackageName()
875 // Note: All callbacks come back on the main thread.
876 private final SessionListener mSessionListener =
877 new SessionListener() {
879 public void onSessionQueued(final Uri uri) {
880 Log.v(TAG, "onSessionQueued: " + uri);
881 if (!Storage.isSessionUri(uri)) {
884 SessionItem newData = new SessionItem(getApplicationContext(), uri);
885 mDataAdapter.addOrUpdate(newData);
889 public void onSessionUpdated(Uri uri) {
890 Log.v(TAG, "onSessionUpdated: " + uri);
891 mDataAdapter.refresh(uri);
895 public void onSessionDone(final Uri sessionUri) {
896 Log.v(TAG, "onSessionDone:" + sessionUri);
897 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
898 if (contentUri == null) {
899 mDataAdapter.refresh(sessionUri);
902 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri);
904 // This can be null if e.g. a session is canceled (e.g.
905 // through discard panorama). It might be worth adding
906 // onSessionCanceled or the like this interface.
907 if (newData == null) {
908 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri);
912 final int pos = mDataAdapter.findByContentUri(sessionUri);
914 // We do not have a placeholder for this image, perhaps
915 // due to the activity crashing or being killed.
916 mDataAdapter.addOrUpdate(newData);
918 // Make the PhotoItem aware of the session placeholder, to
919 // allow it to make a smooth transition to its content if it
920 // the session item is currently visible.
921 FilmstripItem oldSessionData = mDataAdapter.getFilmstripItemAt(pos);
922 if (mCameraAppUI.getFilmstripVisibility() == View.VISIBLE
923 && mFilmstripController.isVisible(oldSessionData)) {
924 Log.v(TAG, "session item visible, setting transition placeholder");
925 newData.setSessionPlaceholderBitmap(
926 Storage.getPlaceholderForSession(sessionUri));
928 mDataAdapter.updateItemAt(pos, newData);
933 public void onSessionProgress(final Uri uri, final int progress) {
935 // Do nothing, there is no task for this URI.
938 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
939 if (currentIndex == -1) {
943 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
944 updateSessionProgress(progress);
949 public void onSessionProgressText(final Uri uri, final int messageId) {
950 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
951 if (currentIndex == -1) {
955 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
956 updateSessionProgressText(messageId);
961 public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
962 // Don't show capture indicator in Photo Sphere.
963 final int photosphereModuleId = getApplicationContext().getResources()
965 R.integer.camera_mode_photosphere);
966 if (mCurrentModeIndex == photosphereModuleId) {
969 indicateCapture(indicator, rotationDegrees);
973 public void onSessionFailed(Uri uri, int failureMessageId,
974 boolean removeFromFilmstrip) {
975 Log.v(TAG, "onSessionFailed:" + uri);
977 int failedIndex = mDataAdapter.findByContentUri(uri);
978 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
980 if (currentIndex == failedIndex) {
981 updateSessionProgress(0);
982 showProcessError(failureMessageId);
983 mDataAdapter.refresh(uri);
985 if (removeFromFilmstrip) {
986 mFatalErrorHandler.onMediaStorageFailure();
987 mDataAdapter.removeAt(failedIndex);
992 public void onSessionThumbnailUpdate(Bitmap bitmap) {
996 public void onSessionPictureDataUpdate(byte[] pictureData, int orientation) {
1001 public Context getAndroidContext() {
1006 public OneCameraFeatureConfig getCameraFeatureConfig() {
1007 return mFeatureConfig;
1011 public Dialog createDialog() {
1012 return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
1016 public void launchActivityByIntent(Intent intent) {
1017 // Starting from L, we prefer not to start edit activity within camera's task.
1018 mResetToPreviewOnResume = false;
1019 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
1021 startActivity(intent);
1025 public int getCurrentModuleIndex() {
1026 return mCurrentModeIndex;
1030 public String getModuleScope() {
1031 ModuleAgent agent = mModuleManager.getModuleAgent(mCurrentModeIndex);
1032 return SettingsManager.getModuleSettingScope(agent.getScopeNamespace());
1036 public String getCameraScope() {
1037 // if an unopen camera i.e. negative ID is returned, which we've observed in
1038 // some automated scenarios, just return it as a valid separate scope
1039 // this could cause user issues, so log a stack trace noting the call path
1040 // which resulted in this scenario.
1042 return SettingsManager.getCameraSettingScope(
1043 mCameraController.getCurrentCameraId().getValue());
1047 public ModuleController getCurrentModuleController() {
1048 return mCurrentModule;
1052 public int getQuickSwitchToModuleId(int currentModuleIndex) {
1053 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
1058 public SurfaceTexture getPreviewBuffer() {
1059 // TODO: implement this
1064 public void onPreviewReadyToStart() {
1065 mCameraAppUI.onPreviewReadyToStart();
1069 public void onPreviewStarted() {
1070 mCameraAppUI.onPreviewStarted();
1074 public void addPreviewAreaSizeChangedListener(
1075 PreviewStatusListener.PreviewAreaChangedListener listener) {
1076 mCameraAppUI.addPreviewAreaChangedListener(listener);
1080 public void removePreviewAreaSizeChangedListener(
1081 PreviewStatusListener.PreviewAreaChangedListener listener) {
1082 mCameraAppUI.removePreviewAreaChangedListener(listener);
1086 public void setupOneShotPreviewListener() {
1087 mCameraController.setOneShotPreviewCallback(mMainHandler,
1088 new CameraAgent.CameraPreviewDataCallback() {
1090 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) {
1091 mCurrentModule.onPreviewInitialDataReceived();
1092 mCameraAppUI.onNewPreviewFrame();
1099 public void updatePreviewAspectRatio(float aspectRatio) {
1100 mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
1104 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
1105 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio);
1109 public RectF getFullscreenRect() {
1110 return mCameraAppUI.getFullscreenRect();
1114 public void updatePreviewTransform(Matrix matrix) {
1115 mCameraAppUI.updatePreviewTransform(matrix);
1119 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1120 mCameraAppUI.setPreviewStatusListener(previewStatusListener);
1124 public FrameLayout getModuleLayoutRoot() {
1125 return mCameraAppUI.getModuleRootView();
1129 public void setShutterEventsListener(ShutterEventsListener listener) {
1130 // TODO: implement this
1134 public void setShutterEnabled(boolean enabled) {
1135 mCameraAppUI.setShutterButtonEnabled(enabled);
1139 public boolean isShutterEnabled() {
1140 return mCameraAppUI.isShutterButtonEnabled();
1144 public void startFlashAnimation(boolean shortFlash) {
1145 mCameraAppUI.startFlashAnimation(shortFlash);
1149 public void startPreCaptureAnimation() {
1150 // TODO: implement this
1154 public void cancelPreCaptureAnimation() {
1155 // TODO: implement this
1159 public void startPostCaptureAnimation() {
1160 // TODO: implement this
1164 public void startPostCaptureAnimation(Bitmap thumbnail) {
1165 // TODO: implement this
1169 public void cancelPostCaptureAnimation() {
1170 // TODO: implement this
1174 public OrientationManager getOrientationManager() {
1175 return mOrientationManager;
1179 public LocationManager getLocationManager() {
1180 return mLocationManager;
1184 public void lockOrientation() {
1185 if (mOrientationManager != null) {
1186 mOrientationManager.lockOrientation();
1191 public void unlockOrientation() {
1192 if (mOrientationManager != null) {
1193 mOrientationManager.unlockOrientation();
1198 * If not in filmstrip, this shows the capture indicator.
1200 private void indicateCapture(final Bitmap indicator, final int rotationDegrees) {
1201 if (mFilmstripVisible) {
1205 // Don't show capture indicator in Photo Sphere.
1206 // TODO: Don't reach into resources to figure out the current mode.
1207 final int photosphereModuleId = getApplicationContext().getResources().getInteger(
1208 R.integer.camera_mode_photosphere);
1209 if (mCurrentModeIndex == photosphereModuleId) {
1213 mMainHandler.post(new Runnable() {
1216 mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule
1217 .getPeekAccessibilityString());
1218 mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees);
1224 public void notifyNewMedia(Uri uri) {
1225 // TODO: This method is running on the main thread. Also we should get
1226 // rid of that AsyncTask.
1228 updateStorageSpaceAndHint(null);
1229 ContentResolver cr = getContentResolver();
1230 String mimeType = cr.getType(uri);
1231 FilmstripItem newData = null;
1232 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
1233 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1234 newData = mVideoItemFactory.queryContentUri(uri);
1235 if (newData == null) {
1236 Log.e(TAG, "Can't find video data in content resolver:" + uri);
1239 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
1240 CameraUtil.broadcastNewPicture(mAppContext, uri);
1241 newData = mPhotoItemFactory.queryContentUri(uri);
1242 if (newData == null) {
1243 Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1247 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1251 // We are preloading the metadata for new video since we need the
1252 // rotation info for the thumbnail.
1253 new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
1255 protected FilmstripItem doInBackground(FilmstripItem... params) {
1256 FilmstripItem data = params[0];
1257 MetadataLoader.loadMetadata(getAndroidContext(), data);
1262 protected void onPostExecute(final FilmstripItem data) {
1263 // TODO: Figure out why sometimes the data is aleady there.
1264 mDataAdapter.addOrUpdate(data);
1266 // Legacy modules don't use CaptureSession, so we show the capture indicator when
1267 // the item was safed.
1268 if (mCurrentModule instanceof PhotoModule ||
1269 mCurrentModule instanceof VideoModule) {
1270 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1273 final Optional<Bitmap> bitmap = data.generateThumbnail(
1274 mAboveFilmstripControlLayout.getWidth(),
1275 mAboveFilmstripControlLayout.getMeasuredHeight());
1276 if (bitmap.isPresent()) {
1277 indicateCapture(bitmap.get(), 0);
1283 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
1287 public void enableKeepScreenOn(boolean enabled) {
1292 mKeepScreenOn = enabled;
1293 if (mKeepScreenOn) {
1294 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1295 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1297 keepScreenOnForAWhile();
1302 public CameraProvider getCameraProvider() {
1303 return mCameraController;
1307 public OneCameraOpener getCameraOpener() {
1308 return mOneCameraOpener;
1311 private void removeItemAt(int index) {
1312 mDataAdapter.removeAt(index);
1313 if (mDataAdapter.getTotalNumber() > 1) {
1314 showUndoDeletionBar();
1316 // If camera preview is the only view left in filmstrip,
1317 // no need to show undo bar.
1318 mPendingDeletion = true;
1320 if (mFilmstripVisible) {
1321 mCameraAppUI.getFilmstripContentPanel().animateHide();
1327 public boolean onOptionsItemSelected(MenuItem item) {
1328 // Handle presses on the action bar items
1329 switch (item.getItemId()) {
1330 case android.R.id.home:
1333 case R.id.action_details:
1334 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex());
1336 case R.id.action_help_and_feedback:
1337 mResetToPreviewOnResume = false;
1338 new GoogleHelpHelper(this).launchGoogleHelp();
1341 return super.onOptionsItemSelected(item);
1345 private boolean isCaptureIntent() {
1346 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1347 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1348 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1356 * Note: Make sure this callback is unregistered properly when the activity
1357 * is destroyed since we're otherwise leaking the Activity reference.
1359 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
1360 = new CameraExceptionHandler.CameraExceptionCallback() {
1362 public void onCameraError(int errorCode) {
1363 // Not a fatal error. only do Log.e().
1364 Log.e(TAG, "Camera error callback. error=" + errorCode);
1367 public void onCameraException(
1368 RuntimeException ex, String commandHistory, int action, int state) {
1369 Log.e(TAG, "Camera Exception", ex);
1370 UsageStatistics.instance().cameraFailure(
1371 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
1372 commandHistory, action, state);
1376 public void onDispatchThreadException(RuntimeException ex) {
1377 Log.e(TAG, "DispatchThread Exception", ex);
1378 UsageStatistics.instance().cameraFailure(
1379 eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
1380 null, UsageStatistics.NONE, UsageStatistics.NONE);
1383 private void onFatalError() {
1384 if (mCameraFatalError) {
1387 mCameraFatalError = true;
1389 // If the activity receives exception during onPause, just exit the app.
1390 if (mPaused && !isFinishing()) {
1391 Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
1394 mFatalErrorHandler.handleFatalError(FatalErrorHandler.Reason.CANNOT_CONNECT_TO_CAMERA);
1400 public void onNewIntentTasks(Intent intent) {
1401 onModeSelected(getModeIndex());
1405 public void onCreateTasks(Bundle state) {
1406 Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start();
1407 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1408 mOnCreateTime = System.currentTimeMillis();
1409 mAppContext = getApplicationContext();
1410 mMainHandler = new MainHandler(this, getMainLooper());
1411 mLocationManager = new LocationManager(mAppContext);
1412 mOrientationManager = new OrientationManagerImpl(this, mMainHandler);
1413 mSettingsManager = getServices().getSettingsManager();
1414 mSoundPlayer = new SoundPlayer(mAppContext);
1415 mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver(),
1416 getServices().getMemoryManager());
1417 mFatalErrorHandler = new FatalErrorHandlerImpl(this);
1420 if (!Glide.isSetup()) {
1421 Context context = getAndroidContext();
1422 Glide.setup(new GlideBuilder(context)
1423 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888)
1424 .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
1426 Glide glide = Glide.get(context);
1428 // As a camera we will use a large amount of memory
1429 // for displaying images.
1430 glide.setMemoryCategory(MemoryCategory.HIGH);
1432 profile.mark("Glide.setup");
1434 mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance();
1436 mOneCameraOpener = OneCameraModule.provideOneCameraOpener(
1439 mActiveCameraDeviceTracker,
1440 ResolutionUtil.getDisplayMetrics(this));
1441 mOneCameraManager = OneCameraModule.provideOneCameraManager();
1442 } catch (OneCameraException e) {
1443 // Log error and continue start process while showing error dialog..
1444 Log.e(TAG, "Creating camera manager failed.", e);
1445 mFatalErrorHandler.onGenericCameraAccessFailure();
1447 profile.mark("OneCameraManager.get");
1449 mCameraController = new CameraController(mAppContext, this, mMainHandler,
1450 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1451 CameraAgentFactory.CameraApi.API_1),
1452 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1453 CameraAgentFactory.CameraApi.AUTO),
1454 mActiveCameraDeviceTracker);
1455 mCameraController.setCameraExceptionHandler(
1456 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
1458 // TODO: Try to move all the resources allocation to happen as soon as
1459 // possible so we can call module.init() at the earliest time.
1460 mModuleManager = new ModuleManagerImpl();
1462 ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig);
1464 AppUpgrader appUpgrader = new AppUpgrader(this);
1465 appUpgrader.upgrade(mSettingsManager);
1467 // Make sure the picture sizes are correctly cached for the current OS
1470 (new PictureSizeLoader(mAppContext)).computePictureSizes();
1471 profile.mark("computePictureSizes");
1472 Keys.setDefaults(mSettingsManager, mAppContext);
1474 mResolutionSetting = new ResolutionSetting(mSettingsManager, mOneCameraManager,
1475 getContentResolver());
1477 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1478 // We suppress this flag via theme when drawing the system preview
1479 // background, but once we create activity here, reactivate to the
1480 // default value. The default is important for L, we don't want to
1481 // change app behavior, just starting background drawable layout.
1482 if (ApiHelper.isLOrHigher()) {
1483 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1487 setContentView(R.layout.activity_main);
1488 profile.mark("setContentView()");
1489 // A window background is set in styles.xml for the system to show a
1490 // drawable background with gray color and camera icon before the
1491 // activity is created. We set the background to null here to prevent
1492 // overdraw, all views must take care of drawing backgrounds if
1493 // necessary. This call to setBackgroundDrawable must occur after
1494 // setContentView, otherwise a background may be set again from the
1496 getWindow().setBackgroundDrawable(null);
1498 mActionBar = getActionBar();
1499 // set actionbar background to 100% or 50% transparent
1500 if (ApiHelper.isLOrHigher()) {
1501 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000));
1503 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
1506 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1507 mModeListView.init(mModuleManager.getSupportedModeIndexList());
1508 if (ApiHelper.HAS_ROTATION_ANIMATION) {
1509 setRotationAnimation();
1511 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1513 public void onVisibilityChanged(boolean visible) {
1514 mModeListVisible = visible;
1515 mCameraAppUI.setShutterButtonImportantToA11y(!visible);
1516 updatePreviewVisibility();
1520 // Check if this is in the secure camera mode.
1521 Intent intent = getIntent();
1522 String action = intent.getAction();
1523 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1524 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1525 mSecureCamera = true;
1527 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1530 if (mSecureCamera) {
1531 // Change the window flags so that secure camera can show when
1533 Window win = getWindow();
1534 WindowManager.LayoutParams params = win.getAttributes();
1535 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1536 win.setAttributes(params);
1538 // Filter for screen off so that we can finish secure camera
1539 // activity when screen is off.
1540 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1541 registerReceiver(mShutdownReceiver, filter_screen_off);
1543 // Filter for phone unlock so that we can finish secure camera
1544 // via this UI path:
1545 // 1. from secure lock screen, user starts secure camera
1546 // 2. user presses home button
1547 // 3. user unlocks phone
1548 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
1549 registerReceiver(mShutdownReceiver, filter_user_unlock);
1551 mCameraAppUI = new CameraAppUI(this,
1552 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1554 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1556 mAboveFilmstripControlLayout =
1557 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1559 // Add the session listener so we can track the session progress
1561 getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1562 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1563 mFilmstripController.setImageGap(
1564 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1565 profile.mark("Configure Camera UI");
1567 mPanoramaViewHelper = new PanoramaViewHelper(this);
1568 mPanoramaViewHelper.onCreate();
1570 ContentResolver appContentResolver = mAppContext.getContentResolver();
1571 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
1572 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
1573 new PhotoDataFactory());
1574 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
1575 new VideoDataFactory());
1576 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
1577 mPhotoItemFactory, mVideoItemFactory);
1578 mDataAdapter.setLocalDataListener(mFilmstripItemListener);
1580 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1583 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1584 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1585 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1586 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1589 setModuleFromModeIndex(getModeIndex());
1592 mCameraAppUI.prepareModuleUI();
1593 profile.mark("Init Current Module UI");
1594 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1595 profile.mark("Init CurrentModule");
1597 if (!mSecureCamera) {
1598 mFilmstripController.setDataAdapter(mDataAdapter);
1599 if (!isCaptureIntent()) {
1600 mDataAdapter.requestLoad(new Callback<Void>() {
1602 public void onCallback(Void result) {
1603 fillTemporarySessions();
1608 // Put a lock placeholder as the last image by setting its date to
1610 ImageView v = (ImageView) getLayoutInflater().inflate(
1611 R.layout.secure_album_placeholder, null);
1612 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
1613 v.setOnClickListener(new View.OnClickListener() {
1615 public void onClick(View view) {
1616 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1617 NavigationChange.InteractionCause.BUTTON);
1622 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1623 mDataAdapter = new FixedLastProxyAdapter(
1626 new PlaceholderItem(
1628 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
1629 v.getDrawable().getIntrinsicWidth(),
1630 v.getDrawable().getIntrinsicHeight()));
1631 // Flush out all the original data.
1632 mDataAdapter.clear();
1633 mFilmstripController.setDataAdapter(mDataAdapter);
1638 mLocalImagesObserver = new FilmstripContentObserver();
1639 mLocalVideosObserver = new FilmstripContentObserver();
1641 getContentResolver().registerContentObserver(
1642 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1643 mLocalImagesObserver);
1644 getContentResolver().registerContentObserver(
1645 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1646 mLocalVideosObserver);
1648 mMemoryManager = getServices().getMemoryManager();
1650 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1653 HashMap memoryData = mMemoryManager.queryMemory();
1654 UsageStatistics.instance().reportMemoryConsumed(memoryData,
1655 MemoryQuery.REPORT_LABEL_LAUNCH);
1659 mMotionManager = getServices().getMotionManager();
1661 mFirstRunDialog = new FirstRunDialog(this,
1662 getAndroidContext(),
1666 new FirstRunDialog.FirstRunDialogListener() {
1668 public void onFirstRunStateReady() {
1669 // Make sure additional preferences have the correct resolution selected
1670 CameraSettingsActivityHelper.verifyDefaults(getSettingsManager(),
1671 getAndroidContext());
1673 // Run normal resume tasks.
1678 public void onFirstRunDialogCancelled() {
1679 // App isn't functional until users finish first run dialog.
1680 // We need to finish here since users hit back button during
1681 // first run dialog (b/19593942).
1686 public void onCameraAccessException() {
1687 mFatalErrorHandler.onGenericCameraAccessFailure();
1694 * Get the current mode index from the Intent or from persistent
1697 private int getModeIndex() {
1699 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1700 int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1701 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1702 int captureIntentIndex =
1703 getResources().getInteger(R.integer.camera_mode_capture_intent);
1704 String intentAction = getIntent().getAction();
1705 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction)
1706 || MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) {
1707 modeIndex = videoIndex;
1708 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction)
1709 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1711 modeIndex = captureIntentIndex;
1712 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction)
1713 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction)
1714 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1715 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1716 Keys.KEY_CAMERA_MODULE_LAST_USED);
1718 // For upgraders who have not seen the aspect ratio selection screen,
1719 // we need to drop them back in the photo module and have them select
1721 // TODO: Move this to SettingsManager as an upgrade procedure.
1722 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1723 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
1724 modeIndex = photoIndex;
1727 // If the activity has not been started using an explicit intent,
1728 // read the module index from the last time the user changed modes
1729 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1730 Keys.KEY_STARTUP_MODULE_INDEX);
1731 if ((modeIndex == gcamIndex &&
1732 !GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) || modeIndex < 0) {
1733 modeIndex = photoIndex;
1740 * Call this whenever the mode drawer or filmstrip change the visibility
1743 private void updatePreviewVisibility() {
1744 if (mCurrentModule == null) {
1748 int visibility = getPreviewVisibility();
1749 mCameraAppUI.onPreviewVisiblityChanged(visibility);
1750 updatePreviewRendering(visibility);
1751 mCurrentModule.onPreviewVisibilityChanged(visibility);
1754 private void updatePreviewRendering(int visibility) {
1755 if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1756 mCameraAppUI.pausePreviewRendering();
1758 mCameraAppUI.resumePreviewRendering();
1762 private int getPreviewVisibility() {
1763 if (mFilmstripCoversPreview) {
1764 return ModuleController.VISIBILITY_HIDDEN;
1765 } else if (mModeListVisible){
1766 return ModuleController.VISIBILITY_COVERED;
1768 return ModuleController.VISIBILITY_VISIBLE;
1772 private void setRotationAnimation() {
1773 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1774 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1775 Window win = getWindow();
1776 WindowManager.LayoutParams winParams = win.getAttributes();
1777 winParams.rotationAnimation = rotationAnimation;
1778 win.setAttributes(winParams);
1782 public void onUserInteraction() {
1783 super.onUserInteraction();
1784 if (!isFinishing()) {
1785 keepScreenOnForAWhile();
1790 public boolean dispatchTouchEvent(MotionEvent ev) {
1791 boolean result = super.dispatchTouchEvent(ev);
1792 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1793 // Real deletion is postponed until the next user interaction after
1794 // the gesture that triggers deletion. Until real deletion is
1795 // performed, users can click the undo button to bring back the
1796 // image that they chose to delete.
1797 if (mPendingDeletion && !mIsUndoingDeletion) {
1805 public void onPauseTasks() {
1806 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1807 Profile profile = mProfiler.create("CameraActivity.onPause").start();
1810 * Save the last module index after all secure camera and icon launches,
1811 * not just on mode switches.
1813 * Right now we exclude capture intents from this logic, because we also
1814 * ignore the cross-Activity recovery logic in onStart for capture intents.
1816 if (!isCaptureIntent()) {
1817 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
1818 Keys.KEY_STARTUP_MODULE_INDEX,
1823 mCameraAppUI.hideCaptureIndicator();
1824 mFirstRunDialog.dismiss();
1826 // Delete photos that are pending deletion
1828 mCurrentModule.pause();
1829 mOrientationManager.pause();
1830 mPanoramaViewHelper.onPause();
1832 mLocalImagesObserver.setForegroundChangeListener(null);
1833 mLocalImagesObserver.setActivityPaused(true);
1834 mLocalVideosObserver.setActivityPaused(true);
1835 mPreloader.cancelAllLoads();
1838 mMotionManager.stop();
1840 // Always stop recording location when paused. Resume will start
1841 // location recording again if the location setting is on.
1842 mLocationManager.recordLocation(false);
1844 UsageStatistics.instance().backgrounded();
1846 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
1847 // button. Let's just kill the process.
1848 if (mCameraFatalError && !isFinishing()) {
1849 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
1852 // Close the camera and wait for the operation done.
1853 Log.v(TAG, "onPause closing camera");
1854 mCameraController.closeCamera(true);
1861 public void onResumeTasks() {
1864 // Show the dialog if necessary. The rest resume logic will be invoked
1865 // at the onFirstRunStateReady() callback.
1866 mFirstRunDialog.showIfNecessary();
1869 private void resume() {
1870 Profile profile = mProfiler.create("CameraActivity.resume").start();
1871 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1872 Log.v(TAG, "Build info: " + Build.DISPLAY);
1874 updateStorageSpaceAndHint(null);
1876 mLastLayoutOrientation = getResources().getConfiguration().orientation;
1878 // TODO: Handle this in OrientationManager.
1880 if (Settings.System.getInt(getContentResolver(),
1881 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1882 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1883 mAutoRotateScreen = false;
1885 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1886 mAutoRotateScreen = true;
1889 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and
1890 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
1891 // lockscreen onResume->onPause->onResume sequence.
1893 String action = getIntent().getAction();
1894 if (action == null) {
1895 source = ForegroundSource.UNKNOWN_SOURCE;
1898 case MediaStore.ACTION_IMAGE_CAPTURE:
1899 source = ForegroundSource.ACTION_IMAGE_CAPTURE;
1901 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
1902 // was UNKNOWN_SOURCE in Fishlake.
1903 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
1905 case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
1906 // was UNKNOWN_SOURCE in Fishlake.
1907 source = ForegroundSource.ACTION_VIDEO_CAMERA;
1909 case MediaStore.ACTION_VIDEO_CAPTURE:
1910 source = ForegroundSource.ACTION_VIDEO_CAPTURE;
1912 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
1913 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
1914 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
1916 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
1917 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
1919 case Intent.ACTION_MAIN:
1920 source = ForegroundSource.ACTION_MAIN;
1923 source = ForegroundSource.UNKNOWN_SOURCE;
1927 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode(),
1928 isKeyguardSecure(), isKeyguardLocked(),
1929 mStartupOnCreate, mExecutionStartNanoTime);
1931 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
1932 if (ApiHelper.isLOrHigher()) {
1933 // hide the up affordance for L devices, it's not very Materially
1934 mActionBar.setDisplayShowHomeEnabled(false);
1937 mOrientationManager.resume();
1939 mCurrentModule.hardResetSettings(mSettingsManager);
1942 mCurrentModule.resume();
1943 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
1944 NavigationChange.InteractionCause.BUTTON);
1945 setSwipingEnabled(true);
1946 profile.mark("mCurrentModule.resume");
1948 if (!mResetToPreviewOnResume) {
1949 FilmstripItem item = mDataAdapter.getItemAt(
1950 mFilmstripController.getCurrentAdapterIndex());
1952 mDataAdapter.refresh(item.getData().getUri());
1956 // The share button might be disabled to avoid double tapping.
1957 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1958 // Default is showing the preview, unless disabled by explicitly
1959 // starting an activity we want to return from to the filmstrip rather
1960 // than the preview.
1961 mResetToPreviewOnResume = true;
1963 if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1964 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1965 if (!mSecureCamera) {
1966 // If it's secure camera, requestLoad() should not be called
1967 // as it will load all the data.
1968 if (!mFilmstripVisible) {
1969 mDataAdapter.requestLoad(new Callback<Void>() {
1971 public void onCallback(Void result) {
1972 fillTemporarySessions();
1976 mDataAdapter.requestLoadNewPhotos();
1980 mLocalImagesObserver.setActivityPaused(false);
1981 mLocalVideosObserver.setActivityPaused(false);
1982 if (!mSecureCamera) {
1983 mLocalImagesObserver.setForegroundChangeListener(
1984 new FilmstripContentObserver.ChangeListener() {
1986 public void onChange() {
1987 mDataAdapter.requestLoadNewPhotos();
1992 keepScreenOnForAWhile();
1994 // Lights-out mode at all times.
1995 final View rootView = findViewById(R.id.activity_root_view);
1996 mLightsOutRunnable.run();
1997 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
1998 new OnSystemUiVisibilityChangeListener() {
2000 public void onSystemUiVisibilityChange(int visibility) {
2001 mMainHandler.removeCallbacks(mLightsOutRunnable);
2002 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
2007 mPanoramaViewHelper.onResume();
2008 profile.mark("mPanoramaViewHelper.onResume()");
2010 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
2011 // Enable location recording if the setting is on.
2012 final boolean locationRecordingEnabled =
2013 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
2014 mLocationManager.recordLocation(locationRecordingEnabled);
2016 final int previewVisibility = getPreviewVisibility();
2017 updatePreviewRendering(previewVisibility);
2019 mMotionManager.start();
2023 private void fillTemporarySessions() {
2024 if (mSecureCamera) {
2027 // There might be sessions still in flight (processed by our service).
2028 // Make sure they're added to the filmstrip.
2029 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
2033 public void onStartTasks() {
2034 mIsActivityRunning = true;
2035 mPanoramaViewHelper.onStart();
2038 * If we're starting after launching a different Activity (lockscreen),
2039 * we need to use the last mode used in the other Activity, and
2040 * not the old one from this Activity.
2042 * This needs to happen before CameraAppUI.resume() in order to set the
2043 * mode cover icon to the actual last mode used.
2045 * Right now we exclude capture intents from this logic.
2047 int modeIndex = getModeIndex();
2048 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
2049 onModeSelected(modeIndex);
2052 if (mResetToPreviewOnResume) {
2053 mCameraAppUI.resume();
2054 mResetToPreviewOnResume = false;
2059 protected void onStopTasks() {
2060 mIsActivityRunning = false;
2061 mPanoramaViewHelper.onStop();
2063 mLocationManager.disconnect();
2067 public void onDestroyTasks() {
2068 if (mSecureCamera) {
2069 unregisterReceiver(mShutdownReceiver);
2072 mSettingsManager.removeAllListeners();
2073 mCameraController.removeCallbackReceiver();
2074 mCameraController.setCameraExceptionHandler(null);
2075 getContentResolver().unregisterContentObserver(mLocalImagesObserver);
2076 getContentResolver().unregisterContentObserver(mLocalVideosObserver);
2077 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
2078 mCameraAppUI.onDestroy();
2079 mModeListView.setVisibilityChangedListener(null);
2080 mCameraController = null;
2081 mSettingsManager = null;
2082 mOrientationManager = null;
2083 mButtonManager = null;
2084 mSoundPlayer.release();
2085 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
2086 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
2090 public void onConfigurationChanged(Configuration config) {
2091 super.onConfigurationChanged(config);
2092 Log.v(TAG, "onConfigurationChanged");
2093 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
2097 if (mLastLayoutOrientation != config.orientation) {
2098 mLastLayoutOrientation = config.orientation;
2099 mCurrentModule.onLayoutOrientationChanged(
2100 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
2105 public boolean onKeyDown(int keyCode, KeyEvent event) {
2106 if (!mFilmstripVisible) {
2107 if (mCurrentModule.onKeyDown(keyCode, event)) {
2110 // Prevent software keyboard or voice search from showing up.
2111 if (keyCode == KeyEvent.KEYCODE_SEARCH
2112 || keyCode == KeyEvent.KEYCODE_MENU) {
2113 if (event.isLongPress()) {
2119 return super.onKeyDown(keyCode, event);
2123 public boolean onKeyUp(int keyCode, KeyEvent event) {
2124 if (!mFilmstripVisible) {
2125 // If a module is in the middle of capture, it should
2126 // consume the key event.
2127 if (mCurrentModule.onKeyUp(keyCode, event)) {
2129 } else if (keyCode == KeyEvent.KEYCODE_MENU
2130 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2131 // Let the mode list view consume the event.
2132 mCameraAppUI.openModeList();
2134 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2135 mCameraAppUI.showFilmstrip();
2139 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2140 mFilmstripController.goToNextItem();
2142 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2143 boolean wentToPrevious = mFilmstripController.goToPreviousItem();
2144 if (!wentToPrevious) {
2145 // at beginning of filmstrip, hide and go back to preview
2146 mCameraAppUI.hideFilmstrip();
2151 return super.onKeyUp(keyCode, event);
2155 public void onBackPressed() {
2156 if (!mCameraAppUI.onBackPressed()) {
2157 if (!mCurrentModule.onBackPressed()) {
2158 super.onBackPressed();
2164 public boolean isAutoRotateScreen() {
2165 // TODO: Move to OrientationManager.
2166 return mAutoRotateScreen;
2170 public boolean onCreateOptionsMenu(Menu menu) {
2171 MenuInflater inflater = getMenuInflater();
2172 inflater.inflate(R.menu.filmstrip_menu, menu);
2173 mActionBarMenu = menu;
2175 // add a button for launching the gallery
2176 if (mGalleryIntent != null) {
2177 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
2178 if (appName != null) {
2179 MenuItem menuItem = menu.add(appName);
2180 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2181 menuItem.setIntent(mGalleryIntent);
2183 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
2184 if (galleryLogo != null) {
2185 menuItem.setIcon(galleryLogo);
2190 return super.onCreateOptionsMenu(menu);
2194 public boolean onPrepareOptionsMenu(Menu menu) {
2195 if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
2196 // Compatibility pre-L: launching new activities right above
2197 // lockscreen does not reliably work, only show help if not secure
2198 menu.removeItem(R.id.action_help_and_feedback);
2201 return super.onPrepareOptionsMenu(menu);
2204 protected long getStorageSpaceBytes() {
2205 synchronized (mStorageSpaceLock) {
2206 return mStorageSpaceBytes;
2210 protected interface OnStorageUpdateDoneListener {
2211 public void onStorageUpdateDone(long bytes);
2214 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
2216 * We execute disk operations on a background thread in order to
2217 * free up the UI thread. Synchronizing on the lock below ensures
2218 * that when getStorageSpaceBytes is called, the main thread waits
2219 * until this method has completed.
2221 * However, .execute() does not ensure this execution block will be
2222 * run right away (.execute() schedules this AsyncTask for sometime
2223 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
2224 * tries to execute the task in parellel with other AsyncTasks, but
2225 * there's still no guarantee).
2226 * e.g. don't call this then immediately call getStorageSpaceBytes().
2227 * Instead, pass in an OnStorageUpdateDoneListener.
2229 (new AsyncTask<Void, Void, Long>() {
2231 protected Long doInBackground(Void ... arg) {
2232 synchronized (mStorageSpaceLock) {
2233 mStorageSpaceBytes = Storage.getAvailableSpace();
2234 return mStorageSpaceBytes;
2239 protected void onPostExecute(Long bytes) {
2240 updateStorageHint(bytes);
2241 // This callback returns after I/O to check disk, so we could be
2242 // pausing and shutting down. If so, don't bother invoking.
2243 if (callback != null && !mPaused) {
2244 callback.onStorageUpdateDone(bytes);
2246 Log.v(TAG, "ignoring storage callback after activity pause");
2249 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2252 protected void updateStorageHint(long storageSpace) {
2253 if (!mIsActivityRunning) {
2257 String message = null;
2258 if (storageSpace == Storage.UNAVAILABLE) {
2259 message = getString(R.string.no_storage);
2260 } else if (storageSpace == Storage.PREPARING) {
2261 message = getString(R.string.preparing_sd);
2262 } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2263 message = getString(R.string.access_sd_fail);
2264 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2265 message = getString(R.string.spaceIsLow_content);
2268 if (message != null) {
2269 Log.w(TAG, "Storage warning: " + message);
2270 if (mStorageHint == null) {
2271 mStorageHint = OnScreenHint.makeText(message);
2273 mStorageHint.setText(message);
2275 mStorageHint.show();
2276 UsageStatistics.instance().storageWarning(storageSpace);
2278 // Disable all user interactions,
2279 mCameraAppUI.setDisableAllUserInteractions(true);
2280 } else if (mStorageHint != null) {
2281 mStorageHint.cancel();
2282 mStorageHint = null;
2284 // Re-enable all user interactions.
2285 mCameraAppUI.setDisableAllUserInteractions(false);
2289 protected void setResultEx(int resultCode) {
2290 mResultCodeForTesting = resultCode;
2291 setResult(resultCode);
2294 protected void setResultEx(int resultCode, Intent data) {
2295 mResultCodeForTesting = resultCode;
2296 mResultDataForTesting = data;
2297 setResult(resultCode, data);
2300 public int getResultCode() {
2301 return mResultCodeForTesting;
2304 public Intent getResultData() {
2305 return mResultDataForTesting;
2308 public boolean isSecureCamera() {
2309 return mSecureCamera;
2313 public boolean isPaused() {
2318 public int getPreferredChildModeIndex(int modeIndex) {
2319 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2320 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2321 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) {
2322 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2329 public void onModeSelected(int modeIndex) {
2330 if (mCurrentModeIndex == modeIndex) {
2334 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2335 // Record last used camera mode for quick switching
2336 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2337 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2338 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2339 Keys.KEY_CAMERA_MODULE_LAST_USED,
2343 closeModule(mCurrentModule);
2345 // Select the correct module index from the mode switcher index.
2346 modeIndex = getPreferredChildModeIndex(modeIndex);
2347 setModuleFromModeIndex(modeIndex);
2349 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2350 mCameraAppUI.addShutterListener(mCurrentModule);
2351 openModule(mCurrentModule);
2352 // Store the module index so we can use it the next time the Camera
2354 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2355 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2359 * Shows the settings dialog.
2362 public void onSettingsSelected() {
2363 UsageStatistics.instance().controlUsed(
2364 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2365 Intent intent = new Intent(this, CameraSettingsActivity.class);
2366 startActivity(intent);
2370 public void freezeScreenUntilPreviewReady() {
2371 mCameraAppUI.freezeScreenUntilPreviewReady();
2375 public int getModuleId(int modeIndex) {
2376 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2377 if (agent == null) {
2380 return agent.getModuleId();
2384 * Sets the mCurrentModuleIndex, creates a new module instance for the given
2385 * index an sets it as mCurrentModule.
2387 private void setModuleFromModeIndex(int modeIndex) {
2388 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2389 if (agent == null) {
2392 if (!agent.requestAppForCamera()) {
2393 mCameraController.closeCamera(true);
2395 mCurrentModeIndex = agent.getModuleId();
2396 mCurrentModule = (CameraModule) agent.createModule(this, getIntent());
2400 public SettingsManager getSettingsManager() {
2401 return mSettingsManager;
2405 public ResolutionSetting getResolutionSetting() {
2406 return mResolutionSetting;
2410 public CameraServices getServices() {
2411 return CameraServicesImpl.instance();
2415 public FatalErrorHandler getFatalErrorHandler() {
2416 return mFatalErrorHandler;
2419 public List<String> getSupportedModeNames() {
2420 List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2421 List<String> supported = new ArrayList<String>();
2423 for (Integer modeIndex : indices) {
2424 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2425 if (name != null && !name.equals("")) {
2426 supported.add(name);
2433 public ButtonManager getButtonManager() {
2434 if (mButtonManager == null) {
2435 mButtonManager = new ButtonManager(this);
2437 return mButtonManager;
2441 public SoundPlayer getSoundPlayer() {
2442 return mSoundPlayer;
2446 * Launches an ACTION_EDIT intent for the given local data item. If
2447 * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2448 * the user start either the tiny planet editor or another photo editor.
2450 * @param data The data item to edit.
2452 public void launchEditor(FilmstripItem data) {
2453 Intent intent = new Intent(Intent.ACTION_EDIT)
2454 .setDataAndType(data.getData().getUri(), data.getData().getMimeType())
2455 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2457 launchActivityByIntent(intent);
2458 } catch (ActivityNotFoundException e) {
2459 final String msgEditWith = getResources().getString(R.string.edit_with);
2460 launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2465 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2466 super.onCreateContextMenu(menu, v, menuInfo);
2468 MenuInflater inflater = getMenuInflater();
2469 inflater.inflate(R.menu.filmstrip_context_menu, menu);
2473 public boolean onContextItemSelected(MenuItem item) {
2474 switch (item.getItemId()) {
2475 case R.id.tiny_planet_editor:
2476 mMyFilmstripBottomControlListener.onTinyPlanet();
2478 case R.id.photo_editor:
2479 mMyFilmstripBottomControlListener.onEdit();
2486 * Launch the tiny planet editor.
2488 * @param data The data must be a 360 degree stereographically mapped
2489 * panoramic image. It will not be modified, instead a new item
2490 * with the result will be added to the filmstrip.
2492 public void launchTinyPlanetEditor(FilmstripItem data) {
2493 TinyPlanetFragment fragment = new TinyPlanetFragment();
2494 Bundle bundle = new Bundle();
2495 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString());
2496 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle());
2497 fragment.setArguments(bundle);
2498 fragment.show(getFragmentManager(), "tiny_planet");
2502 * Returns what UI mode (capture mode or filmstrip) we are in.
2503 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2505 private int currentUserInterfaceMode() {
2506 int mode = NavigationChange.Mode.UNKNOWN_MODE;
2507 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2508 mode = NavigationChange.Mode.PHOTO_CAPTURE;
2510 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2511 mode = NavigationChange.Mode.VIDEO_CAPTURE;
2513 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2514 mode = NavigationChange.Mode.LENS_BLUR;
2516 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2517 mode = NavigationChange.Mode.HDR_PLUS;
2519 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2520 mode = NavigationChange.Mode.PHOTO_SPHERE;
2522 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2523 mode = NavigationChange.Mode.PANORAMA;
2525 if (mFilmstripVisible) {
2526 mode = NavigationChange.Mode.FILMSTRIP;
2531 private void openModule(CameraModule module) {
2532 module.init(this, isSecureCamera(), isCaptureIntent());
2533 module.hardResetSettings(mSettingsManager);
2534 // Hide accessibility zoom UI by default. Modules will enable it themselves if required.
2535 getCameraAppUI().hideAccessibilityZoomUI();
2538 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2539 NavigationChange.InteractionCause.BUTTON);
2540 updatePreviewVisibility();
2544 private void closeModule(CameraModule module) {
2546 mCameraAppUI.clearModuleUI();
2549 private void performDeletion() {
2550 if (!mPendingDeletion) {
2553 hideUndoDeletionBar(false);
2554 mDataAdapter.executeDeletion();
2557 public void showUndoDeletionBar() {
2558 if (mPendingDeletion) {
2561 Log.v(TAG, "showing undo bar");
2562 mPendingDeletion = true;
2563 if (mUndoDeletionBar == null) {
2564 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2565 mAboveFilmstripControlLayout, true);
2566 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2567 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2568 button.setOnClickListener(new View.OnClickListener() {
2570 public void onClick(View view) {
2571 mDataAdapter.undoDeletion();
2572 hideUndoDeletionBar(true);
2575 // Setting undo bar clickable to avoid touch events going through
2576 // the bar to the buttons (eg. edit button, etc) underneath the bar.
2577 mUndoDeletionBar.setClickable(true);
2578 // When there is user interaction going on with the undo button, we
2579 // do not want to hide the undo bar.
2580 button.setOnTouchListener(new View.OnTouchListener() {
2582 public boolean onTouch(View v, MotionEvent event) {
2583 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2584 mIsUndoingDeletion = true;
2585 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2586 mIsUndoingDeletion = false;
2592 mUndoDeletionBar.setAlpha(0f);
2593 mUndoDeletionBar.setVisibility(View.VISIBLE);
2594 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2597 private void hideUndoDeletionBar(boolean withAnimation) {
2598 Log.v(TAG, "Hiding undo deletion bar");
2599 mPendingDeletion = false;
2600 if (mUndoDeletionBar != null) {
2601 if (withAnimation) {
2602 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2603 .setListener(new Animator.AnimatorListener() {
2605 public void onAnimationStart(Animator animation) {
2610 public void onAnimationEnd(Animator animation) {
2611 mUndoDeletionBar.setVisibility(View.GONE);
2615 public void onAnimationCancel(Animator animation) {
2620 public void onAnimationRepeat(Animator animation) {
2625 mUndoDeletionBar.setVisibility(View.GONE);
2631 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2634 * @param enable {@code true} to enable swipe.
2636 public void setSwipingEnabled(boolean enable) {
2637 // TODO: Bring back the functionality.
2638 if (isCaptureIntent()) {
2639 // lockPreview(true);
2641 // lockPreview(!enable);
2645 // Accessor methods for getting latency times used in performance testing
2646 public long getFirstPreviewTime() {
2647 if (mCurrentModule instanceof PhotoModule) {
2648 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2649 if (coverHiddenTime != -1) {
2650 return coverHiddenTime - mOnCreateTime;
2656 public long getAutoFocusTime() {
2657 return (mCurrentModule instanceof PhotoModule) ?
2658 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2661 public long getShutterLag() {
2662 return (mCurrentModule instanceof PhotoModule) ?
2663 ((PhotoModule) mCurrentModule).mShutterLag : -1;
2666 public long getShutterToPictureDisplayedTime() {
2667 return (mCurrentModule instanceof PhotoModule) ?
2668 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2671 public long getPictureDisplayedToJpegCallbackTime() {
2672 return (mCurrentModule instanceof PhotoModule) ?
2673 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2676 public long getJpegCallbackFinishTime() {
2677 return (mCurrentModule instanceof PhotoModule) ?
2678 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2681 public long getCaptureStartTime() {
2682 return (mCurrentModule instanceof PhotoModule) ?
2683 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2686 public boolean isRecording() {
2687 return (mCurrentModule instanceof VideoModule) ?
2688 ((VideoModule) mCurrentModule).isRecording() : false;
2691 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2692 return mCameraController;
2695 // For debugging purposes only.
2696 public CameraModule getCurrentModule() {
2697 return mCurrentModule;
2701 public void showTutorial(AbstractTutorialOverlay tutorial) {
2702 mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2706 public void finishActivityWithIntentCompleted(Intent resultIntent) {
2707 finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent);
2711 public void finishActivityWithIntentCanceled() {
2712 finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent());
2715 private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) {
2716 mResultCodeForTesting = resultCode;
2717 mResultDataForTesting = resultIntent;
2718 setResult(resultCode, resultIntent);
2722 private void keepScreenOnForAWhile() {
2723 if (mKeepScreenOn) {
2726 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2727 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2728 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2731 private void resetScreenOn() {
2732 mKeepScreenOn = false;
2733 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2734 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2738 * @return {@code true} if the Gallery is launched successfully.
2740 private boolean startGallery() {
2741 if (mGalleryIntent == null) {
2745 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2746 NavigationChange.InteractionCause.BUTTON);
2747 Intent startGalleryIntent = new Intent(mGalleryIntent);
2748 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
2749 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex);
2750 if (currentFilmstripItem != null) {
2751 GalleryHelper.setContentUri(startGalleryIntent,
2752 currentFilmstripItem.getData().getUri());
2754 launchActivityByIntent(startGalleryIntent);
2755 } catch (ActivityNotFoundException e) {
2756 Log.w(TAG, "Failed to launch gallery activity, closing");
2761 private void setNfcBeamPushUriFromData(FilmstripItem data) {
2762 final Uri uri = data.getData().getUri();
2763 if (uri != Uri.EMPTY) {
2764 mNfcPushUris[0] = uri;
2766 mNfcPushUris[0] = null;
2771 * Updates the visibility of the filmstrip bottom controls and action bar.
2773 private void updateUiByData(final int index) {
2774 final FilmstripItem currentData = mDataAdapter.getItemAt(index);
2775 if (currentData == null) {
2776 Log.w(TAG, "Current data ID not found.");
2777 hideSessionProgress();
2780 updateActionBarMenu(currentData);
2782 /* Bottom controls. */
2783 updateBottomControlsByData(currentData);
2785 if (isSecureCamera()) {
2786 // We cannot show buttons in secure camera since go to other
2787 // activities might create a security hole.
2788 mCameraAppUI.getFilmstripBottomControls().hideControls();
2792 setNfcBeamPushUriFromData(currentData);
2794 if (!mDataAdapter.isMetadataUpdatedAt(index)) {
2795 mDataAdapter.updateMetadataAt(index);
2800 * Updates the bottom controls based on the data.
2802 private void updateBottomControlsByData(final FilmstripItem currentData) {
2804 final CameraAppUI.BottomPanel filmstripBottomPanel =
2805 mCameraAppUI.getFilmstripBottomControls();
2806 filmstripBottomPanel.showControls();
2807 filmstripBottomPanel.setEditButtonVisibility(
2808 currentData.getAttributes().canEdit());
2809 filmstripBottomPanel.setShareButtonVisibility(
2810 currentData.getAttributes().canShare());
2811 filmstripBottomPanel.setDeleteButtonVisibility(
2812 currentData.getAttributes().canDelete());
2816 Uri contentUri = currentData.getData().getUri();
2817 CaptureSessionManager sessionManager = getServices()
2818 .getCaptureSessionManager();
2820 if (sessionManager.hasErrorMessage(contentUri)) {
2821 showProcessError(sessionManager.getErrorMessageId(contentUri));
2823 filmstripBottomPanel.hideProgressError();
2824 CaptureSession session = sessionManager.getSession(contentUri);
2826 if (session != null) {
2827 int sessionProgress = session.getProgress();
2829 if (sessionProgress < 0) {
2830 hideSessionProgress();
2832 int progressMessageId = session.getProgressMessageId();
2833 showSessionProgress(progressMessageId);
2834 updateSessionProgress(sessionProgress);
2837 hideSessionProgress();
2843 // We need to add this to a separate DB.
2844 final int viewButtonVisibility;
2845 if (currentData.getMetadata().isUsePanoramaViewer()) {
2846 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2847 } else if (currentData.getMetadata().isHasRgbzData()) {
2848 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2850 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2853 filmstripBottomPanel.setTinyPlanetEnabled(
2854 currentData.getMetadata().isPanorama360());
2855 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2858 private void showDetailsDialog(int index) {
2859 final FilmstripItem data = mDataAdapter.getItemAt(index);
2863 Optional<MediaDetails> details = data.getMediaDetails();
2864 if (!details.isPresent()) {
2867 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get());
2868 detailDialog.show();
2869 UsageStatistics.instance().mediaInteraction(
2870 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS,
2871 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index));
2875 * Show or hide action bar items depending on current data type.
2877 private void updateActionBarMenu(FilmstripItem data) {
2878 if (mActionBarMenu == null) {
2882 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2883 if (detailsMenuItem == null) {
2887 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo();
2888 detailsMenuItem.setVisible(showDetails);