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.CameraUtil;
139 import com.android.camera.util.GalleryHelper;
140 import com.android.camera.util.GcamHelper;
141 import com.android.camera.util.GoogleHelpHelper;
142 import com.android.camera.util.IntentHelper;
143 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
144 import com.android.camera.util.QuickActivity;
145 import com.android.camera.util.ReleaseHelper;
146 import com.android.camera.widget.FilmstripView;
147 import com.android.camera.widget.Preloader;
148 import com.android.camera2.R;
149 import com.android.ex.camera2.portability.CameraAgent;
150 import com.android.ex.camera2.portability.CameraAgentFactory;
151 import com.android.ex.camera2.portability.CameraExceptionHandler;
152 import com.android.ex.camera2.portability.CameraSettings;
153 import com.bumptech.glide.Glide;
154 import com.bumptech.glide.GlideBuilder;
155 import com.bumptech.glide.MemoryCategory;
156 import com.bumptech.glide.load.DecodeFormat;
157 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
158 import com.google.common.base.Optional;
159 import com.google.common.logging.eventprotos;
160 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
161 import com.google.common.logging.eventprotos.MediaInteraction;
162 import com.google.common.logging.eventprotos.NavigationChange;
165 import java.lang.ref.WeakReference;
166 import java.util.ArrayList;
167 import java.util.HashMap;
168 import java.util.List;
170 public class CameraActivity extends QuickActivity
171 implements AppController, CameraAgent.CameraOpenCallback,
172 ShareActionProvider.OnShareTargetSelectedListener {
174 private static final Log.Tag TAG = new Log.Tag("CameraActivity");
176 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
177 "android.media.action.STILL_IMAGE_CAMERA_SECURE";
178 public static final String ACTION_IMAGE_CAPTURE_SECURE =
179 "android.media.action.IMAGE_CAPTURE_SECURE";
181 // The intent extra for camera from secure lock screen. True if the gallery
182 // should only show newly captured pictures. sSecureAlbumId does not
183 // increment. This is used when switching between camera, camcorder, and
184 // panorama. If the extra is not set, it is in the normal camera mode.
185 public static final String SECURE_CAMERA_EXTRA = "secure_camera";
187 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
188 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
189 /** Load metadata for 10 items ahead of our current. */
190 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
192 /** Should be used wherever a context is needed. */
193 private Context mAppContext;
196 * Camera fatal error handling:
197 * 1) Present error dialog to guide users to exit the app.
198 * 2) If users hit home button, onPause should just call finish() to exit the app.
200 private boolean mCameraFatalError = false;
203 * Whether onResume should reset the view to the preview.
205 private boolean mResetToPreviewOnResume = true;
208 * This data adapter is used by FilmStripView.
210 private VideoItemFactory mVideoItemFactory;
211 private PhotoItemFactory mPhotoItemFactory;
212 private LocalFilmstripDataAdapter mDataAdapter;
214 private ActiveCameraDeviceTracker mActiveCameraDeviceTracker;
215 private OneCameraOpener mOneCameraOpener;
216 private OneCameraManager mOneCameraManager;
217 private SettingsManager mSettingsManager;
218 private ResolutionSetting mResolutionSetting;
219 private ModeListView mModeListView;
220 private boolean mModeListVisible = false;
221 private int mCurrentModeIndex;
222 private CameraModule mCurrentModule;
223 private ModuleManagerImpl mModuleManager;
224 private FrameLayout mAboveFilmstripControlLayout;
225 private FilmstripController mFilmstripController;
226 private boolean mFilmstripVisible;
227 /** Whether the filmstrip fully covers the preview. */
228 private boolean mFilmstripCoversPreview = false;
229 private int mResultCodeForTesting;
230 private Intent mResultDataForTesting;
231 private OnScreenHint mStorageHint;
232 private final Object mStorageSpaceLock = new Object();
233 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
234 private boolean mAutoRotateScreen;
235 private boolean mSecureCamera;
236 private OrientationManagerImpl mOrientationManager;
237 private LocationManager mLocationManager;
238 private ButtonManager mButtonManager;
239 private Handler mMainHandler;
240 private PanoramaViewHelper mPanoramaViewHelper;
241 private ActionBar mActionBar;
242 private ViewGroup mUndoDeletionBar;
243 private boolean mIsUndoingDeletion = false;
244 private boolean mIsActivityRunning = false;
245 private FatalErrorHandler mFatalErrorHandler;
247 private final Uri[] mNfcPushUris = new Uri[1];
249 private FilmstripContentObserver mLocalImagesObserver;
250 private FilmstripContentObserver mLocalVideosObserver;
252 private boolean mPendingDeletion = false;
254 private CameraController mCameraController;
255 private boolean mPaused;
256 private CameraAppUI mCameraAppUI;
258 private Intent mGalleryIntent;
259 private long mOnCreateTime;
261 private Menu mActionBarMenu;
262 private Preloader<Integer, AsyncTask> mPreloader;
264 /** Can be used to play custom sounds. */
265 private SoundPlayer mSoundPlayer;
267 /** Holds configuration for various OneCamera features. */
268 private OneCameraFeatureConfig mFeatureConfig;
270 private static final int LIGHTS_OUT_DELAY_MS = 4000;
271 private final int BASE_SYS_UI_VISIBILITY =
272 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
273 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
274 private final Runnable mLightsOutRunnable = new Runnable() {
277 getWindow().getDecorView().setSystemUiVisibility(
278 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE);
281 private MemoryManager mMemoryManager;
282 private MotionManager mMotionManager;
283 private final Profiler mProfiler = Profilers.instance().guard();
285 /** First run dialog */
286 private FirstRunDialog mFirstRunDialog;
289 public CameraAppUI getCameraAppUI() {
294 public ModuleManager getModuleManager() {
295 return mModuleManager;
299 * Close activity when secure app passes lock screen or screen turns
302 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
304 public void onReceive(Context context, Intent intent) {
310 * Whether the screen is kept turned on.
312 private boolean mKeepScreenOn;
313 private int mLastLayoutOrientation;
314 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
315 new CameraAppUI.BottomPanel.Listener() {
318 * If the current photo is a photo sphere, this will launch the
319 * Photo Sphere panorama viewer.
322 public void onExternalViewer() {
323 if (mPanoramaViewHelper == null) {
326 final FilmstripItem data = getCurrentLocalData();
328 Log.w(TAG, "Cannot open null data.");
331 final Uri contentUri = data.getData().getUri();
332 if (contentUri == Uri.EMPTY) {
333 Log.w(TAG, "Cannot open empty URL.");
337 if (data.getMetadata().isUsePanoramaViewer()) {
338 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
339 } else if (data.getMetadata().isHasRgbzData()) {
340 mPanoramaViewHelper.showRgbz(contentUri);
341 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
342 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
343 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
344 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false);
345 mCameraAppUI.clearClingForViewer(
346 CameraAppUI.BottomPanel.VIEWER_REFOCUS);
352 public void onEdit() {
353 FilmstripItem data = getCurrentLocalData();
355 Log.w(TAG, "Cannot edit null data.");
358 final int currentDataId = getCurrentDataId();
359 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
361 MediaInteraction.InteractionType.EDIT,
362 NavigationChange.InteractionCause.BUTTON,
363 fileAgeFromAdapterAtIndex(currentDataId));
368 public void onTinyPlanet() {
369 FilmstripItem data = getCurrentLocalData();
371 Log.w(TAG, "Cannot edit tiny planet on null data.");
374 launchTinyPlanetEditor(data);
378 public void onDelete() {
379 final int currentDataId = getCurrentDataId();
380 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
382 MediaInteraction.InteractionType.DELETE,
383 NavigationChange.InteractionCause.BUTTON,
384 fileAgeFromAdapterAtIndex(currentDataId));
385 removeItemAt(currentDataId);
389 public void onShare() {
390 final FilmstripItem data = getCurrentLocalData();
392 Log.w(TAG, "Cannot share null data.");
396 final int currentDataId = getCurrentDataId();
397 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
399 MediaInteraction.InteractionType.SHARE,
400 NavigationChange.InteractionCause.BUTTON,
401 fileAgeFromAdapterAtIndex(currentDataId));
402 // If applicable, show release information before this item
404 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
405 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
406 new Callback<Void>() {
408 public void onCallback(Void result) {
417 private void share(FilmstripItem data) {
418 Intent shareIntent = getShareIntentByData(data);
419 if (shareIntent != null) {
421 launchActivityByIntent(shareIntent);
422 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
423 } catch (ActivityNotFoundException ex) {
429 private int getCurrentDataId() {
430 return mFilmstripController.getCurrentAdapterIndex();
433 private FilmstripItem getCurrentLocalData() {
434 return mDataAdapter.getItemAt(getCurrentDataId());
438 * Sets up the share intent and NFC properly according to the
441 * @param item The data to be shared.
443 private Intent getShareIntentByData(final FilmstripItem item) {
444 Intent intent = null;
445 final Uri contentUri = item.getData().getUri();
446 final String msgShareTo = getResources().getString(R.string.share_to);
448 if (item.getMetadata().isPanorama360() &&
449 item.getData().getUri() != Uri.EMPTY) {
450 intent = new Intent(Intent.ACTION_SEND);
451 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE);
452 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
453 } else if (item.getAttributes().canShare()) {
454 final String mimeType = item.getData().getMimeType();
455 intent = getShareIntentFromType(mimeType);
456 if (intent != null) {
457 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
458 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
460 intent = Intent.createChooser(intent, msgShareTo);
466 * Get the share intent according to the mimeType
468 * @param mimeType The mimeType of current data.
469 * @return the video/image's ShareIntent or null if mimeType is
472 private Intent getShareIntentFromType(String mimeType) {
473 // Lazily create the intent object.
474 Intent intent = new Intent(Intent.ACTION_SEND);
475 if (mimeType.startsWith("video/")) {
476 intent.setType("video/*");
478 if (mimeType.startsWith("image/")) {
479 intent.setType("image/*");
481 Log.w(TAG, "unsupported mimeType " + mimeType);
488 public void onProgressErrorClicked() {
489 FilmstripItem data = getCurrentLocalData();
490 getServices().getCaptureSessionManager().removeErrorMessage(
491 data.getData().getUri());
492 updateBottomControlsByData(data);
497 public void onCameraOpened(CameraAgent.CameraProxy camera) {
498 Log.v(TAG, "onCameraOpened");
500 // We've paused, but just asynchronously opened the camera. Close it
501 // because we should be releasing the camera when paused to allow
502 // other apps to access it.
503 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera");
504 mCameraController.closeCamera(false);
508 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
509 // We shouldn't be here. Just close the camera and leave.
510 mCameraController.closeCamera(false);
511 throw new IllegalStateException("Camera opened but the module shouldn't be " +
514 if (mCurrentModule != null) {
515 resetExposureCompensationToDefault(camera);
517 mCurrentModule.onCameraAvailable(camera);
518 } catch (RuntimeException ex) {
519 Log.e(TAG, "Error connecting to camera", ex);
520 mFatalErrorHandler.onCameraOpenFailure();
523 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
525 Log.v(TAG, "invoking onChangeCamera");
526 mCameraAppUI.onChangeCamera();
529 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) {
530 // Reset the exposure compensation before handing the camera to module.
531 CameraSettings cameraSettings = camera.getSettings();
532 cameraSettings.setExposureCompensationIndex(0);
533 camera.applySettings(cameraSettings);
537 public void onCameraDisabled(int cameraId) {
538 Log.w(TAG, "Camera disabled: " + cameraId);
539 mFatalErrorHandler.onCameraDisabledFailure();
543 public void onDeviceOpenFailure(int cameraId, String info) {
544 Log.w(TAG, "Camera open failure: " + info);
545 mFatalErrorHandler.onCameraOpenFailure();
549 public void onDeviceOpenedAlready(int cameraId, String info) {
550 Log.w(TAG, "Camera open already: " + cameraId + "," + info);
551 mFatalErrorHandler.onGenericCameraAccessFailure();
555 public void onReconnectionFailure(CameraAgent mgr, String info) {
556 Log.w(TAG, "Camera reconnection failure:" + info);
557 mFatalErrorHandler.onCameraReconnectFailure();
560 private static class MainHandler extends Handler {
561 final WeakReference<CameraActivity> mActivity;
563 public MainHandler(CameraActivity activity, Looper looper) {
565 mActivity = new WeakReference<CameraActivity>(activity);
569 public void handleMessage(Message msg) {
570 CameraActivity activity = mActivity.get();
571 if (activity == null) {
576 case MSG_CLEAR_SCREEN_ON_FLAG: {
577 if (!activity.mPaused) {
578 activity.getWindow().clearFlags(
579 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
587 private String fileNameFromAdapterAtIndex(int index) {
588 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
589 if (filmstripItem == null) {
593 File localFile = new File(filmstripItem.getData().getFilePath());
594 return localFile.getName();
597 private float fileAgeFromAdapterAtIndex(int index) {
598 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
599 if (filmstripItem == null) {
603 File localFile = new File(filmstripItem.getData().getFilePath());
604 return 0.001f * (System.currentTimeMillis() - localFile.lastModified());
607 private final FilmstripContentPanel.Listener mFilmstripListener =
608 new FilmstripContentPanel.Listener() {
611 public void onSwipeOut() {
615 public void onSwipeOutBegin() {
617 mCameraAppUI.hideBottomControls();
618 mFilmstripCoversPreview = false;
619 updatePreviewVisibility();
623 public void onFilmstripHidden() {
624 mFilmstripVisible = false;
625 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
626 NavigationChange.InteractionCause.SWIPE_RIGHT);
627 CameraActivity.this.setFilmstripUiVisibility(false);
628 // When the user hide the filmstrip (either swipe out or
629 // tap on back key) we move to the first item so next time
630 // when the user swipe in the filmstrip, the most recent
632 mFilmstripController.goToFirstItem();
636 public void onFilmstripShown() {
637 mFilmstripVisible = true;
638 mCameraAppUI.hideCaptureIndicator();
639 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
640 NavigationChange.InteractionCause.SWIPE_LEFT);
641 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
645 public void onFocusedDataLongPressed(int adapterIndex) {
650 public void onFocusedDataPromoted(int adapterIndex) {
651 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
653 MediaInteraction.InteractionType.DELETE,
654 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex(
656 removeItemAt(adapterIndex);
660 public void onFocusedDataDemoted(int adapterIndex) {
661 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
663 MediaInteraction.InteractionType.DELETE,
664 NavigationChange.InteractionCause.SWIPE_DOWN,
665 fileAgeFromAdapterAtIndex(adapterIndex));
666 removeItemAt(adapterIndex);
670 public void onEnterFullScreenUiShown(int adapterIndex) {
671 if (mFilmstripVisible) {
672 CameraActivity.this.setFilmstripUiVisibility(true);
677 public void onLeaveFullScreenUiShown(int adapterIndex) {
682 public void onEnterFullScreenUiHidden(int adapterIndex) {
683 if (mFilmstripVisible) {
684 CameraActivity.this.setFilmstripUiVisibility(false);
689 public void onLeaveFullScreenUiHidden(int adapterIndex) {
694 public void onEnterFilmstrip(int adapterIndex) {
695 if (mFilmstripVisible) {
696 CameraActivity.this.setFilmstripUiVisibility(true);
701 public void onLeaveFilmstrip(int adapterIndex) {
706 public void onDataReloaded() {
707 if (!mFilmstripVisible) {
710 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
714 public void onDataUpdated(int adapterIndex) {
715 if (!mFilmstripVisible) {
718 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
722 public void onEnterZoomView(int adapterIndex) {
723 if (mFilmstripVisible) {
724 CameraActivity.this.setFilmstripUiVisibility(false);
729 public void onZoomAtIndexChanged(int adapterIndex, float zoom) {
730 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex);
731 long ageMillis = System.currentTimeMillis()
732 - filmstripItem.getData().getLastModifiedDate().getTime();
734 // Do not log if items is to old or does not have a path (which is
735 // being used as a key).
736 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) ||
737 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) {
740 File localFile = new File(filmstripItem.getData().getFilePath());
741 UsageStatistics.instance().mediaView(localFile.getName(),
742 filmstripItem.getData().getLastModifiedDate().getTime(), zoom);
746 public void onDataFocusChanged(final int prevIndex, final int newIndex) {
747 if (!mFilmstripVisible) {
750 // TODO: This callback is UI event callback, should always
751 // happen on UI thread. Find the reason for this
752 // runOnUiThread() and fix it.
753 runOnUiThread(new Runnable() {
756 updateUiByData(newIndex);
762 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
763 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
767 private final FilmstripItemListener mFilmstripItemListener =
768 new FilmstripItemListener() {
770 public void onMetadataUpdated(List<Integer> indexes) {
772 // Callback after the activity is paused.
775 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
776 for (Integer index : indexes) {
777 if (index == currentIndex) {
778 updateBottomControlsByData(mDataAdapter.getItemAt(index));
779 // Currently we have only 1 data can be matched.
780 // No need to look for more, break.
787 public void gotoGallery() {
788 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
789 NavigationChange.InteractionCause.BUTTON);
791 mFilmstripController.goToNextItem();
795 * If 'visible' is false, this hides the action bar. Also maintains
796 * lights-out at all times.
798 * @param visible is false, this hides the action bar and filmstrip bottom
801 private void setFilmstripUiVisibility(boolean visible) {
802 mLightsOutRunnable.run();
803 mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
804 if (visible != mActionBar.isShowing()) {
807 mCameraAppUI.showBottomControls();
810 mCameraAppUI.hideBottomControls();
813 mFilmstripCoversPreview = visible;
814 updatePreviewVisibility();
817 private void hideSessionProgress() {
818 mCameraAppUI.getFilmstripBottomControls().hideProgress();
821 private void showSessionProgress(int messageId) {
822 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
823 controls.setProgressText(messageId > 0 ? getString(messageId) : "");
824 controls.hideControls();
825 controls.hideProgressError();
826 controls.showProgress();
829 private void showProcessError(int messageId) {
830 mCameraAppUI.getFilmstripBottomControls().showProgressError(
831 messageId > 0 ? getString(messageId) : "");
834 private void updateSessionProgress(int progress) {
835 mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
838 private void updateSessionProgressText(int messageId) {
839 mCameraAppUI.getFilmstripBottomControls().setProgressText(
840 messageId > 0 ? getString(messageId) : "");
843 private void setupNfcBeamPush() {
844 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
845 if (adapter == null) {
849 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
851 adapter.setNdefPushMessage(null, CameraActivity.this);
855 adapter.setBeamPushUris(null, CameraActivity.this);
856 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
858 public Uri[] createBeamUris(NfcEvent event) {
861 }, CameraActivity.this);
865 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
866 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
867 if (currentIndex < 0) {
870 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex),
871 MediaInteraction.InteractionType.SHARE,
872 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex));
873 // TODO add intent.getComponent().getPackageName()
877 // Note: All callbacks come back on the main thread.
878 private final SessionListener mSessionListener =
879 new SessionListener() {
881 public void onSessionQueued(final Uri uri) {
882 Log.v(TAG, "onSessionQueued: " + uri);
883 if (!Storage.isSessionUri(uri)) {
886 Optional<SessionItem> newData = SessionItem.create(getApplicationContext(), uri);
887 if (newData.isPresent()) {
888 mDataAdapter.addOrUpdate(newData.get());
893 public void onSessionUpdated(Uri uri) {
894 Log.v(TAG, "onSessionUpdated: " + uri);
895 mDataAdapter.refresh(uri);
899 public void onSessionDone(final Uri sessionUri) {
900 Log.v(TAG, "onSessionDone:" + sessionUri);
901 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
902 if (contentUri == null) {
903 mDataAdapter.refresh(sessionUri);
906 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri);
908 // This can be null if e.g. a session is canceled (e.g.
909 // through discard panorama). It might be worth adding
910 // onSessionCanceled or the like this interface.
911 if (newData == null) {
912 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri);
916 final int pos = mDataAdapter.findByContentUri(sessionUri);
918 // We do not have a placeholder for this image, perhaps
919 // due to the activity crashing or being killed.
920 mDataAdapter.addOrUpdate(newData);
922 // Make the PhotoItem aware of the session placeholder, to
923 // allow it to make a smooth transition to its content if it
924 // the session item is currently visible.
925 FilmstripItem oldSessionData = mDataAdapter.getFilmstripItemAt(pos);
926 if (mCameraAppUI.getFilmstripVisibility() == View.VISIBLE
927 && mFilmstripController.isVisible(oldSessionData)) {
928 Log.v(TAG, "session item visible, setting transition placeholder");
929 newData.setSessionPlaceholderBitmap(
930 Storage.getPlaceholderForSession(sessionUri));
932 mDataAdapter.updateItemAt(pos, newData);
937 public void onSessionProgress(final Uri uri, final int progress) {
939 // Do nothing, there is no task for this URI.
942 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
943 if (currentIndex == -1) {
947 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
948 updateSessionProgress(progress);
953 public void onSessionProgressText(final Uri uri, final int messageId) {
954 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
955 if (currentIndex == -1) {
959 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
960 updateSessionProgressText(messageId);
965 public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
966 // Don't show capture indicator in Photo Sphere.
967 final int photosphereModuleId = getApplicationContext().getResources()
969 R.integer.camera_mode_photosphere);
970 if (mCurrentModeIndex == photosphereModuleId) {
973 indicateCapture(indicator, rotationDegrees);
977 public void onSessionFailed(Uri uri, int failureMessageId,
978 boolean removeFromFilmstrip) {
979 Log.v(TAG, "onSessionFailed:" + uri);
981 int failedIndex = mDataAdapter.findByContentUri(uri);
982 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
984 if (currentIndex == failedIndex) {
985 updateSessionProgress(0);
986 showProcessError(failureMessageId);
987 mDataAdapter.refresh(uri);
989 if (removeFromFilmstrip) {
990 mFatalErrorHandler.onMediaStorageFailure();
991 mDataAdapter.removeAt(failedIndex);
996 public void onSessionCanceled(Uri uri) {
997 Log.v(TAG, "onSessionCanceled:" + uri);
998 int failedIndex = mDataAdapter.findByContentUri(uri);
999 mDataAdapter.removeAt(failedIndex);
1003 public void onSessionThumbnailUpdate(Bitmap bitmap) {
1007 public void onSessionPictureDataUpdate(byte[] pictureData, int orientation) {
1012 public Context getAndroidContext() {
1017 public OneCameraFeatureConfig getCameraFeatureConfig() {
1018 return mFeatureConfig;
1022 public Dialog createDialog() {
1023 return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
1027 public void launchActivityByIntent(Intent intent) {
1028 // Starting from L, we prefer not to start edit activity within camera's task.
1029 mResetToPreviewOnResume = false;
1030 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
1032 startActivity(intent);
1036 public int getCurrentModuleIndex() {
1037 return mCurrentModeIndex;
1041 public String getModuleScope() {
1042 ModuleAgent agent = mModuleManager.getModuleAgent(mCurrentModeIndex);
1043 return SettingsManager.getModuleSettingScope(agent.getScopeNamespace());
1047 public String getCameraScope() {
1048 // if an unopen camera i.e. negative ID is returned, which we've observed in
1049 // some automated scenarios, just return it as a valid separate scope
1050 // this could cause user issues, so log a stack trace noting the call path
1051 // which resulted in this scenario.
1053 return SettingsManager.getCameraSettingScope(
1054 mCameraController.getCurrentCameraId().getValue());
1058 public ModuleController getCurrentModuleController() {
1059 return mCurrentModule;
1063 public int getQuickSwitchToModuleId(int currentModuleIndex) {
1064 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
1069 public SurfaceTexture getPreviewBuffer() {
1070 // TODO: implement this
1075 public void onPreviewReadyToStart() {
1076 mCameraAppUI.onPreviewReadyToStart();
1080 public void onPreviewStarted() {
1081 mCameraAppUI.onPreviewStarted();
1085 public void addPreviewAreaSizeChangedListener(
1086 PreviewStatusListener.PreviewAreaChangedListener listener) {
1087 mCameraAppUI.addPreviewAreaChangedListener(listener);
1091 public void removePreviewAreaSizeChangedListener(
1092 PreviewStatusListener.PreviewAreaChangedListener listener) {
1093 mCameraAppUI.removePreviewAreaChangedListener(listener);
1097 public void setupOneShotPreviewListener() {
1098 mCameraController.setOneShotPreviewCallback(mMainHandler,
1099 new CameraAgent.CameraPreviewDataCallback() {
1101 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) {
1102 mCurrentModule.onPreviewInitialDataReceived();
1103 mCameraAppUI.onNewPreviewFrame();
1110 public void updatePreviewAspectRatio(float aspectRatio) {
1111 mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
1115 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
1116 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio);
1120 public RectF getFullscreenRect() {
1121 return mCameraAppUI.getFullscreenRect();
1125 public void updatePreviewTransform(Matrix matrix) {
1126 mCameraAppUI.updatePreviewTransform(matrix);
1130 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1131 mCameraAppUI.setPreviewStatusListener(previewStatusListener);
1135 public FrameLayout getModuleLayoutRoot() {
1136 return mCameraAppUI.getModuleRootView();
1140 public void setShutterEventsListener(ShutterEventsListener listener) {
1141 // TODO: implement this
1145 public void setShutterEnabled(boolean enabled) {
1146 mCameraAppUI.setShutterButtonEnabled(enabled);
1150 public boolean isShutterEnabled() {
1151 return mCameraAppUI.isShutterButtonEnabled();
1155 public void startFlashAnimation(boolean shortFlash) {
1156 mCameraAppUI.startFlashAnimation(shortFlash);
1160 public void startPreCaptureAnimation() {
1161 // TODO: implement this
1165 public void cancelPreCaptureAnimation() {
1166 // TODO: implement this
1170 public void startPostCaptureAnimation() {
1171 // TODO: implement this
1175 public void startPostCaptureAnimation(Bitmap thumbnail) {
1176 // TODO: implement this
1180 public void cancelPostCaptureAnimation() {
1181 // TODO: implement this
1185 public OrientationManager getOrientationManager() {
1186 return mOrientationManager;
1190 public LocationManager getLocationManager() {
1191 return mLocationManager;
1195 public void lockOrientation() {
1196 if (mOrientationManager != null) {
1197 mOrientationManager.lockOrientation();
1202 public void unlockOrientation() {
1203 if (mOrientationManager != null) {
1204 mOrientationManager.unlockOrientation();
1209 * If not in filmstrip, this shows the capture indicator.
1211 private void indicateCapture(final Bitmap indicator, final int rotationDegrees) {
1212 if (mFilmstripVisible) {
1216 // Don't show capture indicator in Photo Sphere.
1217 // TODO: Don't reach into resources to figure out the current mode.
1218 final int photosphereModuleId = getApplicationContext().getResources().getInteger(
1219 R.integer.camera_mode_photosphere);
1220 if (mCurrentModeIndex == photosphereModuleId) {
1224 mMainHandler.post(new Runnable() {
1227 mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule
1228 .getPeekAccessibilityString());
1229 mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees);
1235 public void notifyNewMedia(Uri uri) {
1236 // TODO: This method is running on the main thread. Also we should get
1237 // rid of that AsyncTask.
1239 updateStorageSpaceAndHint(null);
1240 ContentResolver cr = getContentResolver();
1241 String mimeType = cr.getType(uri);
1242 FilmstripItem newData = null;
1243 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
1244 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1245 newData = mVideoItemFactory.queryContentUri(uri);
1246 if (newData == null) {
1247 Log.e(TAG, "Can't find video data in content resolver:" + uri);
1250 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
1251 CameraUtil.broadcastNewPicture(mAppContext, uri);
1252 newData = mPhotoItemFactory.queryContentUri(uri);
1253 if (newData == null) {
1254 Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1258 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1262 // We are preloading the metadata for new video since we need the
1263 // rotation info for the thumbnail.
1264 new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
1266 protected FilmstripItem doInBackground(FilmstripItem... params) {
1267 FilmstripItem data = params[0];
1268 MetadataLoader.loadMetadata(getAndroidContext(), data);
1273 protected void onPostExecute(final FilmstripItem data) {
1274 // TODO: Figure out why sometimes the data is aleady there.
1275 mDataAdapter.addOrUpdate(data);
1277 // Legacy modules don't use CaptureSession, so we show the capture indicator when
1278 // the item was safed.
1279 if (mCurrentModule instanceof PhotoModule ||
1280 mCurrentModule instanceof VideoModule) {
1281 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1284 final Optional<Bitmap> bitmap = data.generateThumbnail(
1285 mAboveFilmstripControlLayout.getWidth(),
1286 mAboveFilmstripControlLayout.getMeasuredHeight());
1287 if (bitmap.isPresent()) {
1288 indicateCapture(bitmap.get(), 0);
1294 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
1298 public void enableKeepScreenOn(boolean enabled) {
1303 mKeepScreenOn = enabled;
1304 if (mKeepScreenOn) {
1305 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1306 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1308 keepScreenOnForAWhile();
1313 public CameraProvider getCameraProvider() {
1314 return mCameraController;
1318 public OneCameraOpener getCameraOpener() {
1319 return mOneCameraOpener;
1322 private void removeItemAt(int index) {
1323 mDataAdapter.removeAt(index);
1324 if (mDataAdapter.getTotalNumber() > 1) {
1325 showUndoDeletionBar();
1327 // If camera preview is the only view left in filmstrip,
1328 // no need to show undo bar.
1329 mPendingDeletion = true;
1331 if (mFilmstripVisible) {
1332 mCameraAppUI.getFilmstripContentPanel().animateHide();
1338 public boolean onOptionsItemSelected(MenuItem item) {
1339 // Handle presses on the action bar items
1340 switch (item.getItemId()) {
1341 case android.R.id.home:
1344 case R.id.action_details:
1345 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex());
1347 case R.id.action_help_and_feedback:
1348 mResetToPreviewOnResume = false;
1349 new GoogleHelpHelper(this).launchGoogleHelp();
1352 return super.onOptionsItemSelected(item);
1356 private boolean isCaptureIntent() {
1357 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1358 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1359 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1367 * Note: Make sure this callback is unregistered properly when the activity
1368 * is destroyed since we're otherwise leaking the Activity reference.
1370 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
1371 = new CameraExceptionHandler.CameraExceptionCallback() {
1373 public void onCameraError(int errorCode) {
1374 // Not a fatal error. only do Log.e().
1375 Log.e(TAG, "Camera error callback. error=" + errorCode);
1378 public void onCameraException(
1379 RuntimeException ex, String commandHistory, int action, int state) {
1380 Log.e(TAG, "Camera Exception", ex);
1381 UsageStatistics.instance().cameraFailure(
1382 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
1383 commandHistory, action, state);
1387 public void onDispatchThreadException(RuntimeException ex) {
1388 Log.e(TAG, "DispatchThread Exception", ex);
1389 UsageStatistics.instance().cameraFailure(
1390 eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
1391 null, UsageStatistics.NONE, UsageStatistics.NONE);
1394 private void onFatalError() {
1395 if (mCameraFatalError) {
1398 mCameraFatalError = true;
1400 // If the activity receives exception during onPause, just exit the app.
1401 if (mPaused && !isFinishing()) {
1402 Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
1405 mFatalErrorHandler.handleFatalError(FatalErrorHandler.Reason.CANNOT_CONNECT_TO_CAMERA);
1411 public void onNewIntentTasks(Intent intent) {
1412 onModeSelected(getModeIndex());
1416 public void onCreateTasks(Bundle state) {
1417 Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start();
1418 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1419 mOnCreateTime = System.currentTimeMillis();
1420 mAppContext = getApplicationContext();
1421 mMainHandler = new MainHandler(this, getMainLooper());
1422 mLocationManager = new LocationManager(mAppContext);
1423 mOrientationManager = new OrientationManagerImpl(this, mMainHandler);
1424 mSettingsManager = getServices().getSettingsManager();
1425 mSoundPlayer = new SoundPlayer(mAppContext);
1426 mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver(),
1427 getServices().getMemoryManager());
1428 mFatalErrorHandler = new FatalErrorHandlerImpl(this);
1431 if (!Glide.isSetup()) {
1432 Context context = getAndroidContext();
1433 Glide.setup(new GlideBuilder(context)
1434 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888)
1435 .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
1437 Glide glide = Glide.get(context);
1439 // As a camera we will use a large amount of memory
1440 // for displaying images.
1441 glide.setMemoryCategory(MemoryCategory.HIGH);
1443 profile.mark("Glide.setup");
1445 mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance();
1447 mOneCameraOpener = OneCameraModule.provideOneCameraOpener(
1450 mActiveCameraDeviceTracker,
1451 ResolutionUtil.getDisplayMetrics(this));
1452 mOneCameraManager = OneCameraModule.provideOneCameraManager();
1453 } catch (OneCameraException e) {
1454 // Log error and continue start process while showing error dialog..
1455 Log.e(TAG, "Creating camera manager failed.", e);
1456 mFatalErrorHandler.onGenericCameraAccessFailure();
1458 profile.mark("OneCameraManager.get");
1461 mCameraController = new CameraController(mAppContext, this, mMainHandler,
1462 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1463 CameraAgentFactory.CameraApi.API_1),
1464 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1465 CameraAgentFactory.CameraApi.AUTO),
1466 mActiveCameraDeviceTracker);
1467 mCameraController.setCameraExceptionHandler(
1468 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
1469 } catch (AssertionError e) {
1470 Log.e(TAG, "Creating camera controller failed.", e);
1471 mFatalErrorHandler.onGenericCameraAccessFailure();
1474 // TODO: Try to move all the resources allocation to happen as soon as
1475 // possible so we can call module.init() at the earliest time.
1476 mModuleManager = new ModuleManagerImpl();
1478 ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig);
1480 AppUpgrader appUpgrader = new AppUpgrader(this);
1481 appUpgrader.upgrade(mSettingsManager);
1483 // Make sure the picture sizes are correctly cached for the current OS
1487 (new PictureSizeLoader(mAppContext)).computePictureSizes();
1488 } catch (AssertionError e) {
1489 Log.e(TAG, "Creating camera controller failed.", e);
1490 mFatalErrorHandler.onGenericCameraAccessFailure();
1492 profile.mark("computePictureSizes");
1493 Keys.setDefaults(mSettingsManager, mAppContext);
1495 mResolutionSetting = new ResolutionSetting(mSettingsManager, mOneCameraManager,
1496 getContentResolver());
1498 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1499 // We suppress this flag via theme when drawing the system preview
1500 // background, but once we create activity here, reactivate to the
1501 // default value. The default is important for L, we don't want to
1502 // change app behavior, just starting background drawable layout.
1503 if (ApiHelper.isLOrHigher()) {
1504 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1508 setContentView(R.layout.activity_main);
1509 profile.mark("setContentView()");
1510 // A window background is set in styles.xml for the system to show a
1511 // drawable background with gray color and camera icon before the
1512 // activity is created. We set the background to null here to prevent
1513 // overdraw, all views must take care of drawing backgrounds if
1514 // necessary. This call to setBackgroundDrawable must occur after
1515 // setContentView, otherwise a background may be set again from the
1517 getWindow().setBackgroundDrawable(null);
1519 mActionBar = getActionBar();
1520 // set actionbar background to 100% or 50% transparent
1521 if (ApiHelper.isLOrHigher()) {
1522 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000));
1524 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
1527 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1528 mModeListView.init(mModuleManager.getSupportedModeIndexList());
1529 if (ApiHelper.HAS_ROTATION_ANIMATION) {
1530 setRotationAnimation();
1532 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1534 public void onVisibilityChanged(boolean visible) {
1535 mModeListVisible = visible;
1536 mCameraAppUI.setShutterButtonImportantToA11y(!visible);
1537 updatePreviewVisibility();
1541 // Check if this is in the secure camera mode.
1542 Intent intent = getIntent();
1543 String action = intent.getAction();
1544 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1545 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1546 mSecureCamera = true;
1548 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1551 if (mSecureCamera) {
1552 // Change the window flags so that secure camera can show when
1554 Window win = getWindow();
1555 WindowManager.LayoutParams params = win.getAttributes();
1556 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1557 win.setAttributes(params);
1559 // Filter for screen off so that we can finish secure camera
1560 // activity when screen is off.
1561 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1562 registerReceiver(mShutdownReceiver, filter_screen_off);
1564 // Filter for phone unlock so that we can finish secure camera
1565 // via this UI path:
1566 // 1. from secure lock screen, user starts secure camera
1567 // 2. user presses home button
1568 // 3. user unlocks phone
1569 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
1570 registerReceiver(mShutdownReceiver, filter_user_unlock);
1572 mCameraAppUI = new CameraAppUI(this,
1573 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1575 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1577 mAboveFilmstripControlLayout =
1578 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1580 // Add the session listener so we can track the session progress
1582 getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1583 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1584 mFilmstripController.setImageGap(
1585 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1586 profile.mark("Configure Camera UI");
1588 mPanoramaViewHelper = new PanoramaViewHelper(this);
1589 mPanoramaViewHelper.onCreate();
1591 ContentResolver appContentResolver = mAppContext.getContentResolver();
1592 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
1593 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
1594 new PhotoDataFactory());
1595 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
1596 new VideoDataFactory());
1597 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
1598 mPhotoItemFactory, mVideoItemFactory);
1599 mDataAdapter.setLocalDataListener(mFilmstripItemListener);
1601 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1604 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1605 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1606 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1607 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1610 setModuleFromModeIndex(getModeIndex());
1613 mCameraAppUI.prepareModuleUI();
1614 profile.mark("Init Current Module UI");
1615 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1616 profile.mark("Init CurrentModule");
1618 if (!mSecureCamera) {
1619 mFilmstripController.setDataAdapter(mDataAdapter);
1620 if (!isCaptureIntent()) {
1621 mDataAdapter.requestLoad(new Callback<Void>() {
1623 public void onCallback(Void result) {
1624 fillTemporarySessions();
1629 // Put a lock placeholder as the last image by setting its date to
1631 ImageView v = (ImageView) getLayoutInflater().inflate(
1632 R.layout.secure_album_placeholder, null);
1633 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
1634 v.setOnClickListener(new View.OnClickListener() {
1636 public void onClick(View view) {
1637 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1638 NavigationChange.InteractionCause.BUTTON);
1643 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1644 mDataAdapter = new FixedLastProxyAdapter(
1647 new PlaceholderItem(
1649 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
1650 v.getDrawable().getIntrinsicWidth(),
1651 v.getDrawable().getIntrinsicHeight()));
1652 // Flush out all the original data.
1653 mDataAdapter.clear();
1654 mFilmstripController.setDataAdapter(mDataAdapter);
1659 mLocalImagesObserver = new FilmstripContentObserver();
1660 mLocalVideosObserver = new FilmstripContentObserver();
1662 getContentResolver().registerContentObserver(
1663 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1664 mLocalImagesObserver);
1665 getContentResolver().registerContentObserver(
1666 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1667 mLocalVideosObserver);
1669 mMemoryManager = getServices().getMemoryManager();
1671 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1674 HashMap memoryData = mMemoryManager.queryMemory();
1675 UsageStatistics.instance().reportMemoryConsumed(memoryData,
1676 MemoryQuery.REPORT_LABEL_LAUNCH);
1680 mMotionManager = getServices().getMotionManager();
1682 mFirstRunDialog = new FirstRunDialog(this,
1683 getAndroidContext(),
1687 new FirstRunDialog.FirstRunDialogListener() {
1689 public void onFirstRunStateReady() {
1690 // Run normal resume tasks.
1695 public void onFirstRunDialogCancelled() {
1696 // App isn't functional until users finish first run dialog.
1697 // We need to finish here since users hit back button during
1698 // first run dialog (b/19593942).
1703 public void onCameraAccessException() {
1704 mFatalErrorHandler.onGenericCameraAccessFailure();
1711 * Get the current mode index from the Intent or from persistent
1714 private int getModeIndex() {
1716 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1717 int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1718 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1719 int captureIntentIndex =
1720 getResources().getInteger(R.integer.camera_mode_capture_intent);
1721 String intentAction = getIntent().getAction();
1722 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction)
1723 || MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) {
1724 modeIndex = videoIndex;
1725 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction)
1726 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1728 modeIndex = captureIntentIndex;
1729 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction)
1730 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction)
1731 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1732 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1733 Keys.KEY_CAMERA_MODULE_LAST_USED);
1735 // For upgraders who have not seen the aspect ratio selection screen,
1736 // we need to drop them back in the photo module and have them select
1738 // TODO: Move this to SettingsManager as an upgrade procedure.
1739 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1740 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
1741 modeIndex = photoIndex;
1744 // If the activity has not been started using an explicit intent,
1745 // read the module index from the last time the user changed modes
1746 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1747 Keys.KEY_STARTUP_MODULE_INDEX);
1748 if ((modeIndex == gcamIndex &&
1749 !GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) || modeIndex < 0) {
1750 modeIndex = photoIndex;
1757 * Call this whenever the mode drawer or filmstrip change the visibility
1760 private void updatePreviewVisibility() {
1761 if (mCurrentModule == null) {
1765 int visibility = getPreviewVisibility();
1766 mCameraAppUI.onPreviewVisiblityChanged(visibility);
1767 updatePreviewRendering(visibility);
1768 mCurrentModule.onPreviewVisibilityChanged(visibility);
1771 private void updatePreviewRendering(int visibility) {
1772 if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1773 mCameraAppUI.pausePreviewRendering();
1775 mCameraAppUI.resumePreviewRendering();
1779 private int getPreviewVisibility() {
1780 if (mFilmstripCoversPreview) {
1781 return ModuleController.VISIBILITY_HIDDEN;
1782 } else if (mModeListVisible){
1783 return ModuleController.VISIBILITY_COVERED;
1785 return ModuleController.VISIBILITY_VISIBLE;
1789 private void setRotationAnimation() {
1790 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1791 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1792 Window win = getWindow();
1793 WindowManager.LayoutParams winParams = win.getAttributes();
1794 winParams.rotationAnimation = rotationAnimation;
1795 win.setAttributes(winParams);
1799 public void onUserInteraction() {
1800 super.onUserInteraction();
1801 if (!isFinishing()) {
1802 keepScreenOnForAWhile();
1807 public boolean dispatchTouchEvent(MotionEvent ev) {
1808 boolean result = super.dispatchTouchEvent(ev);
1809 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1810 // Real deletion is postponed until the next user interaction after
1811 // the gesture that triggers deletion. Until real deletion is
1812 // performed, users can click the undo button to bring back the
1813 // image that they chose to delete.
1814 if (mPendingDeletion && !mIsUndoingDeletion) {
1822 public void onPauseTasks() {
1823 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1824 Profile profile = mProfiler.create("CameraActivity.onPause").start();
1827 * Save the last module index after all secure camera and icon launches,
1828 * not just on mode switches.
1830 * Right now we exclude capture intents from this logic, because we also
1831 * ignore the cross-Activity recovery logic in onStart for capture intents.
1833 if (!isCaptureIntent()) {
1834 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
1835 Keys.KEY_STARTUP_MODULE_INDEX,
1840 mCameraAppUI.hideCaptureIndicator();
1841 mFirstRunDialog.dismiss();
1843 // Delete photos that are pending deletion
1845 mCurrentModule.pause();
1846 mOrientationManager.pause();
1847 mPanoramaViewHelper.onPause();
1849 mLocalImagesObserver.setForegroundChangeListener(null);
1850 mLocalImagesObserver.setActivityPaused(true);
1851 mLocalVideosObserver.setActivityPaused(true);
1852 mPreloader.cancelAllLoads();
1855 mMotionManager.stop();
1857 // Always stop recording location when paused. Resume will start
1858 // location recording again if the location setting is on.
1859 mLocationManager.recordLocation(false);
1861 UsageStatistics.instance().backgrounded();
1863 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
1864 // button. Let's just kill the process.
1865 if (mCameraFatalError && !isFinishing()) {
1866 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
1869 // Close the camera and wait for the operation done.
1870 Log.v(TAG, "onPause closing camera");
1871 if (mCameraController != null) {
1872 mCameraController.closeCamera(true);
1880 public void onResumeTasks() {
1883 // Show the dialog if necessary. The rest resume logic will be invoked
1884 // at the onFirstRunStateReady() callback.
1886 mFirstRunDialog.showIfNecessary();
1887 } catch (AssertionError e) {
1888 Log.e(TAG, "Creating camera controller failed.", e);
1889 mFatalErrorHandler.onGenericCameraAccessFailure();
1893 private void resume() {
1894 Profile profile = mProfiler.create("CameraActivity.resume").start();
1895 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1896 Log.v(TAG, "Build info: " + Build.DISPLAY);
1898 updateStorageSpaceAndHint(null);
1900 mLastLayoutOrientation = getResources().getConfiguration().orientation;
1902 // TODO: Handle this in OrientationManager.
1904 if (Settings.System.getInt(getContentResolver(),
1905 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1906 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1907 mAutoRotateScreen = false;
1909 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1910 mAutoRotateScreen = true;
1913 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and
1914 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
1915 // lockscreen onResume->onPause->onResume sequence.
1917 String action = getIntent().getAction();
1918 if (action == null) {
1919 source = ForegroundSource.UNKNOWN_SOURCE;
1922 case MediaStore.ACTION_IMAGE_CAPTURE:
1923 source = ForegroundSource.ACTION_IMAGE_CAPTURE;
1925 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
1926 // was UNKNOWN_SOURCE in Fishlake.
1927 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
1929 case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
1930 // was UNKNOWN_SOURCE in Fishlake.
1931 source = ForegroundSource.ACTION_VIDEO_CAMERA;
1933 case MediaStore.ACTION_VIDEO_CAPTURE:
1934 source = ForegroundSource.ACTION_VIDEO_CAPTURE;
1936 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
1937 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
1938 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
1940 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
1941 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
1943 case Intent.ACTION_MAIN:
1944 source = ForegroundSource.ACTION_MAIN;
1947 source = ForegroundSource.UNKNOWN_SOURCE;
1951 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode(),
1952 isKeyguardSecure(), isKeyguardLocked(),
1953 mStartupOnCreate, mExecutionStartNanoTime);
1955 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
1956 if (ApiHelper.isLOrHigher()) {
1957 // hide the up affordance for L devices, it's not very Materially
1958 mActionBar.setDisplayShowHomeEnabled(false);
1961 mOrientationManager.resume();
1963 mCurrentModule.hardResetSettings(mSettingsManager);
1966 mCurrentModule.resume();
1967 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
1968 NavigationChange.InteractionCause.BUTTON);
1969 setSwipingEnabled(true);
1970 profile.mark("mCurrentModule.resume");
1972 if (!mResetToPreviewOnResume) {
1973 FilmstripItem item = mDataAdapter.getItemAt(
1974 mFilmstripController.getCurrentAdapterIndex());
1976 mDataAdapter.refresh(item.getData().getUri());
1980 // The share button might be disabled to avoid double tapping.
1981 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1982 // Default is showing the preview, unless disabled by explicitly
1983 // starting an activity we want to return from to the filmstrip rather
1984 // than the preview.
1985 mResetToPreviewOnResume = true;
1987 if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1988 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1989 if (!mSecureCamera) {
1990 // If it's secure camera, requestLoad() should not be called
1991 // as it will load all the data.
1992 if (!mFilmstripVisible) {
1993 mDataAdapter.requestLoad(new Callback<Void>() {
1995 public void onCallback(Void result) {
1996 fillTemporarySessions();
2000 mDataAdapter.requestLoadNewPhotos();
2004 mLocalImagesObserver.setActivityPaused(false);
2005 mLocalVideosObserver.setActivityPaused(false);
2006 if (!mSecureCamera) {
2007 mLocalImagesObserver.setForegroundChangeListener(
2008 new FilmstripContentObserver.ChangeListener() {
2010 public void onChange() {
2011 mDataAdapter.requestLoadNewPhotos();
2016 keepScreenOnForAWhile();
2018 // Lights-out mode at all times.
2019 final View rootView = findViewById(R.id.activity_root_view);
2020 mLightsOutRunnable.run();
2021 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
2022 new OnSystemUiVisibilityChangeListener() {
2024 public void onSystemUiVisibilityChange(int visibility) {
2025 mMainHandler.removeCallbacks(mLightsOutRunnable);
2026 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
2031 mPanoramaViewHelper.onResume();
2032 profile.mark("mPanoramaViewHelper.onResume()");
2034 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
2035 // Enable location recording if the setting is on.
2036 final boolean locationRecordingEnabled =
2037 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
2038 mLocationManager.recordLocation(locationRecordingEnabled);
2040 final int previewVisibility = getPreviewVisibility();
2041 updatePreviewRendering(previewVisibility);
2043 mMotionManager.start();
2047 private void fillTemporarySessions() {
2048 if (mSecureCamera) {
2051 // There might be sessions still in flight (processed by our service).
2052 // Make sure they're added to the filmstrip.
2053 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
2057 public void onStartTasks() {
2058 mIsActivityRunning = true;
2059 mPanoramaViewHelper.onStart();
2062 * If we're starting after launching a different Activity (lockscreen),
2063 * we need to use the last mode used in the other Activity, and
2064 * not the old one from this Activity.
2066 * This needs to happen before CameraAppUI.resume() in order to set the
2067 * mode cover icon to the actual last mode used.
2069 * Right now we exclude capture intents from this logic.
2071 int modeIndex = getModeIndex();
2072 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
2073 onModeSelected(modeIndex);
2076 if (mResetToPreviewOnResume) {
2077 mCameraAppUI.resume();
2078 mResetToPreviewOnResume = false;
2083 protected void onStopTasks() {
2084 mIsActivityRunning = false;
2085 mPanoramaViewHelper.onStop();
2087 mLocationManager.disconnect();
2091 public void onDestroyTasks() {
2092 if (mSecureCamera) {
2093 unregisterReceiver(mShutdownReceiver);
2096 // Ensure anything that checks for "isPaused" returns true.
2099 mSettingsManager.removeAllListeners();
2100 if (mCameraController != null) {
2101 mCameraController.removeCallbackReceiver();
2102 mCameraController.setCameraExceptionHandler(null);
2104 getContentResolver().unregisterContentObserver(mLocalImagesObserver);
2105 getContentResolver().unregisterContentObserver(mLocalVideosObserver);
2106 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
2107 mCameraAppUI.onDestroy();
2108 mModeListView.setVisibilityChangedListener(null);
2109 mCameraController = null;
2110 mSettingsManager = null;
2111 mOrientationManager = null;
2112 mButtonManager = null;
2113 mSoundPlayer.release();
2114 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
2115 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
2119 public void onConfigurationChanged(Configuration config) {
2120 super.onConfigurationChanged(config);
2121 Log.v(TAG, "onConfigurationChanged");
2122 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
2126 if (mLastLayoutOrientation != config.orientation) {
2127 mLastLayoutOrientation = config.orientation;
2128 mCurrentModule.onLayoutOrientationChanged(
2129 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
2134 public boolean onKeyDown(int keyCode, KeyEvent event) {
2135 if (!mFilmstripVisible) {
2136 if (mCurrentModule.onKeyDown(keyCode, event)) {
2139 // Prevent software keyboard or voice search from showing up.
2140 if (keyCode == KeyEvent.KEYCODE_SEARCH
2141 || keyCode == KeyEvent.KEYCODE_MENU) {
2142 if (event.isLongPress()) {
2148 return super.onKeyDown(keyCode, event);
2152 public boolean onKeyUp(int keyCode, KeyEvent event) {
2153 if (!mFilmstripVisible) {
2154 // If a module is in the middle of capture, it should
2155 // consume the key event.
2156 if (mCurrentModule.onKeyUp(keyCode, event)) {
2158 } else if (keyCode == KeyEvent.KEYCODE_MENU
2159 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2160 // Let the mode list view consume the event.
2161 mCameraAppUI.openModeList();
2163 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2164 mCameraAppUI.showFilmstrip();
2168 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2169 mFilmstripController.goToNextItem();
2171 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2172 boolean wentToPrevious = mFilmstripController.goToPreviousItem();
2173 if (!wentToPrevious) {
2174 // at beginning of filmstrip, hide and go back to preview
2175 mCameraAppUI.hideFilmstrip();
2180 return super.onKeyUp(keyCode, event);
2184 public void onBackPressed() {
2185 if (!mCameraAppUI.onBackPressed()) {
2186 if (!mCurrentModule.onBackPressed()) {
2187 super.onBackPressed();
2193 public boolean isAutoRotateScreen() {
2194 // TODO: Move to OrientationManager.
2195 return mAutoRotateScreen;
2199 public boolean onCreateOptionsMenu(Menu menu) {
2200 MenuInflater inflater = getMenuInflater();
2201 inflater.inflate(R.menu.filmstrip_menu, menu);
2202 mActionBarMenu = menu;
2204 // add a button for launching the gallery
2205 if (mGalleryIntent != null) {
2206 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
2207 if (appName != null) {
2208 MenuItem menuItem = menu.add(appName);
2209 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2210 menuItem.setIntent(mGalleryIntent);
2212 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
2213 if (galleryLogo != null) {
2214 menuItem.setIcon(galleryLogo);
2219 return super.onCreateOptionsMenu(menu);
2223 public boolean onPrepareOptionsMenu(Menu menu) {
2224 if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
2225 // Compatibility pre-L: launching new activities right above
2226 // lockscreen does not reliably work, only show help if not secure
2227 menu.removeItem(R.id.action_help_and_feedback);
2230 return super.onPrepareOptionsMenu(menu);
2233 protected long getStorageSpaceBytes() {
2234 synchronized (mStorageSpaceLock) {
2235 return mStorageSpaceBytes;
2239 protected interface OnStorageUpdateDoneListener {
2240 public void onStorageUpdateDone(long bytes);
2243 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
2245 * We execute disk operations on a background thread in order to
2246 * free up the UI thread. Synchronizing on the lock below ensures
2247 * that when getStorageSpaceBytes is called, the main thread waits
2248 * until this method has completed.
2250 * However, .execute() does not ensure this execution block will be
2251 * run right away (.execute() schedules this AsyncTask for sometime
2252 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
2253 * tries to execute the task in parellel with other AsyncTasks, but
2254 * there's still no guarantee).
2255 * e.g. don't call this then immediately call getStorageSpaceBytes().
2256 * Instead, pass in an OnStorageUpdateDoneListener.
2258 (new AsyncTask<Void, Void, Long>() {
2260 protected Long doInBackground(Void ... arg) {
2261 synchronized (mStorageSpaceLock) {
2262 mStorageSpaceBytes = Storage.getAvailableSpace();
2263 return mStorageSpaceBytes;
2268 protected void onPostExecute(Long bytes) {
2269 updateStorageHint(bytes);
2270 // This callback returns after I/O to check disk, so we could be
2271 // pausing and shutting down. If so, don't bother invoking.
2272 if (callback != null && !mPaused) {
2273 callback.onStorageUpdateDone(bytes);
2275 Log.v(TAG, "ignoring storage callback after activity pause");
2278 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2281 protected void updateStorageHint(long storageSpace) {
2282 if (!mIsActivityRunning) {
2286 String message = null;
2287 if (storageSpace == Storage.UNAVAILABLE) {
2288 message = getString(R.string.no_storage);
2289 } else if (storageSpace == Storage.PREPARING) {
2290 message = getString(R.string.preparing_sd);
2291 } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2292 message = getString(R.string.access_sd_fail);
2293 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2294 message = getString(R.string.spaceIsLow_content);
2297 if (message != null) {
2298 Log.w(TAG, "Storage warning: " + message);
2299 if (mStorageHint == null) {
2300 mStorageHint = OnScreenHint.makeText(CameraActivity.this, message);
2302 mStorageHint.setText(message);
2304 mStorageHint.show();
2305 UsageStatistics.instance().storageWarning(storageSpace);
2307 // Disable all user interactions,
2308 mCameraAppUI.setDisableAllUserInteractions(true);
2309 } else if (mStorageHint != null) {
2310 mStorageHint.cancel();
2311 mStorageHint = null;
2313 // Re-enable all user interactions.
2314 mCameraAppUI.setDisableAllUserInteractions(false);
2318 protected void setResultEx(int resultCode) {
2319 mResultCodeForTesting = resultCode;
2320 setResult(resultCode);
2323 protected void setResultEx(int resultCode, Intent data) {
2324 mResultCodeForTesting = resultCode;
2325 mResultDataForTesting = data;
2326 setResult(resultCode, data);
2329 public int getResultCode() {
2330 return mResultCodeForTesting;
2333 public Intent getResultData() {
2334 return mResultDataForTesting;
2337 public boolean isSecureCamera() {
2338 return mSecureCamera;
2342 public boolean isPaused() {
2347 public int getPreferredChildModeIndex(int modeIndex) {
2348 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2349 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2350 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) {
2351 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2358 public void onModeSelected(int modeIndex) {
2359 if (mCurrentModeIndex == modeIndex) {
2363 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2364 // Record last used camera mode for quick switching
2365 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2366 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2367 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2368 Keys.KEY_CAMERA_MODULE_LAST_USED,
2372 closeModule(mCurrentModule);
2374 // Select the correct module index from the mode switcher index.
2375 modeIndex = getPreferredChildModeIndex(modeIndex);
2376 setModuleFromModeIndex(modeIndex);
2378 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2379 mCameraAppUI.addShutterListener(mCurrentModule);
2380 openModule(mCurrentModule);
2381 // Store the module index so we can use it the next time the Camera
2383 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2384 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2388 * Shows the settings dialog.
2391 public void onSettingsSelected() {
2392 UsageStatistics.instance().controlUsed(
2393 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2394 Intent intent = new Intent(this, CameraSettingsActivity.class);
2395 startActivity(intent);
2399 public void freezeScreenUntilPreviewReady() {
2400 mCameraAppUI.freezeScreenUntilPreviewReady();
2404 public int getModuleId(int modeIndex) {
2405 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2406 if (agent == null) {
2409 return agent.getModuleId();
2413 * Sets the mCurrentModuleIndex, creates a new module instance for the given
2414 * index an sets it as mCurrentModule.
2416 private void setModuleFromModeIndex(int modeIndex) {
2417 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2418 if (agent == null) {
2421 if (!agent.requestAppForCamera()) {
2422 mCameraController.closeCamera(true);
2424 mCurrentModeIndex = agent.getModuleId();
2425 mCurrentModule = (CameraModule) agent.createModule(this, getIntent());
2429 public SettingsManager getSettingsManager() {
2430 return mSettingsManager;
2434 public ResolutionSetting getResolutionSetting() {
2435 return mResolutionSetting;
2439 public CameraServices getServices() {
2440 return CameraServicesImpl.instance();
2444 public FatalErrorHandler getFatalErrorHandler() {
2445 return mFatalErrorHandler;
2448 public List<String> getSupportedModeNames() {
2449 List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2450 List<String> supported = new ArrayList<String>();
2452 for (Integer modeIndex : indices) {
2453 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2454 if (name != null && !name.equals("")) {
2455 supported.add(name);
2462 public ButtonManager getButtonManager() {
2463 if (mButtonManager == null) {
2464 mButtonManager = new ButtonManager(this);
2466 return mButtonManager;
2470 public SoundPlayer getSoundPlayer() {
2471 return mSoundPlayer;
2475 * Launches an ACTION_EDIT intent for the given local data item. If
2476 * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2477 * the user start either the tiny planet editor or another photo editor.
2479 * @param data The data item to edit.
2481 public void launchEditor(FilmstripItem data) {
2482 Intent intent = new Intent(Intent.ACTION_EDIT)
2483 .setDataAndType(data.getData().getUri(), data.getData().getMimeType())
2484 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2486 launchActivityByIntent(intent);
2487 } catch (ActivityNotFoundException e) {
2488 final String msgEditWith = getResources().getString(R.string.edit_with);
2489 launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2494 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2495 super.onCreateContextMenu(menu, v, menuInfo);
2497 MenuInflater inflater = getMenuInflater();
2498 inflater.inflate(R.menu.filmstrip_context_menu, menu);
2502 public boolean onContextItemSelected(MenuItem item) {
2503 switch (item.getItemId()) {
2504 case R.id.tiny_planet_editor:
2505 mMyFilmstripBottomControlListener.onTinyPlanet();
2507 case R.id.photo_editor:
2508 mMyFilmstripBottomControlListener.onEdit();
2515 * Launch the tiny planet editor.
2517 * @param data The data must be a 360 degree stereographically mapped
2518 * panoramic image. It will not be modified, instead a new item
2519 * with the result will be added to the filmstrip.
2521 public void launchTinyPlanetEditor(FilmstripItem data) {
2522 TinyPlanetFragment fragment = new TinyPlanetFragment();
2523 Bundle bundle = new Bundle();
2524 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString());
2525 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle());
2526 fragment.setArguments(bundle);
2527 fragment.show(getFragmentManager(), "tiny_planet");
2531 * Returns what UI mode (capture mode or filmstrip) we are in.
2532 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2534 private int currentUserInterfaceMode() {
2535 int mode = NavigationChange.Mode.UNKNOWN_MODE;
2536 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2537 mode = NavigationChange.Mode.PHOTO_CAPTURE;
2539 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2540 mode = NavigationChange.Mode.VIDEO_CAPTURE;
2542 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2543 mode = NavigationChange.Mode.LENS_BLUR;
2545 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2546 mode = NavigationChange.Mode.HDR_PLUS;
2548 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2549 mode = NavigationChange.Mode.PHOTO_SPHERE;
2551 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2552 mode = NavigationChange.Mode.PANORAMA;
2554 if (mFilmstripVisible) {
2555 mode = NavigationChange.Mode.FILMSTRIP;
2560 private void openModule(CameraModule module) {
2561 module.init(this, isSecureCamera(), isCaptureIntent());
2562 module.hardResetSettings(mSettingsManager);
2563 // Hide accessibility zoom UI by default. Modules will enable it themselves if required.
2564 getCameraAppUI().hideAccessibilityZoomUI();
2567 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2568 NavigationChange.InteractionCause.BUTTON);
2569 updatePreviewVisibility();
2573 private void closeModule(CameraModule module) {
2575 mCameraAppUI.clearModuleUI();
2578 private void performDeletion() {
2579 if (!mPendingDeletion) {
2582 hideUndoDeletionBar(false);
2583 mDataAdapter.executeDeletion();
2586 public void showUndoDeletionBar() {
2587 if (mPendingDeletion) {
2590 Log.v(TAG, "showing undo bar");
2591 mPendingDeletion = true;
2592 if (mUndoDeletionBar == null) {
2593 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2594 mAboveFilmstripControlLayout, true);
2595 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2596 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2597 button.setOnClickListener(new View.OnClickListener() {
2599 public void onClick(View view) {
2600 mDataAdapter.undoDeletion();
2601 hideUndoDeletionBar(true);
2604 // Setting undo bar clickable to avoid touch events going through
2605 // the bar to the buttons (eg. edit button, etc) underneath the bar.
2606 mUndoDeletionBar.setClickable(true);
2607 // When there is user interaction going on with the undo button, we
2608 // do not want to hide the undo bar.
2609 button.setOnTouchListener(new View.OnTouchListener() {
2611 public boolean onTouch(View v, MotionEvent event) {
2612 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2613 mIsUndoingDeletion = true;
2614 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2615 mIsUndoingDeletion = false;
2621 mUndoDeletionBar.setAlpha(0f);
2622 mUndoDeletionBar.setVisibility(View.VISIBLE);
2623 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2626 private void hideUndoDeletionBar(boolean withAnimation) {
2627 Log.v(TAG, "Hiding undo deletion bar");
2628 mPendingDeletion = false;
2629 if (mUndoDeletionBar != null) {
2630 if (withAnimation) {
2631 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2632 .setListener(new Animator.AnimatorListener() {
2634 public void onAnimationStart(Animator animation) {
2639 public void onAnimationEnd(Animator animation) {
2640 mUndoDeletionBar.setVisibility(View.GONE);
2644 public void onAnimationCancel(Animator animation) {
2649 public void onAnimationRepeat(Animator animation) {
2654 mUndoDeletionBar.setVisibility(View.GONE);
2660 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2663 * @param enable {@code true} to enable swipe.
2665 public void setSwipingEnabled(boolean enable) {
2666 // TODO: Bring back the functionality.
2667 if (isCaptureIntent()) {
2668 // lockPreview(true);
2670 // lockPreview(!enable);
2674 // Accessor methods for getting latency times used in performance testing
2675 public long getFirstPreviewTime() {
2676 if (mCurrentModule instanceof PhotoModule) {
2677 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2678 if (coverHiddenTime != -1) {
2679 return coverHiddenTime - mOnCreateTime;
2685 public long getAutoFocusTime() {
2686 return (mCurrentModule instanceof PhotoModule) ?
2687 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2690 public long getShutterLag() {
2691 return (mCurrentModule instanceof PhotoModule) ?
2692 ((PhotoModule) mCurrentModule).mShutterLag : -1;
2695 public long getShutterToPictureDisplayedTime() {
2696 return (mCurrentModule instanceof PhotoModule) ?
2697 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2700 public long getPictureDisplayedToJpegCallbackTime() {
2701 return (mCurrentModule instanceof PhotoModule) ?
2702 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2705 public long getJpegCallbackFinishTime() {
2706 return (mCurrentModule instanceof PhotoModule) ?
2707 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2710 public long getCaptureStartTime() {
2711 return (mCurrentModule instanceof PhotoModule) ?
2712 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2715 public boolean isRecording() {
2716 return (mCurrentModule instanceof VideoModule) ?
2717 ((VideoModule) mCurrentModule).isRecording() : false;
2720 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2721 return mCameraController;
2724 // For debugging purposes only.
2725 public CameraModule getCurrentModule() {
2726 return mCurrentModule;
2730 public void showTutorial(AbstractTutorialOverlay tutorial) {
2731 mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2735 public void finishActivityWithIntentCompleted(Intent resultIntent) {
2736 finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent);
2740 public void finishActivityWithIntentCanceled() {
2741 finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent());
2744 private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) {
2745 mResultCodeForTesting = resultCode;
2746 mResultDataForTesting = resultIntent;
2747 setResult(resultCode, resultIntent);
2751 private void keepScreenOnForAWhile() {
2752 if (mKeepScreenOn) {
2755 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2756 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2757 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2760 private void resetScreenOn() {
2761 mKeepScreenOn = false;
2762 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2763 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2767 * @return {@code true} if the Gallery is launched successfully.
2769 private boolean startGallery() {
2770 if (mGalleryIntent == null) {
2774 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2775 NavigationChange.InteractionCause.BUTTON);
2776 Intent startGalleryIntent = new Intent(mGalleryIntent);
2777 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
2778 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex);
2779 if (currentFilmstripItem != null) {
2780 GalleryHelper.setContentUri(startGalleryIntent,
2781 currentFilmstripItem.getData().getUri());
2783 launchActivityByIntent(startGalleryIntent);
2784 } catch (ActivityNotFoundException e) {
2785 Log.w(TAG, "Failed to launch gallery activity, closing");
2790 private void setNfcBeamPushUriFromData(FilmstripItem data) {
2791 final Uri uri = data.getData().getUri();
2792 if (uri != Uri.EMPTY) {
2793 mNfcPushUris[0] = uri;
2795 mNfcPushUris[0] = null;
2800 * Updates the visibility of the filmstrip bottom controls and action bar.
2802 private void updateUiByData(final int index) {
2803 final FilmstripItem currentData = mDataAdapter.getItemAt(index);
2804 if (currentData == null) {
2805 Log.w(TAG, "Current data ID not found.");
2806 hideSessionProgress();
2809 updateActionBarMenu(currentData);
2811 /* Bottom controls. */
2812 updateBottomControlsByData(currentData);
2814 if (isSecureCamera()) {
2815 // We cannot show buttons in secure camera since go to other
2816 // activities might create a security hole.
2817 mCameraAppUI.getFilmstripBottomControls().hideControls();
2821 setNfcBeamPushUriFromData(currentData);
2823 if (!mDataAdapter.isMetadataUpdatedAt(index)) {
2824 mDataAdapter.updateMetadataAt(index);
2829 * Updates the bottom controls based on the data.
2831 private void updateBottomControlsByData(final FilmstripItem currentData) {
2833 final CameraAppUI.BottomPanel filmstripBottomPanel =
2834 mCameraAppUI.getFilmstripBottomControls();
2835 filmstripBottomPanel.showControls();
2836 filmstripBottomPanel.setEditButtonVisibility(
2837 currentData.getAttributes().canEdit());
2838 filmstripBottomPanel.setShareButtonVisibility(
2839 currentData.getAttributes().canShare());
2840 filmstripBottomPanel.setDeleteButtonVisibility(
2841 currentData.getAttributes().canDelete());
2845 Uri contentUri = currentData.getData().getUri();
2846 CaptureSessionManager sessionManager = getServices()
2847 .getCaptureSessionManager();
2849 if (sessionManager.hasErrorMessage(contentUri)) {
2850 showProcessError(sessionManager.getErrorMessageId(contentUri));
2852 filmstripBottomPanel.hideProgressError();
2853 CaptureSession session = sessionManager.getSession(contentUri);
2855 if (session != null) {
2856 int sessionProgress = session.getProgress();
2858 if (sessionProgress < 0) {
2859 hideSessionProgress();
2861 int progressMessageId = session.getProgressMessageId();
2862 showSessionProgress(progressMessageId);
2863 updateSessionProgress(sessionProgress);
2866 hideSessionProgress();
2872 // We need to add this to a separate DB.
2873 final int viewButtonVisibility;
2874 if (currentData.getMetadata().isUsePanoramaViewer()) {
2875 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2876 } else if (currentData.getMetadata().isHasRgbzData()) {
2877 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2879 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2882 filmstripBottomPanel.setTinyPlanetEnabled(
2883 currentData.getMetadata().isPanorama360());
2884 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2887 private void showDetailsDialog(int index) {
2888 final FilmstripItem data = mDataAdapter.getItemAt(index);
2892 Optional<MediaDetails> details = data.getMediaDetails();
2893 if (!details.isPresent()) {
2896 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get());
2897 detailDialog.show();
2898 UsageStatistics.instance().mediaInteraction(
2899 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS,
2900 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index));
2904 * Show or hide action bar items depending on current data type.
2906 private void updateActionBarMenu(FilmstripItem data) {
2907 if (mActionBarMenu == null) {
2911 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2912 if (detailsMenuItem == null) {
2916 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo();
2917 detailsMenuItem.setVisible(showDetails);