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.Dialog;
23 import android.content.ActivityNotFoundException;
24 import android.content.BroadcastReceiver;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.ActivityInfo;
30 import android.content.res.Configuration;
31 import android.graphics.Bitmap;
32 import android.graphics.Matrix;
33 import android.graphics.RectF;
34 import android.graphics.SurfaceTexture;
35 import android.graphics.drawable.ColorDrawable;
36 import android.graphics.drawable.Drawable;
37 import android.net.Uri;
38 import android.nfc.NfcAdapter;
39 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
40 import android.nfc.NfcEvent;
41 import android.os.AsyncTask;
42 import android.os.Build;
43 import android.os.Bundle;
44 import android.os.Handler;
45 import android.os.HandlerThread;
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.ModuleManagerImpl;
80 import com.android.camera.app.MotionManager;
81 import com.android.camera.app.OrientationManager;
82 import com.android.camera.app.OrientationManagerImpl;
83 import com.android.camera.data.CameraFilmstripDataAdapter;
84 import com.android.camera.data.FilmstripContentObserver;
85 import com.android.camera.data.FilmstripItem;
86 import com.android.camera.data.FilmstripItemData;
87 import com.android.camera.data.FilmstripItemType;
88 import com.android.camera.data.FilmstripItemUtils;
89 import com.android.camera.data.FixedLastProxyAdapter;
90 import com.android.camera.data.GlideFilmstripManager;
91 import com.android.camera.data.LocalFilmstripDataAdapter;
92 import com.android.camera.data.LocalFilmstripDataAdapter.FilmstripItemListener;
93 import com.android.camera.data.MediaDetails;
94 import com.android.camera.data.MetadataLoader;
95 import com.android.camera.data.PhotoDataFactory;
96 import com.android.camera.data.PhotoItem;
97 import com.android.camera.data.PhotoItemFactory;
98 import com.android.camera.data.PlaceholderItem;
99 import com.android.camera.data.SessionItem;
100 import com.android.camera.data.VideoDataFactory;
101 import com.android.camera.data.VideoItemFactory;
102 import com.android.camera.debug.Log;
103 import com.android.camera.filmstrip.FilmstripContentPanel;
104 import com.android.camera.filmstrip.FilmstripController;
105 import com.android.camera.module.ModuleController;
106 import com.android.camera.module.ModulesInfo;
107 import com.android.camera.one.OneCameraException;
108 import com.android.camera.one.OneCameraManager;
109 import com.android.camera.session.CaptureSession;
110 import com.android.camera.session.CaptureSessionManager;
111 import com.android.camera.session.CaptureSessionManager.SessionListener;
112 import com.android.camera.settings.AppUpgrader;
113 import com.android.camera.settings.CameraSettingsActivity;
114 import com.android.camera.settings.Keys;
115 import com.android.camera.settings.ResolutionSetting;
116 import com.android.camera.settings.ResolutionUtil;
117 import com.android.camera.settings.SettingsManager;
118 import com.android.camera.stats.UsageStatistics;
119 import com.android.camera.tinyplanet.TinyPlanetFragment;
120 import com.android.camera.ui.AbstractTutorialOverlay;
121 import com.android.camera.ui.DetailsDialog;
122 import com.android.camera.ui.MainActivityLayout;
123 import com.android.camera.ui.ModeListView;
124 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
125 import com.android.camera.ui.PreviewStatusListener;
126 import com.android.camera.util.ApiHelper;
127 import com.android.camera.util.Callback;
128 import com.android.camera.util.CameraUtil;
129 import com.android.camera.util.GalleryHelper;
130 import com.android.camera.util.GcamHelper;
131 import com.android.camera.util.GoogleHelpHelper;
132 import com.android.camera.util.IntentHelper;
133 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
134 import com.android.camera.util.QuickActivity;
135 import com.android.camera.util.ReleaseHelper;
136 import com.android.camera.widget.FilmstripView;
137 import com.android.camera.widget.Preloader;
138 import com.android.camera2.R;
139 import com.android.ex.camera2.portability.CameraAgent;
140 import com.android.ex.camera2.portability.CameraAgentFactory;
141 import com.android.ex.camera2.portability.CameraExceptionHandler;
142 import com.android.ex.camera2.portability.CameraSettings;
143 import com.bumptech.glide.Glide;
144 import com.bumptech.glide.GlideBuilder;
145 import com.bumptech.glide.MemoryCategory;
146 import com.bumptech.glide.load.DecodeFormat;
147 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
148 import com.bumptech.glide.load.engine.prefill.PreFillType;
149 import com.google.common.base.Optional;
150 import com.google.common.logging.eventprotos;
151 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
152 import com.google.common.logging.eventprotos.MediaInteraction;
153 import com.google.common.logging.eventprotos.NavigationChange;
156 import java.lang.ref.WeakReference;
157 import java.util.ArrayList;
158 import java.util.HashMap;
159 import java.util.List;
161 public class CameraActivity extends QuickActivity
162 implements AppController, CameraAgent.CameraOpenCallback,
163 ShareActionProvider.OnShareTargetSelectedListener {
165 private static final Log.Tag TAG = new Log.Tag("CameraActivity");
167 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
168 "android.media.action.STILL_IMAGE_CAMERA_SECURE";
169 public static final String ACTION_IMAGE_CAPTURE_SECURE =
170 "android.media.action.IMAGE_CAPTURE_SECURE";
172 // The intent extra for camera from secure lock screen. True if the gallery
173 // should only show newly captured pictures. sSecureAlbumId does not
174 // increment. This is used when switching between camera, camcorder, and
175 // panorama. If the extra is not set, it is in the normal camera mode.
176 public static final String SECURE_CAMERA_EXTRA = "secure_camera";
178 public static final String MODULE_SCOPE_PREFIX = "_preferences_module_";
179 public static final String CAMERA_SCOPE_PREFIX = "_preferences_camera_";
181 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
182 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
183 /** Load metadata for 10 items ahead of our current. */
184 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
186 /** Should be used wherever a context is needed. */
187 private Context mAppContext;
190 * Camera fatal error handling:
191 * 1) Present error dialog to guide users to exit the app.
192 * 2) If users hit home button, onPause should just call finish() to exit the app.
194 private boolean mCameraFatalError = false;
197 * Whether onResume should reset the view to the preview.
199 private boolean mResetToPreviewOnResume = true;
202 * This data adapter is used by FilmStripView.
204 private VideoItemFactory mVideoItemFactory;
205 private PhotoItemFactory mPhotoItemFactory;
206 private LocalFilmstripDataAdapter mDataAdapter;
208 private OneCameraManager mCameraManager;
209 private SettingsManager mSettingsManager;
210 private ResolutionSetting mResolutionSetting;
211 private ModeListView mModeListView;
212 private boolean mModeListVisible = false;
213 private int mCurrentModeIndex;
214 private CameraModule mCurrentModule;
215 private ModuleManagerImpl mModuleManager;
216 private FrameLayout mAboveFilmstripControlLayout;
217 private FilmstripController mFilmstripController;
218 private boolean mFilmstripVisible;
219 /** Whether the filmstrip fully covers the preview. */
220 private boolean mFilmstripCoversPreview = false;
221 private int mResultCodeForTesting;
222 private Intent mResultDataForTesting;
223 private OnScreenHint mStorageHint;
224 private final Object mStorageSpaceLock = new Object();
225 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
226 private boolean mAutoRotateScreen;
227 private boolean mSecureCamera;
228 private OrientationManagerImpl mOrientationManager;
229 private LocationManager mLocationManager;
230 private ButtonManager mButtonManager;
231 private Handler mMainHandler;
232 private PanoramaViewHelper mPanoramaViewHelper;
233 private ActionBar mActionBar;
234 private ViewGroup mUndoDeletionBar;
235 private boolean mIsUndoingDeletion = false;
236 private boolean mIsActivityRunning = false;
238 private final Uri[] mNfcPushUris = new Uri[1];
240 private FilmstripContentObserver mLocalImagesObserver;
241 private FilmstripContentObserver mLocalVideosObserver;
243 private boolean mPendingDeletion = false;
245 private CameraController mCameraController;
246 private boolean mPaused;
247 private CameraAppUI mCameraAppUI;
249 private PeekAnimationHandler mPeekAnimationHandler;
250 private HandlerThread mPeekAnimationThread;
252 private Intent mGalleryIntent;
253 private long mOnCreateTime;
255 private Menu mActionBarMenu;
256 private Preloader<Integer, AsyncTask> mPreloader;
258 /** Can be used to play custom sounds. */
259 private SoundPlayer mSoundPlayer;
261 private static final int LIGHTS_OUT_DELAY_MS = 4000;
262 private final int BASE_SYS_UI_VISIBILITY =
263 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
264 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
265 private final Runnable mLightsOutRunnable = new Runnable() {
268 getWindow().getDecorView().setSystemUiVisibility(
269 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE);
272 private MemoryManager mMemoryManager;
273 private MotionManager mMotionManager;
275 /** First run dialog */
276 private FirstRunDialog mFirstRunDialog;
279 public CameraAppUI getCameraAppUI() {
284 public ModuleManager getModuleManager() {
285 return mModuleManager;
289 * Close activity when secure app passes lock screen or screen turns
292 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
294 public void onReceive(Context context, Intent intent) {
300 * Whether the screen is kept turned on.
302 private boolean mKeepScreenOn;
303 private int mLastLayoutOrientation;
304 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
305 new CameraAppUI.BottomPanel.Listener() {
308 * If the current photo is a photo sphere, this will launch the
309 * Photo Sphere panorama viewer.
312 public void onExternalViewer() {
313 if (mPanoramaViewHelper == null) {
316 final FilmstripItem data = getCurrentLocalData();
318 Log.w(TAG, "Cannot open null data.");
321 final Uri contentUri = data.getData().getUri();
322 if (contentUri == Uri.EMPTY) {
323 Log.w(TAG, "Cannot open empty URL.");
327 if (data.getMetadata().isUsePanoramaViewer()) {
328 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
329 } else if (data.getMetadata().isHasRgbzData()) {
330 mPanoramaViewHelper.showRgbz(contentUri);
331 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
332 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
333 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
334 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false);
335 mCameraAppUI.clearClingForViewer(
336 CameraAppUI.BottomPanel.VIEWER_REFOCUS);
342 public void onEdit() {
343 FilmstripItem data = getCurrentLocalData();
345 Log.w(TAG, "Cannot edit null data.");
348 final int currentDataId = getCurrentDataId();
349 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
351 MediaInteraction.InteractionType.EDIT,
352 NavigationChange.InteractionCause.BUTTON,
353 fileAgeFromAdapterAtIndex(currentDataId));
358 public void onTinyPlanet() {
359 FilmstripItem data = getCurrentLocalData();
361 Log.w(TAG, "Cannot edit tiny planet on null data.");
364 launchTinyPlanetEditor(data);
368 public void onDelete() {
369 final int currentDataId = getCurrentDataId();
370 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
372 MediaInteraction.InteractionType.DELETE,
373 NavigationChange.InteractionCause.BUTTON,
374 fileAgeFromAdapterAtIndex(currentDataId));
375 removeItemAt(currentDataId);
379 public void onShare() {
380 final FilmstripItem data = getCurrentLocalData();
382 Log.w(TAG, "Cannot share null data.");
386 final int currentDataId = getCurrentDataId();
387 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
389 MediaInteraction.InteractionType.SHARE,
390 NavigationChange.InteractionCause.BUTTON,
391 fileAgeFromAdapterAtIndex(currentDataId));
392 // If applicable, show release information before this item
394 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
395 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
396 new Callback<Void>() {
398 public void onCallback(Void result) {
407 private void share(FilmstripItem data) {
408 Intent shareIntent = getShareIntentByData(data);
409 if (shareIntent != null) {
411 launchActivityByIntent(shareIntent);
412 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
413 } catch (ActivityNotFoundException ex) {
419 private int getCurrentDataId() {
420 return mFilmstripController.getCurrentAdapterIndex();
423 private FilmstripItem getCurrentLocalData() {
424 return mDataAdapter.getItemAt(getCurrentDataId());
428 * Sets up the share intent and NFC properly according to the
431 * @param item The data to be shared.
433 private Intent getShareIntentByData(final FilmstripItem item) {
434 Intent intent = null;
435 final Uri contentUri = item.getData().getUri();
436 final String msgShareTo = getResources().getString(R.string.share_to);
438 if (item.getMetadata().isPanorama360() &&
439 item.getData().getUri() != Uri.EMPTY) {
440 intent = new Intent(Intent.ACTION_SEND);
441 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE);
442 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
443 } else if (item.getAttributes().canShare()) {
444 final String mimeType = item.getData().getMimeType();
445 intent = getShareIntentFromType(mimeType);
446 if (intent != null) {
447 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
448 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
450 intent = Intent.createChooser(intent, msgShareTo);
456 * Get the share intent according to the mimeType
458 * @param mimeType The mimeType of current data.
459 * @return the video/image's ShareIntent or null if mimeType is
462 private Intent getShareIntentFromType(String mimeType) {
463 // Lazily create the intent object.
464 Intent intent = new Intent(Intent.ACTION_SEND);
465 if (mimeType.startsWith("video/")) {
466 intent.setType("video/*");
468 if (mimeType.startsWith("image/")) {
469 intent.setType("image/*");
471 Log.w(TAG, "unsupported mimeType " + mimeType);
478 public void onProgressErrorClicked() {
479 FilmstripItem data = getCurrentLocalData();
480 getServices().getCaptureSessionManager().removeErrorMessage(
481 data.getData().getUri());
482 updateBottomControlsByData(data);
487 public void onCameraOpened(CameraAgent.CameraProxy camera) {
488 Log.v(TAG, "onCameraOpened");
490 // We've paused, but just asynchronously opened the camera. Close it
491 // because we should be releasing the camera when paused to allow
492 // other apps to access it.
493 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera");
494 mCameraController.closeCamera(false);
498 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
499 // We shouldn't be here. Just close the camera and leave.
500 mCameraController.closeCamera(false);
501 throw new IllegalStateException("Camera opened but the module shouldn't be " +
504 if (mCurrentModule != null) {
505 resetExposureCompensationToDefault(camera);
506 mCurrentModule.onCameraAvailable(camera);
508 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
510 Log.v(TAG, "invoking onChangeCamera");
511 mCameraAppUI.onChangeCamera();
514 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) {
515 // Reset the exposure compensation before handing the camera to module.
516 CameraSettings cameraSettings = camera.getSettings();
517 cameraSettings.setExposureCompensationIndex(0);
518 camera.applySettings(cameraSettings);
522 public void onCameraDisabled(int cameraId) {
523 UsageStatistics.instance().cameraFailure(
524 eventprotos.CameraFailure.FailureReason.SECURITY, null,
525 UsageStatistics.NONE, UsageStatistics.NONE);
526 Log.w(TAG, "Camera disabled: " + cameraId);
527 CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
531 public void onDeviceOpenFailure(int cameraId, String info) {
532 UsageStatistics.instance().cameraFailure(
533 eventprotos.CameraFailure.FailureReason.OPEN_FAILURE, info,
534 UsageStatistics.NONE, UsageStatistics.NONE);
535 Log.w(TAG, "Camera open failure: " + info);
536 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
540 public void onDeviceOpenedAlready(int cameraId, String info) {
541 Log.w(TAG, "Camera open already: " + cameraId + "," + info);
542 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
546 public void onReconnectionFailure(CameraAgent mgr, String info) {
547 UsageStatistics.instance().cameraFailure(
548 eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE, null,
549 UsageStatistics.NONE, UsageStatistics.NONE);
550 Log.w(TAG, "Camera reconnection failure:" + info);
551 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
554 private static class MainHandler extends Handler {
555 final WeakReference<CameraActivity> mActivity;
557 public MainHandler(CameraActivity activity, Looper looper) {
559 mActivity = new WeakReference<CameraActivity>(activity);
563 public void handleMessage(Message msg) {
564 CameraActivity activity = mActivity.get();
565 if (activity == null) {
570 case MSG_CLEAR_SCREEN_ON_FLAG: {
571 if (!activity.mPaused) {
572 activity.getWindow().clearFlags(
573 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
581 private String fileNameFromAdapterAtIndex(int index) {
582 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
583 if (filmstripItem == null) {
587 File localFile = new File(filmstripItem.getData().getFilePath());
588 return localFile.getName();
591 private float fileAgeFromAdapterAtIndex(int index) {
592 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
593 if (filmstripItem == null) {
597 File localFile = new File(filmstripItem.getData().getFilePath());
598 return 0.001f * (System.currentTimeMillis() - localFile.lastModified());
601 private final FilmstripContentPanel.Listener mFilmstripListener =
602 new FilmstripContentPanel.Listener() {
605 public void onSwipeOut() {
609 public void onSwipeOutBegin() {
611 mCameraAppUI.hideBottomControls();
612 mFilmstripCoversPreview = false;
613 updatePreviewVisibility();
617 public void onFilmstripHidden() {
618 mFilmstripVisible = false;
619 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
620 NavigationChange.InteractionCause.SWIPE_RIGHT);
621 CameraActivity.this.setFilmstripUiVisibility(false);
622 // When the user hide the filmstrip (either swipe out or
623 // tap on back key) we move to the first item so next time
624 // when the user swipe in the filmstrip, the most recent
626 mFilmstripController.goToFirstItem();
630 public void onFilmstripShown() {
631 mFilmstripVisible = true;
632 mCameraAppUI.hideCaptureIndicator();
633 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
634 NavigationChange.InteractionCause.SWIPE_LEFT);
635 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
639 public void onFocusedDataLongPressed(int adapterIndex) {
644 public void onFocusedDataPromoted(int adapterIndex) {
645 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
647 MediaInteraction.InteractionType.DELETE,
648 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex(
650 removeItemAt(adapterIndex);
654 public void onFocusedDataDemoted(int adapterIndex) {
655 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
657 MediaInteraction.InteractionType.DELETE,
658 NavigationChange.InteractionCause.SWIPE_DOWN,
659 fileAgeFromAdapterAtIndex(adapterIndex));
660 removeItemAt(adapterIndex);
664 public void onEnterFullScreenUiShown(int adapterIndex) {
665 if (mFilmstripVisible) {
666 CameraActivity.this.setFilmstripUiVisibility(true);
671 public void onLeaveFullScreenUiShown(int adapterIndex) {
676 public void onEnterFullScreenUiHidden(int adapterIndex) {
677 if (mFilmstripVisible) {
678 CameraActivity.this.setFilmstripUiVisibility(false);
683 public void onLeaveFullScreenUiHidden(int adapterIndex) {
688 public void onEnterFilmstrip(int adapterIndex) {
689 if (mFilmstripVisible) {
690 CameraActivity.this.setFilmstripUiVisibility(true);
695 public void onLeaveFilmstrip(int adapterIndex) {
700 public void onDataReloaded() {
701 if (!mFilmstripVisible) {
704 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
708 public void onDataUpdated(int adapterIndex) {
709 if (!mFilmstripVisible) {
712 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
716 public void onEnterZoomView(int adapterIndex) {
717 if (mFilmstripVisible) {
718 CameraActivity.this.setFilmstripUiVisibility(false);
723 public void onZoomAtIndexChanged(int adapterIndex, float zoom) {
724 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex);
725 long ageMillis = System.currentTimeMillis()
726 - filmstripItem.getData().getLastModifiedDate().getTime();
728 // Do not log if items is to old or does not have a path (which is
729 // being used as a key).
730 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) ||
731 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) {
734 File localFile = new File(filmstripItem.getData().getFilePath());
735 UsageStatistics.instance().mediaView(localFile.getName(),
736 filmstripItem.getData().getLastModifiedDate().getTime(), zoom);
740 public void onDataFocusChanged(final int prevIndex, final int newIndex) {
741 if (!mFilmstripVisible) {
744 // TODO: This callback is UI event callback, should always
745 // happen on UI thread. Find the reason for this
746 // runOnUiThread() and fix it.
747 runOnUiThread(new Runnable() {
750 updateUiByData(newIndex);
756 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
757 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
761 private final FilmstripItemListener mFilmstripItemListener =
762 new FilmstripItemListener() {
764 public void onMetadataUpdated(List<Integer> indexes) {
766 // Callback after the activity is paused.
769 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
770 for (Integer index : indexes) {
771 if (index == currentIndex) {
772 updateBottomControlsByData(mDataAdapter.getItemAt(index));
773 // Currently we have only 1 data can be matched.
774 // No need to look for more, break.
781 public void gotoGallery() {
782 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
783 NavigationChange.InteractionCause.BUTTON);
785 mFilmstripController.goToNextItem();
789 * If 'visible' is false, this hides the action bar. Also maintains
790 * lights-out at all times.
792 * @param visible is false, this hides the action bar and filmstrip bottom
795 private void setFilmstripUiVisibility(boolean visible) {
796 mLightsOutRunnable.run();
797 mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
798 if (visible != mActionBar.isShowing()) {
801 mCameraAppUI.showBottomControls();
804 mCameraAppUI.hideBottomControls();
807 mFilmstripCoversPreview = visible;
808 updatePreviewVisibility();
811 private void hideSessionProgress() {
812 mCameraAppUI.getFilmstripBottomControls().hideProgress();
815 private void showSessionProgress(CharSequence message) {
816 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
817 controls.setProgressText(message);
818 controls.hideControls();
819 controls.hideProgressError();
820 controls.showProgress();
823 private void showProcessError(CharSequence message) {
824 mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
827 private void updateSessionProgress(int progress) {
828 mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
831 private void updateSessionProgressText(CharSequence message) {
832 mCameraAppUI.getFilmstripBottomControls().setProgressText(message);
835 private void setupNfcBeamPush() {
836 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
837 if (adapter == null) {
841 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
843 adapter.setNdefPushMessage(null, CameraActivity.this);
847 adapter.setBeamPushUris(null, CameraActivity.this);
848 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
850 public Uri[] createBeamUris(NfcEvent event) {
853 }, CameraActivity.this);
857 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
858 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
859 if (currentIndex < 0) {
862 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex),
863 MediaInteraction.InteractionType.SHARE,
864 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex));
865 // TODO add intent.getComponent().getPackageName()
869 // Note: All callbacks come back on the main thread.
870 private final SessionListener mSessionListener =
871 new SessionListener() {
873 public void onSessionQueued(final Uri uri) {
874 Log.v(TAG, "onSessionQueued: " + uri);
875 if (!Storage.isSessionUri(uri)) {
878 SessionItem newData = new SessionItem(getApplicationContext(), uri);
879 mDataAdapter.addOrUpdate(newData);
883 public void onSessionDone(final Uri sessionUri) {
884 Log.v(TAG, "onSessionDone:" + sessionUri);
885 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
886 if (contentUri == null) {
887 mDataAdapter.refresh(sessionUri);
890 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri);
892 // This can be null if e.g. a session is canceled (e.g.
893 // through discard panorama). It might be worth adding
894 // onSessionCanceled or the like this interface.
895 if (newData == null) {
896 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri);
900 // Make the PhotoItem aware of the session placeholder, to
901 // allow it to make a smooth transition to its content.
902 newData.setSessionPlaceholderBitmap(
903 Storage.getPlacerHolderForSession(sessionUri));
905 final int pos = mDataAdapter.findByContentUri(sessionUri);
907 // We do not have a placeholder for this image, perhaps
908 // due to the activity crashing or being killed.
909 mDataAdapter.addOrUpdate(newData);
911 mDataAdapter.updateItemAt(pos, newData);
916 public void onSessionProgress(final Uri uri, final int progress) {
918 // Do nothing, there is no task for this URI.
921 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
922 if (currentIndex == -1) {
926 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
927 updateSessionProgress(progress);
932 public void onSessionProgressText(final Uri uri, final CharSequence message) {
933 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
934 if (currentIndex == -1) {
938 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
939 updateSessionProgressText(message);
944 public void onSessionUpdated(Uri uri) {
945 Log.v(TAG, "onSessionUpdated: " + uri);
946 mDataAdapter.refresh(uri);
950 public void onSessionPreviewAvailable(Uri uri) {
951 Log.v(TAG, "onSessionPreviewAvailable: " + uri);
952 mDataAdapter.refresh(uri);
953 int index = mDataAdapter.findByContentUri(uri);
955 startPeekAnimation(mDataAdapter.getItemAt(index),
956 mCurrentModule.getPeekAccessibilityString());
961 public void onSessionFailed(Uri uri, CharSequence reason) {
962 Log.v(TAG, "onSessionFailed:" + uri);
964 int failedIndex = mDataAdapter.findByContentUri(uri);
965 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
967 if (currentIndex == failedIndex) {
968 updateSessionProgress(0);
969 showProcessError(reason);
972 mDataAdapter.refresh(uri);
977 public Context getAndroidContext() {
982 public Dialog createDialog() {
983 return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
987 public void launchActivityByIntent(Intent intent) {
988 // Starting from L, we prefer not to start edit activity within camera's task.
989 mResetToPreviewOnResume = false;
990 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
992 startActivity(intent);
996 public int getCurrentModuleIndex() {
997 return mCurrentModeIndex;
1001 public int getCurrentCameraId() {
1002 return mCameraController.getCurrentCameraId();
1006 public String getModuleScope() {
1007 return MODULE_SCOPE_PREFIX + mCurrentModule.getModuleStringIdentifier();
1011 public String getCameraScope() {
1012 int currentCameraId = getCurrentCameraId();
1013 if (currentCameraId < 0) {
1014 // if an unopen camera i.e. negative ID is returned, which we've observed in
1015 // some automated scenarios, just return it as a valid separate scope
1016 // this could cause user issues, so log a stack trace noting the call path
1017 // which resulted in this scenario.
1018 Log.w(TAG, "getting camera scope with no open camera, using id: " + currentCameraId);
1020 return CAMERA_SCOPE_PREFIX + Integer.toString(currentCameraId);
1024 public ModuleController getCurrentModuleController() {
1025 return mCurrentModule;
1029 public int getQuickSwitchToModuleId(int currentModuleIndex) {
1030 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
1035 public SurfaceTexture getPreviewBuffer() {
1036 // TODO: implement this
1041 public void onPreviewReadyToStart() {
1042 mCameraAppUI.onPreviewReadyToStart();
1046 public void onPreviewStarted() {
1047 mCameraAppUI.onPreviewStarted();
1051 public void addPreviewAreaSizeChangedListener(
1052 PreviewStatusListener.PreviewAreaChangedListener listener) {
1053 mCameraAppUI.addPreviewAreaChangedListener(listener);
1057 public void removePreviewAreaSizeChangedListener(
1058 PreviewStatusListener.PreviewAreaChangedListener listener) {
1059 mCameraAppUI.removePreviewAreaChangedListener(listener);
1063 public void setupOneShotPreviewListener() {
1064 mCameraController.setOneShotPreviewCallback(mMainHandler,
1065 new CameraAgent.CameraPreviewDataCallback() {
1067 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) {
1068 mCurrentModule.onPreviewInitialDataReceived();
1069 mCameraAppUI.onNewPreviewFrame();
1076 public void updatePreviewAspectRatio(float aspectRatio) {
1077 mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
1081 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
1082 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio);
1086 public RectF getFullscreenRect() {
1087 return mCameraAppUI.getFullscreenRect();
1091 public void updatePreviewTransform(Matrix matrix) {
1092 mCameraAppUI.updatePreviewTransform(matrix);
1096 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1097 mCameraAppUI.setPreviewStatusListener(previewStatusListener);
1101 public FrameLayout getModuleLayoutRoot() {
1102 return mCameraAppUI.getModuleRootView();
1106 public void setShutterEventsListener(ShutterEventsListener listener) {
1107 // TODO: implement this
1111 public void setShutterEnabled(boolean enabled) {
1112 mCameraAppUI.setShutterButtonEnabled(enabled);
1116 public boolean isShutterEnabled() {
1117 return mCameraAppUI.isShutterButtonEnabled();
1121 public void startFlashAnimation(boolean shortFlash) {
1122 mCameraAppUI.startFlashAnimation(shortFlash);
1126 public void startPreCaptureAnimation() {
1127 // TODO: implement this
1131 public void cancelPreCaptureAnimation() {
1132 // TODO: implement this
1136 public void startPostCaptureAnimation() {
1137 // TODO: implement this
1141 public void startPostCaptureAnimation(Bitmap thumbnail) {
1142 // TODO: implement this
1146 public void cancelPostCaptureAnimation() {
1147 // TODO: implement this
1151 public OrientationManager getOrientationManager() {
1152 return mOrientationManager;
1156 public LocationManager getLocationManager() {
1157 return mLocationManager;
1161 public void lockOrientation() {
1162 if (mOrientationManager != null) {
1163 mOrientationManager.lockOrientation();
1168 public void unlockOrientation() {
1169 if (mOrientationManager != null) {
1170 mOrientationManager.unlockOrientation();
1175 * Starts the filmstrip peek animation if the filmstrip is not visible.
1177 * @param data The data to peek.
1178 * @param accessibilityString Accessibility string to announce on peek animation.
1180 private void startPeekAnimation(final FilmstripItem data, final String accessibilityString) {
1181 if (mFilmstripVisible || mPeekAnimationHandler == null) {
1185 if (!data.getAttributes().isImage() &&
1186 !data.getAttributes().isVideo() &&
1187 !data.getAttributes().isRendering()) {
1191 // Don't show capture indicator in Photo Sphere.
1192 // TODO: Don't reach into resources to figure out the current mode.
1193 final int photosphereModuleId = getApplicationContext().getResources().getInteger(
1194 R.integer.camera_mode_photosphere);
1195 if (mCurrentModeIndex == photosphereModuleId) {
1198 mPeekAnimationHandler.startDecodingJob(data, new Callback<Bitmap>() {
1200 public void onCallback(Bitmap result) {
1201 mCameraAppUI.startCaptureIndicatorRevealAnimation(accessibilityString);
1202 mCameraAppUI.updateCaptureIndicatorThumbnail(result, 0);
1208 public void notifyNewMedia(Uri uri) {
1209 // TODO: This method is running on the main thread. Also we should get
1210 // rid of that AsyncTask.
1212 updateStorageSpaceAndHint(null);
1213 ContentResolver cr = getContentResolver();
1214 String mimeType = cr.getType(uri);
1215 FilmstripItem newData = null;
1216 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
1217 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1218 newData = mVideoItemFactory.queryContentUri(uri);
1219 if (newData == null) {
1220 Log.e(TAG, "Can't find video data in content resolver:" + uri);
1223 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
1224 CameraUtil.broadcastNewPicture(mAppContext, uri);
1225 newData = mPhotoItemFactory.queryContentUri(uri);
1226 if (newData == null) {
1227 Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1231 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1235 // We are preloading the metadata for new video since we need the
1236 // rotation info for the thumbnail.
1237 new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
1239 protected FilmstripItem doInBackground(FilmstripItem... params) {
1240 FilmstripItem data = params[0];
1241 MetadataLoader.loadMetadata(getAndroidContext(), data);
1246 protected void onPostExecute(FilmstripItem data) {
1247 // TODO: Figure out why sometimes the data is aleady there.
1248 mDataAdapter.addOrUpdate(data);
1249 startPeekAnimation(data, mCurrentModule.getPeekAccessibilityString());
1251 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
1255 public void enableKeepScreenOn(boolean enabled) {
1260 mKeepScreenOn = enabled;
1261 if (mKeepScreenOn) {
1262 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1263 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1265 keepScreenOnForAWhile();
1270 public CameraProvider getCameraProvider() {
1271 return mCameraController;
1275 public OneCameraManager getCameraManager() {
1276 return mCameraManager;
1279 private void removeItemAt(int index) {
1280 mDataAdapter.removeAt(index);
1281 if (mDataAdapter.getTotalNumber() > 1) {
1282 showUndoDeletionBar();
1284 // If camera preview is the only view left in filmstrip,
1285 // no need to show undo bar.
1286 mPendingDeletion = true;
1288 if (mFilmstripVisible) {
1289 mCameraAppUI.getFilmstripContentPanel().animateHide();
1295 public boolean onOptionsItemSelected(MenuItem item) {
1296 // Handle presses on the action bar items
1297 switch (item.getItemId()) {
1298 case android.R.id.home:
1301 case R.id.action_details:
1302 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex());
1304 case R.id.action_help_and_feedback:
1305 mResetToPreviewOnResume = false;
1306 new GoogleHelpHelper(this).launchGoogleHelp();
1309 return super.onOptionsItemSelected(item);
1313 private boolean isCaptureIntent() {
1314 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1315 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1316 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1324 * Note: Make sure this callback is unregistered properly when the activity
1325 * is destroyed since we're otherwise leaking the Activity reference.
1327 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
1328 = new CameraExceptionHandler.CameraExceptionCallback() {
1330 public void onCameraError(int errorCode) {
1331 // Not a fatal error. only do Log.e().
1332 Log.e(TAG, "Camera error callback. error=" + errorCode);
1335 public void onCameraException(
1336 RuntimeException ex, String commandHistory, int action, int state) {
1337 Log.e(TAG, "Camera Exception", ex);
1338 UsageStatistics.instance().cameraFailure(
1339 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
1340 commandHistory, action, state);
1344 public void onDispatchThreadException(RuntimeException ex) {
1345 Log.e(TAG, "DispatchThread Exception", ex);
1346 UsageStatistics.instance().cameraFailure(
1347 eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
1348 null, UsageStatistics.NONE, UsageStatistics.NONE);
1351 private void onFatalError() {
1352 if (mCameraFatalError) {
1355 mCameraFatalError = true;
1357 // If the activity receives exception during onPause, just exit the app.
1358 if (mPaused && !isFinishing()) {
1359 Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
1362 CameraUtil.showErrorAndFinish(CameraActivity.this,
1363 R.string.cannot_connect_camera);
1369 public void onNewIntentTasks(Intent intent) {
1370 onModeSelected(getModeIndex());
1374 public void onCreateTasks(Bundle state) {
1375 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1376 mAppContext = getApplicationContext();
1378 if (!Glide.isSetup()) {
1379 Context context = getAndroidContext();
1380 Glide.setup(new GlideBuilder(context)
1381 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888)
1382 .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
1384 Glide glide = Glide.get(context);
1386 // As a camera we will use a large amount of memory
1387 // for displaying images.
1388 glide.setMemoryCategory(MemoryCategory.HIGH);
1390 // Prefill glides bitmap pool to prevent excessive jank
1391 // when loading large images.
1392 glide.preFillBitmapPool(
1393 new PreFillType.Builder(GlideFilmstripManager.MAXIMUM_TEXTURE_SIZE)
1395 // It's more important for jank and GC to have
1396 // A larger weight of max texture size images than
1397 // media store sized images.
1398 new PreFillType.Builder(
1399 GlideFilmstripManager.MEDIASTORE_THUMB_WIDTH,
1400 GlideFilmstripManager.MEDIASTORE_THUMB_HEIGHT));
1403 mOnCreateTime = System.currentTimeMillis();
1404 mSoundPlayer = new SoundPlayer(mAppContext);
1407 mCameraManager = OneCameraManager.get(this, ResolutionUtil.getDisplayMetrics(this));
1408 } catch (OneCameraException e) {
1409 // Log error and continue. Modules requiring OneCamera should check
1410 // and handle if null by showing error dialog or other treatment.
1411 Log.e(TAG, "Creating camera manager failed.", e);
1412 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
1415 // TODO: Try to move all the resources allocation to happen as soon as
1416 // possible so we can call module.init() at the earliest time.
1417 mModuleManager = new ModuleManagerImpl();
1418 GcamHelper.init(getContentResolver());
1419 ModulesInfo.setupModules(mAppContext, mModuleManager);
1421 mSettingsManager = getServices().getSettingsManager();
1422 AppUpgrader appUpgrader = new AppUpgrader(this);
1423 appUpgrader.upgrade(mSettingsManager);
1424 Keys.setDefaults(mSettingsManager, mAppContext);
1425 mResolutionSetting = new ResolutionSetting(mSettingsManager, mCameraManager);
1427 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1428 // We suppress this flag via theme when drawing the system preview
1429 // background, but once we create activity here, reactivate to the
1430 // default value. The default is important for L, we don't want to
1431 // change app behavior, just starting background drawable layout.
1432 if (ApiHelper.isLOrHigher()) {
1433 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1435 setContentView(R.layout.activity_main);
1436 // A window background is set in styles.xml for the system to show a
1437 // drawable background with gray color and camera icon before the
1438 // activity is created. We set the background to null here to prevent
1439 // overdraw, all views must take care of drawing backgrounds if
1440 // necessary. This call to setBackgroundDrawable must occur after
1441 // setContentView, otherwise a background may be set again from the
1443 getWindow().setBackgroundDrawable(null);
1445 mActionBar = getActionBar();
1446 // set actionbar background to 100% or 50% transparent
1447 if (ApiHelper.isLOrHigher()) {
1448 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000));
1450 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
1453 mMainHandler = new MainHandler(this, getMainLooper());
1454 mCameraController = new CameraController(mAppContext, this, mMainHandler,
1455 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1456 CameraAgentFactory.CameraApi.API_1),
1457 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1458 CameraAgentFactory.CameraApi.AUTO));
1459 mCameraController.setCameraExceptionHandler(
1460 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
1462 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1463 mModeListView.init(mModuleManager.getSupportedModeIndexList());
1464 if (ApiHelper.HAS_ROTATION_ANIMATION) {
1465 setRotationAnimation();
1467 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1469 public void onVisibilityChanged(boolean visible) {
1470 mModeListVisible = visible;
1471 mCameraAppUI.setShutterButtonImportantToA11y(!visible);
1472 updatePreviewVisibility();
1476 // Check if this is in the secure camera mode.
1477 Intent intent = getIntent();
1478 String action = intent.getAction();
1479 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1480 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1481 mSecureCamera = true;
1483 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1486 if (mSecureCamera) {
1487 // Change the window flags so that secure camera can show when
1489 Window win = getWindow();
1490 WindowManager.LayoutParams params = win.getAttributes();
1491 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1492 win.setAttributes(params);
1494 // Filter for screen off so that we can finish secure camera
1495 // activity when screen is off.
1496 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1497 registerReceiver(mShutdownReceiver, filter_screen_off);
1499 // Filter for phone unlock so that we can finish secure camera
1500 // via this UI path:
1501 // 1. from secure lock screen, user starts secure camera
1502 // 2. user presses home button
1503 // 3. user unlocks phone
1504 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
1505 registerReceiver(mShutdownReceiver, filter_user_unlock);
1507 mCameraAppUI = new CameraAppUI(this,
1508 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1510 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1512 mAboveFilmstripControlLayout =
1513 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1515 // Add the session listener so we can track the session progress
1517 getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1518 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1519 mFilmstripController.setImageGap(
1520 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1521 mPanoramaViewHelper = new PanoramaViewHelper(this);
1522 mPanoramaViewHelper.onCreate();
1524 ContentResolver appContentResolver = mAppContext.getContentResolver();
1525 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
1526 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
1527 new PhotoDataFactory());
1528 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
1529 new VideoDataFactory());
1530 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
1531 mPhotoItemFactory, mVideoItemFactory);
1532 mDataAdapter.setLocalDataListener(mFilmstripItemListener);
1534 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1537 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1538 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1539 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1540 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1543 mLocationManager = new LocationManager(mAppContext);
1544 mOrientationManager = new OrientationManagerImpl(this, mMainHandler);
1546 setModuleFromModeIndex(getModeIndex());
1547 mCameraAppUI.prepareModuleUI();
1548 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1550 if (!mSecureCamera) {
1551 mFilmstripController.setDataAdapter(mDataAdapter);
1552 if (!isCaptureIntent()) {
1553 mDataAdapter.requestLoad(new Callback<Void>() {
1555 public void onCallback(Void result) {
1556 fillTemporarySessions();
1561 // Put a lock placeholder as the last image by setting its date to
1563 ImageView v = (ImageView) getLayoutInflater().inflate(
1564 R.layout.secure_album_placeholder, null);
1565 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
1566 v.setOnClickListener(new View.OnClickListener() {
1568 public void onClick(View view) {
1569 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1570 NavigationChange.InteractionCause.BUTTON);
1575 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1576 mDataAdapter = new FixedLastProxyAdapter(
1579 new PlaceholderItem(
1581 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
1582 v.getDrawable().getIntrinsicWidth(),
1583 v.getDrawable().getIntrinsicHeight()));
1584 // Flush out all the original data.
1585 mDataAdapter.clear();
1586 mFilmstripController.setDataAdapter(mDataAdapter);
1591 mLocalImagesObserver = new FilmstripContentObserver();
1592 mLocalVideosObserver = new FilmstripContentObserver();
1594 getContentResolver().registerContentObserver(
1595 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1596 mLocalImagesObserver);
1597 getContentResolver().registerContentObserver(
1598 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1599 mLocalVideosObserver);
1600 mMemoryManager = getServices().getMemoryManager();
1602 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1605 HashMap memoryData = mMemoryManager.queryMemory();
1606 UsageStatistics.instance().reportMemoryConsumed(memoryData,
1607 MemoryQuery.REPORT_LABEL_LAUNCH);
1610 mMotionManager = getServices().getMotionManager();
1612 mFirstRunDialog = new FirstRunDialog(this, new FirstRunDialog.FirstRunDialogListener() {
1614 public void onFirstRunStateReady() {
1615 // Run normal resume tasks.
1620 public void onCameraAccessException() {
1621 CameraUtil.showErrorAndFinish(CameraActivity.this, R.string.cannot_connect_camera);
1627 * Get the current mode index from the Intent or from persistent
1630 public int getModeIndex() {
1632 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1633 int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1634 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1635 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1636 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1637 modeIndex = videoIndex;
1638 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())) {
1640 modeIndex = photoIndex;
1641 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1642 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1644 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1645 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1646 Keys.KEY_CAMERA_MODULE_LAST_USED);
1648 // For upgraders who have not seen the aspect ratio selection screen,
1649 // we need to drop them back in the photo module and have them select
1651 // TODO: Move this to SettingsManager as an upgrade procedure.
1652 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1653 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
1654 modeIndex = photoIndex;
1657 // If the activity has not been started using an explicit intent,
1658 // read the module index from the last time the user changed modes
1659 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1660 Keys.KEY_STARTUP_MODULE_INDEX);
1661 if ((modeIndex == gcamIndex &&
1662 !GcamHelper.hasGcamAsSeparateModule()) || modeIndex < 0) {
1663 modeIndex = photoIndex;
1670 * Call this whenever the mode drawer or filmstrip change the visibility
1673 private void updatePreviewVisibility() {
1674 if (mCurrentModule == null) {
1678 int visibility = getPreviewVisibility();
1679 mCameraAppUI.onPreviewVisiblityChanged(visibility);
1680 updatePreviewRendering(visibility);
1681 mCurrentModule.onPreviewVisibilityChanged(visibility);
1684 private void updatePreviewRendering(int visibility) {
1685 if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1686 mCameraAppUI.pausePreviewRendering();
1688 mCameraAppUI.resumePreviewRendering();
1692 private int getPreviewVisibility() {
1693 if (mFilmstripCoversPreview) {
1694 return ModuleController.VISIBILITY_HIDDEN;
1695 } else if (mModeListVisible){
1696 return ModuleController.VISIBILITY_COVERED;
1698 return ModuleController.VISIBILITY_VISIBLE;
1702 private void setRotationAnimation() {
1703 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1704 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1705 Window win = getWindow();
1706 WindowManager.LayoutParams winParams = win.getAttributes();
1707 winParams.rotationAnimation = rotationAnimation;
1708 win.setAttributes(winParams);
1712 public void onUserInteraction() {
1713 super.onUserInteraction();
1714 if (!isFinishing()) {
1715 keepScreenOnForAWhile();
1720 public boolean dispatchTouchEvent(MotionEvent ev) {
1721 boolean result = super.dispatchTouchEvent(ev);
1722 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1723 // Real deletion is postponed until the next user interaction after
1724 // the gesture that triggers deletion. Until real deletion is
1725 // performed, users can click the undo button to bring back the
1726 // image that they chose to delete.
1727 if (mPendingDeletion && !mIsUndoingDeletion) {
1735 public void onPauseTasks() {
1736 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1739 * Save the last module index after all secure camera and icon launches,
1740 * not just on mode switches.
1742 * Right now we exclude capture intents from this logic, because we also
1743 * ignore the cross-Activity recovery logic in onStart for capture intents.
1745 if (!isCaptureIntent()) {
1746 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
1747 Keys.KEY_STARTUP_MODULE_INDEX,
1752 if (mPeekAnimationHandler != null) {
1753 mPeekAnimationHandler = null;
1754 mPeekAnimationThread.quitSafely();
1755 mPeekAnimationThread = null;
1757 mCameraAppUI.hideCaptureIndicator();
1758 mFirstRunDialog.dismiss();
1760 // Delete photos that are pending deletion
1762 mCurrentModule.pause();
1763 mOrientationManager.pause();
1764 mPanoramaViewHelper.onPause();
1766 mLocalImagesObserver.setForegroundChangeListener(null);
1767 mLocalImagesObserver.setActivityPaused(true);
1768 mLocalVideosObserver.setActivityPaused(true);
1769 mPreloader.cancelAllLoads();
1772 mMotionManager.stop();
1774 // Always stop recording location when paused. Resume will start
1775 // location recording again if the location setting is on.
1776 mLocationManager.recordLocation(false);
1778 UsageStatistics.instance().backgrounded();
1780 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
1781 // button. Let's just kill the process.
1782 if (mCameraFatalError && !isFinishing()) {
1783 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
1786 // Close the camera and wait for the operation done.
1787 Log.v(TAG, "onPause closing camera");
1788 mCameraController.closeCamera(true);
1793 public void onResumeTasks() {
1796 // Show the dialog if necessary. The rest resume logic will be invoked
1797 // at the onFirstRunStateReady() callback.
1798 mFirstRunDialog.showIfNecessary();
1801 private void resume() {
1802 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1803 Log.v(TAG, "Build info: " + Build.DISPLAY);
1805 updateStorageSpaceAndHint(null);
1807 mLastLayoutOrientation = getResources().getConfiguration().orientation;
1809 // TODO: Handle this in OrientationManager.
1811 if (Settings.System.getInt(getContentResolver(),
1812 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1813 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1814 mAutoRotateScreen = false;
1816 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1817 mAutoRotateScreen = true;
1820 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and
1821 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
1822 // lockscreen onResume->onPause->onResume sequence.
1824 String action = getIntent().getAction();
1825 if (action == null) {
1826 source = ForegroundSource.UNKNOWN_SOURCE;
1829 case MediaStore.ACTION_IMAGE_CAPTURE:
1830 source = ForegroundSource.ACTION_IMAGE_CAPTURE;
1832 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
1833 // was UNKNOWN_SOURCE in Fishlake.
1834 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
1836 case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
1837 // was UNKNOWN_SOURCE in Fishlake.
1838 source = ForegroundSource.ACTION_VIDEO_CAMERA;
1840 case MediaStore.ACTION_VIDEO_CAPTURE:
1841 source = ForegroundSource.ACTION_VIDEO_CAPTURE;
1843 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
1844 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
1845 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
1847 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
1848 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
1850 case Intent.ACTION_MAIN:
1851 source = ForegroundSource.ACTION_MAIN;
1854 source = ForegroundSource.UNKNOWN_SOURCE;
1858 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode());
1860 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
1861 if (ApiHelper.isLOrHigher()) {
1862 // hide the up affordance for L devices, it's not very Materially
1863 mActionBar.setDisplayShowHomeEnabled(false);
1866 mOrientationManager.resume();
1867 mPeekAnimationThread = new HandlerThread("Peek animation");
1868 mPeekAnimationThread.start();
1869 mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper(),
1870 mMainHandler, mAboveFilmstripControlLayout);
1872 mCurrentModule.hardResetSettings(mSettingsManager);
1873 mCurrentModule.resume();
1874 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
1875 NavigationChange.InteractionCause.BUTTON);
1876 setSwipingEnabled(true);
1878 if (!mResetToPreviewOnResume) {
1879 FilmstripItem item = mDataAdapter.getItemAt(
1880 mFilmstripController.getCurrentAdapterIndex());
1882 mDataAdapter.refresh(item.getData().getUri());
1885 // The share button might be disabled to avoid double tapping.
1886 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1887 // Default is showing the preview, unless disabled by explicitly
1888 // starting an activity we want to return from to the filmstrip rather
1889 // than the preview.
1890 mResetToPreviewOnResume = true;
1892 if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1893 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1894 if (!mSecureCamera) {
1895 // If it's secure camera, requestLoad() should not be called
1896 // as it will load all the data.
1897 if (!mFilmstripVisible) {
1898 mDataAdapter.requestLoad(new Callback<Void>() {
1900 public void onCallback(Void result) {
1901 fillTemporarySessions();
1905 mDataAdapter.requestLoadNewPhotos();
1909 mLocalImagesObserver.setActivityPaused(false);
1910 mLocalVideosObserver.setActivityPaused(false);
1911 if (!mSecureCamera) {
1912 mLocalImagesObserver.setForegroundChangeListener(
1913 new FilmstripContentObserver.ChangeListener() {
1915 public void onChange() {
1916 mDataAdapter.requestLoadNewPhotos();
1921 keepScreenOnForAWhile();
1923 // Lights-out mode at all times.
1924 final View rootView = findViewById(R.id.activity_root_view);
1925 mLightsOutRunnable.run();
1926 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
1927 new OnSystemUiVisibilityChangeListener() {
1929 public void onSystemUiVisibilityChange(int visibility) {
1930 mMainHandler.removeCallbacks(mLightsOutRunnable);
1931 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
1935 mPanoramaViewHelper.onResume();
1936 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1938 // Enable location recording if the setting is on.
1939 final boolean locationRecordingEnabled =
1940 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
1941 mLocationManager.recordLocation(locationRecordingEnabled);
1943 final int previewVisibility = getPreviewVisibility();
1944 updatePreviewRendering(previewVisibility);
1946 mMotionManager.start();
1949 private void fillTemporarySessions() {
1950 if (mSecureCamera) {
1953 // There might be sessions still in flight (processed by our service).
1954 // Make sure they're added to the filmstrip.
1955 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
1959 public void onStartTasks() {
1960 mIsActivityRunning = true;
1961 mPanoramaViewHelper.onStart();
1964 * If we're starting after launching a different Activity (lockscreen),
1965 * we need to use the last mode used in the other Activity, and
1966 * not the old one from this Activity.
1968 * This needs to happen before CameraAppUI.resume() in order to set the
1969 * mode cover icon to the actual last mode used.
1971 * Right now we exclude capture intents from this logic.
1973 int modeIndex = getModeIndex();
1974 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
1975 onModeSelected(modeIndex);
1978 if (mResetToPreviewOnResume) {
1979 mCameraAppUI.resume();
1980 mResetToPreviewOnResume = false;
1985 protected void onStopTasks() {
1986 mIsActivityRunning = false;
1987 mPanoramaViewHelper.onStop();
1989 mLocationManager.disconnect();
1993 public void onDestroyTasks() {
1994 if (mSecureCamera) {
1995 unregisterReceiver(mShutdownReceiver);
1998 mSettingsManager.removeAllListeners();
1999 mCameraController.removeCallbackReceiver();
2000 mCameraController.setCameraExceptionHandler(null);
2001 getContentResolver().unregisterContentObserver(mLocalImagesObserver);
2002 getContentResolver().unregisterContentObserver(mLocalVideosObserver);
2003 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
2004 mCameraAppUI.onDestroy();
2005 mModeListView.setVisibilityChangedListener(null);
2006 mCameraController = null;
2007 mSettingsManager = null;
2008 mOrientationManager = null;
2009 mButtonManager = null;
2010 mSoundPlayer.release();
2011 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
2012 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
2016 public void onConfigurationChanged(Configuration config) {
2017 super.onConfigurationChanged(config);
2018 Log.v(TAG, "onConfigurationChanged");
2019 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
2023 if (mLastLayoutOrientation != config.orientation) {
2024 mLastLayoutOrientation = config.orientation;
2025 mCurrentModule.onLayoutOrientationChanged(
2026 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
2031 public boolean onKeyDown(int keyCode, KeyEvent event) {
2032 if (!mFilmstripVisible) {
2033 if (mCurrentModule.onKeyDown(keyCode, event)) {
2036 // Prevent software keyboard or voice search from showing up.
2037 if (keyCode == KeyEvent.KEYCODE_SEARCH
2038 || keyCode == KeyEvent.KEYCODE_MENU) {
2039 if (event.isLongPress()) {
2045 return super.onKeyDown(keyCode, event);
2049 public boolean onKeyUp(int keyCode, KeyEvent event) {
2050 if (!mFilmstripVisible) {
2051 // If a module is in the middle of capture, it should
2052 // consume the key event.
2053 if (mCurrentModule.onKeyUp(keyCode, event)) {
2055 } else if (keyCode == KeyEvent.KEYCODE_MENU
2056 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2057 // Let the mode list view consume the event.
2058 mCameraAppUI.openModeList();
2060 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2061 mCameraAppUI.showFilmstrip();
2065 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2066 mFilmstripController.goToNextItem();
2068 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2069 boolean wentToPrevious = mFilmstripController.goToPreviousItem();
2070 if (!wentToPrevious) {
2071 // at beginning of filmstrip, hide and go back to preview
2072 mCameraAppUI.hideFilmstrip();
2077 return super.onKeyUp(keyCode, event);
2081 public void onBackPressed() {
2082 if (!mCameraAppUI.onBackPressed()) {
2083 if (!mCurrentModule.onBackPressed()) {
2084 super.onBackPressed();
2090 public boolean isAutoRotateScreen() {
2091 // TODO: Move to OrientationManager.
2092 return mAutoRotateScreen;
2096 public boolean onCreateOptionsMenu(Menu menu) {
2097 MenuInflater inflater = getMenuInflater();
2098 inflater.inflate(R.menu.filmstrip_menu, menu);
2099 mActionBarMenu = menu;
2101 // add a button for launching the gallery
2102 if (mGalleryIntent != null) {
2103 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
2104 if (appName != null) {
2105 MenuItem menuItem = menu.add(appName);
2106 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2107 menuItem.setIntent(mGalleryIntent);
2109 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
2110 if (galleryLogo != null) {
2111 menuItem.setIcon(galleryLogo);
2116 return super.onCreateOptionsMenu(menu);
2120 public boolean onPrepareOptionsMenu(Menu menu) {
2121 if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
2122 // Compatibility pre-L: launching new activities right above
2123 // lockscreen does not reliably work, only show help if not secure
2124 menu.removeItem(R.id.action_help_and_feedback);
2127 return super.onPrepareOptionsMenu(menu);
2130 protected long getStorageSpaceBytes() {
2131 synchronized (mStorageSpaceLock) {
2132 return mStorageSpaceBytes;
2136 protected interface OnStorageUpdateDoneListener {
2137 public void onStorageUpdateDone(long bytes);
2140 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
2142 * We execute disk operations on a background thread in order to
2143 * free up the UI thread. Synchronizing on the lock below ensures
2144 * that when getStorageSpaceBytes is called, the main thread waits
2145 * until this method has completed.
2147 * However, .execute() does not ensure this execution block will be
2148 * run right away (.execute() schedules this AsyncTask for sometime
2149 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
2150 * tries to execute the task in parellel with other AsyncTasks, but
2151 * there's still no guarantee).
2152 * e.g. don't call this then immediately call getStorageSpaceBytes().
2153 * Instead, pass in an OnStorageUpdateDoneListener.
2155 (new AsyncTask<Void, Void, Long>() {
2157 protected Long doInBackground(Void ... arg) {
2158 synchronized (mStorageSpaceLock) {
2159 mStorageSpaceBytes = Storage.getAvailableSpace();
2160 return mStorageSpaceBytes;
2165 protected void onPostExecute(Long bytes) {
2166 updateStorageHint(bytes);
2167 // This callback returns after I/O to check disk, so we could be
2168 // pausing and shutting down. If so, don't bother invoking.
2169 if (callback != null && !mPaused) {
2170 callback.onStorageUpdateDone(bytes);
2172 Log.v(TAG, "ignoring storage callback after activity pause");
2175 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2178 protected void updateStorageHint(long storageSpace) {
2179 if (!mIsActivityRunning) {
2183 String message = null;
2184 if (storageSpace == Storage.UNAVAILABLE) {
2185 message = getString(R.string.no_storage);
2186 } else if (storageSpace == Storage.PREPARING) {
2187 message = getString(R.string.preparing_sd);
2188 } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2189 message = getString(R.string.access_sd_fail);
2190 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2191 message = getString(R.string.spaceIsLow_content);
2194 if (message != null) {
2195 Log.w(TAG, "Storage warning: " + message);
2196 if (mStorageHint == null) {
2197 mStorageHint = OnScreenHint.makeText(message);
2199 mStorageHint.setText(message);
2201 mStorageHint.show();
2202 UsageStatistics.instance().storageWarning(storageSpace);
2204 // Disable all user interactions,
2205 mCameraAppUI.setDisableAllUserInteractions(true);
2206 } else if (mStorageHint != null) {
2207 mStorageHint.cancel();
2208 mStorageHint = null;
2210 // Re-enable all user interactions.
2211 mCameraAppUI.setDisableAllUserInteractions(false);
2215 protected void setResultEx(int resultCode) {
2216 mResultCodeForTesting = resultCode;
2217 setResult(resultCode);
2220 protected void setResultEx(int resultCode, Intent data) {
2221 mResultCodeForTesting = resultCode;
2222 mResultDataForTesting = data;
2223 setResult(resultCode, data);
2226 public int getResultCode() {
2227 return mResultCodeForTesting;
2230 public Intent getResultData() {
2231 return mResultDataForTesting;
2234 public boolean isSecureCamera() {
2235 return mSecureCamera;
2239 public boolean isPaused() {
2244 public int getPreferredChildModeIndex(int modeIndex) {
2245 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2246 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2247 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule()) {
2248 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2255 public void onModeSelected(int modeIndex) {
2256 if (mCurrentModeIndex == modeIndex) {
2260 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2261 // Record last used camera mode for quick switching
2262 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2263 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2264 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2265 Keys.KEY_CAMERA_MODULE_LAST_USED,
2269 closeModule(mCurrentModule);
2271 // Select the correct module index from the mode switcher index.
2272 modeIndex = getPreferredChildModeIndex(modeIndex);
2273 setModuleFromModeIndex(modeIndex);
2275 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2276 mCameraAppUI.addShutterListener(mCurrentModule);
2277 openModule(mCurrentModule);
2278 // Store the module index so we can use it the next time the Camera
2280 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2281 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2285 * Shows the settings dialog.
2288 public void onSettingsSelected() {
2289 UsageStatistics.instance().controlUsed(
2290 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2291 Intent intent = new Intent(this, CameraSettingsActivity.class);
2292 startActivity(intent);
2296 public void freezeScreenUntilPreviewReady() {
2297 mCameraAppUI.freezeScreenUntilPreviewReady();
2301 public int getModuleId(int modeIndex) {
2302 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2303 if (agent == null) {
2306 return agent.getModuleId();
2310 * Sets the mCurrentModuleIndex, creates a new module instance for the given
2311 * index an sets it as mCurrentModule.
2313 private void setModuleFromModeIndex(int modeIndex) {
2314 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2315 if (agent == null) {
2318 if (!agent.requestAppForCamera()) {
2319 mCameraController.closeCamera(true);
2321 mCurrentModeIndex = agent.getModuleId();
2322 mCurrentModule = (CameraModule) agent.createModule(this);
2326 public SettingsManager getSettingsManager() {
2327 return mSettingsManager;
2331 public ResolutionSetting getResolutionSetting() {
2332 return mResolutionSetting;
2336 public CameraServices getServices() {
2337 return CameraServicesImpl.instance();
2340 public List<String> getSupportedModeNames() {
2341 List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2342 List<String> supported = new ArrayList<String>();
2344 for (Integer modeIndex : indices) {
2345 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2346 if (name != null && !name.equals("")) {
2347 supported.add(name);
2354 public ButtonManager getButtonManager() {
2355 if (mButtonManager == null) {
2356 mButtonManager = new ButtonManager(this);
2358 return mButtonManager;
2362 public SoundPlayer getSoundPlayer() {
2363 return mSoundPlayer;
2367 * Launches an ACTION_EDIT intent for the given local data item. If
2368 * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2369 * the user start either the tiny planet editor or another photo editor.
2371 * @param data The data item to edit.
2373 public void launchEditor(FilmstripItem data) {
2374 Intent intent = new Intent(Intent.ACTION_EDIT)
2375 .setDataAndType(data.getData().getUri(), data.getData().getMimeType())
2376 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2378 launchActivityByIntent(intent);
2379 } catch (ActivityNotFoundException e) {
2380 final String msgEditWith = getResources().getString(R.string.edit_with);
2381 launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2386 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2387 super.onCreateContextMenu(menu, v, menuInfo);
2389 MenuInflater inflater = getMenuInflater();
2390 inflater.inflate(R.menu.filmstrip_context_menu, menu);
2394 public boolean onContextItemSelected(MenuItem item) {
2395 switch (item.getItemId()) {
2396 case R.id.tiny_planet_editor:
2397 mMyFilmstripBottomControlListener.onTinyPlanet();
2399 case R.id.photo_editor:
2400 mMyFilmstripBottomControlListener.onEdit();
2407 * Launch the tiny planet editor.
2409 * @param data The data must be a 360 degree stereographically mapped
2410 * panoramic image. It will not be modified, instead a new item
2411 * with the result will be added to the filmstrip.
2413 public void launchTinyPlanetEditor(FilmstripItem data) {
2414 TinyPlanetFragment fragment = new TinyPlanetFragment();
2415 Bundle bundle = new Bundle();
2416 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString());
2417 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle());
2418 fragment.setArguments(bundle);
2419 fragment.show(getFragmentManager(), "tiny_planet");
2423 * Returns what UI mode (capture mode or filmstrip) we are in.
2424 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2426 private int currentUserInterfaceMode() {
2427 int mode = NavigationChange.Mode.UNKNOWN_MODE;
2428 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2429 mode = NavigationChange.Mode.PHOTO_CAPTURE;
2431 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2432 mode = NavigationChange.Mode.VIDEO_CAPTURE;
2434 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2435 mode = NavigationChange.Mode.LENS_BLUR;
2437 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2438 mode = NavigationChange.Mode.HDR_PLUS;
2440 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2441 mode = NavigationChange.Mode.PHOTO_SPHERE;
2443 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2444 mode = NavigationChange.Mode.PANORAMA;
2446 if (mFilmstripVisible) {
2447 mode = NavigationChange.Mode.FILMSTRIP;
2452 private void openModule(CameraModule module) {
2453 module.init(this, isSecureCamera(), isCaptureIntent());
2454 module.hardResetSettings(mSettingsManager);
2457 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2458 NavigationChange.InteractionCause.BUTTON);
2459 updatePreviewVisibility();
2463 private void closeModule(CameraModule module) {
2465 mCameraAppUI.clearModuleUI();
2468 private void performDeletion() {
2469 if (!mPendingDeletion) {
2472 hideUndoDeletionBar(false);
2473 mDataAdapter.executeDeletion();
2476 public void showUndoDeletionBar() {
2477 if (mPendingDeletion) {
2480 Log.v(TAG, "showing undo bar");
2481 mPendingDeletion = true;
2482 if (mUndoDeletionBar == null) {
2483 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2484 mAboveFilmstripControlLayout, true);
2485 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2486 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2487 button.setOnClickListener(new View.OnClickListener() {
2489 public void onClick(View view) {
2490 mDataAdapter.undoDeletion();
2491 hideUndoDeletionBar(true);
2494 // Setting undo bar clickable to avoid touch events going through
2495 // the bar to the buttons (eg. edit button, etc) underneath the bar.
2496 mUndoDeletionBar.setClickable(true);
2497 // When there is user interaction going on with the undo button, we
2498 // do not want to hide the undo bar.
2499 button.setOnTouchListener(new View.OnTouchListener() {
2501 public boolean onTouch(View v, MotionEvent event) {
2502 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2503 mIsUndoingDeletion = true;
2504 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2505 mIsUndoingDeletion = false;
2511 mUndoDeletionBar.setAlpha(0f);
2512 mUndoDeletionBar.setVisibility(View.VISIBLE);
2513 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2516 private void hideUndoDeletionBar(boolean withAnimation) {
2517 Log.v(TAG, "Hiding undo deletion bar");
2518 mPendingDeletion = false;
2519 if (mUndoDeletionBar != null) {
2520 if (withAnimation) {
2521 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2522 .setListener(new Animator.AnimatorListener() {
2524 public void onAnimationStart(Animator animation) {
2529 public void onAnimationEnd(Animator animation) {
2530 mUndoDeletionBar.setVisibility(View.GONE);
2534 public void onAnimationCancel(Animator animation) {
2539 public void onAnimationRepeat(Animator animation) {
2544 mUndoDeletionBar.setVisibility(View.GONE);
2550 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2553 * @param enable {@code true} to enable swipe.
2555 public void setSwipingEnabled(boolean enable) {
2556 // TODO: Bring back the functionality.
2557 if (isCaptureIntent()) {
2558 // lockPreview(true);
2560 // lockPreview(!enable);
2564 // Accessor methods for getting latency times used in performance testing
2565 public long getFirstPreviewTime() {
2566 if (mCurrentModule instanceof PhotoModule) {
2567 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2568 if (coverHiddenTime != -1) {
2569 return coverHiddenTime - mOnCreateTime;
2575 public long getAutoFocusTime() {
2576 return (mCurrentModule instanceof PhotoModule) ?
2577 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2580 public long getShutterLag() {
2581 return (mCurrentModule instanceof PhotoModule) ?
2582 ((PhotoModule) mCurrentModule).mShutterLag : -1;
2585 public long getShutterToPictureDisplayedTime() {
2586 return (mCurrentModule instanceof PhotoModule) ?
2587 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2590 public long getPictureDisplayedToJpegCallbackTime() {
2591 return (mCurrentModule instanceof PhotoModule) ?
2592 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2595 public long getJpegCallbackFinishTime() {
2596 return (mCurrentModule instanceof PhotoModule) ?
2597 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2600 public long getCaptureStartTime() {
2601 return (mCurrentModule instanceof PhotoModule) ?
2602 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2605 public boolean isRecording() {
2606 return (mCurrentModule instanceof VideoModule) ?
2607 ((VideoModule) mCurrentModule).isRecording() : false;
2610 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2611 return mCameraController;
2614 // For debugging purposes only.
2615 public CameraModule getCurrentModule() {
2616 return mCurrentModule;
2620 public void showTutorial(AbstractTutorialOverlay tutorial) {
2621 mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2625 public void showErrorAndFinish(int messageId) {
2626 CameraUtil.showErrorAndFinish(this, messageId);
2630 private void keepScreenOnForAWhile() {
2631 if (mKeepScreenOn) {
2634 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2635 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2636 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2639 private void resetScreenOn() {
2640 mKeepScreenOn = false;
2641 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2642 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2646 * @return {@code true} if the Gallery is launched successfully.
2648 private boolean startGallery() {
2649 if (mGalleryIntent == null) {
2653 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2654 NavigationChange.InteractionCause.BUTTON);
2655 Intent startGalleryIntent = new Intent(mGalleryIntent);
2656 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
2657 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex);
2658 if (currentFilmstripItem != null) {
2659 GalleryHelper.setContentUri(startGalleryIntent,
2660 currentFilmstripItem.getData().getUri());
2662 launchActivityByIntent(startGalleryIntent);
2663 } catch (ActivityNotFoundException e) {
2664 Log.w(TAG, "Failed to launch gallery activity, closing");
2669 private void setNfcBeamPushUriFromData(FilmstripItem data) {
2670 final Uri uri = data.getData().getUri();
2671 if (uri != Uri.EMPTY) {
2672 mNfcPushUris[0] = uri;
2674 mNfcPushUris[0] = null;
2679 * Updates the visibility of the filmstrip bottom controls and action bar.
2681 private void updateUiByData(final int index) {
2682 final FilmstripItem currentData = mDataAdapter.getItemAt(index);
2683 if (currentData == null) {
2684 Log.w(TAG, "Current data ID not found.");
2685 hideSessionProgress();
2688 updateActionBarMenu(currentData);
2690 /* Bottom controls. */
2691 updateBottomControlsByData(currentData);
2693 if (isSecureCamera()) {
2694 // We cannot show buttons in secure camera since go to other
2695 // activities might create a security hole.
2696 mCameraAppUI.getFilmstripBottomControls().hideControls();
2700 setNfcBeamPushUriFromData(currentData);
2702 if (!mDataAdapter.isMetadataUpdatedAt(index)) {
2703 mDataAdapter.updateMetadataAt(index);
2708 * Updates the bottom controls based on the data.
2710 private void updateBottomControlsByData(final FilmstripItem currentData) {
2712 final CameraAppUI.BottomPanel filmstripBottomPanel =
2713 mCameraAppUI.getFilmstripBottomControls();
2714 filmstripBottomPanel.showControls();
2715 filmstripBottomPanel.setEditButtonVisibility(
2716 currentData.getAttributes().canEdit());
2717 filmstripBottomPanel.setShareButtonVisibility(
2718 currentData.getAttributes().canShare());
2719 filmstripBottomPanel.setDeleteButtonVisibility(
2720 currentData.getAttributes().canDelete());
2724 Uri contentUri = currentData.getData().getUri();
2725 CaptureSessionManager sessionManager = getServices()
2726 .getCaptureSessionManager();
2728 if (sessionManager.hasErrorMessage(contentUri)) {
2729 showProcessError(sessionManager.getErrorMesage(contentUri));
2731 filmstripBottomPanel.hideProgressError();
2732 CaptureSession session = sessionManager.getSession(contentUri);
2734 if (session != null) {
2735 int sessionProgress = session.getProgress();
2737 if (sessionProgress < 0) {
2738 hideSessionProgress();
2740 CharSequence progressMessage = session.getProgressMessage();
2741 showSessionProgress(progressMessage);
2742 updateSessionProgress(sessionProgress);
2745 hideSessionProgress();
2751 // We need to add this to a separate DB.
2752 final int viewButtonVisibility;
2753 if (currentData.getMetadata().isUsePanoramaViewer()) {
2754 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2755 } else if (currentData.getMetadata().isHasRgbzData()) {
2756 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2758 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2761 filmstripBottomPanel.setTinyPlanetEnabled(
2762 currentData.getMetadata().isPanorama360());
2763 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2766 private static class PeekAnimationHandler extends Handler {
2767 private class DataAndCallback {
2768 FilmstripItem mData;
2769 com.android.camera.util.Callback<Bitmap> mCallback;
2771 public DataAndCallback(FilmstripItem data, com.android.camera.util.Callback<Bitmap>
2774 mCallback = callback;
2778 private final Handler mMainHandler;
2779 private final FrameLayout mAboveFilmstripControlLayout;
2781 public PeekAnimationHandler(Looper looper, Handler mainHandler,
2782 FrameLayout aboveFilmstripControlLayout) {
2784 mMainHandler = mainHandler;
2785 mAboveFilmstripControlLayout = aboveFilmstripControlLayout;
2789 * Starts the animation decoding job and posts a {@code Runnable} back
2790 * when when the decoding is done.
2792 * @param data The data item to decode the thumbnail for.
2793 * @param callback {@link com.android.camera.util.Callback} after the
2796 public void startDecodingJob(final FilmstripItem data,
2797 final com.android.camera.util.Callback<Bitmap> callback) {
2798 PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2799 new DataAndCallback(data, callback)).sendToTarget();
2803 public void handleMessage(Message msg) {
2804 final FilmstripItem data = ((DataAndCallback) msg.obj).mData;
2805 final com.android.camera.util.Callback<Bitmap> callback =
2806 ((DataAndCallback) msg.obj).mCallback;
2807 if (data == null || callback == null) {
2811 final Optional<Bitmap> bitmap = data.generateThumbnail(
2812 mAboveFilmstripControlLayout.getWidth(),
2813 mAboveFilmstripControlLayout.getMeasuredHeight());
2815 if (bitmap.isPresent()) {
2816 mMainHandler.post(new Runnable() {
2819 callback.onCallback(bitmap.get());
2826 private void showDetailsDialog(int index) {
2827 final FilmstripItem data = mDataAdapter.getItemAt(index);
2831 Optional<MediaDetails> details = data.getMediaDetails();
2832 if (!details.isPresent()) {
2835 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get());
2836 detailDialog.show();
2837 UsageStatistics.instance().mediaInteraction(
2838 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS,
2839 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index));
2843 * Show or hide action bar items depending on current data type.
2845 private void updateActionBarMenu(FilmstripItem data) {
2846 if (mActionBarMenu == null) {
2850 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2851 if (detailsMenuItem == null) {
2855 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo();
2856 detailsMenuItem.setVisible(showDetails);