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.filmstrip.FilmstripContentPanel;
105 import com.android.camera.filmstrip.FilmstripController;
106 import com.android.camera.module.ModuleController;
107 import com.android.camera.module.ModulesInfo;
108 import com.android.camera.one.OneCameraException;
109 import com.android.camera.one.OneCameraManager;
110 import com.android.camera.one.config.OneCameraFeatureConfig;
111 import com.android.camera.one.config.OneCameraFeatureConfigCreator;
112 import com.android.camera.session.CaptureSession;
113 import com.android.camera.session.CaptureSessionManager;
114 import com.android.camera.session.CaptureSessionManager.SessionListener;
115 import com.android.camera.settings.AppUpgrader;
116 import com.android.camera.settings.CameraSettingsActivity;
117 import com.android.camera.settings.Keys;
118 import com.android.camera.settings.ResolutionSetting;
119 import com.android.camera.settings.ResolutionUtil;
120 import com.android.camera.settings.SettingsManager;
121 import com.android.camera.stats.UsageStatistics;
122 import com.android.camera.stats.profiler.Profile;
123 import com.android.camera.stats.profiler.Profiler;
124 import com.android.camera.stats.profiler.Profilers;
125 import com.android.camera.tinyplanet.TinyPlanetFragment;
126 import com.android.camera.ui.AbstractTutorialOverlay;
127 import com.android.camera.ui.DetailsDialog;
128 import com.android.camera.ui.MainActivityLayout;
129 import com.android.camera.ui.ModeListView;
130 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
131 import com.android.camera.ui.PreviewStatusListener;
132 import com.android.camera.util.ApiHelper;
133 import com.android.camera.util.Callback;
134 import com.android.camera.util.CameraUtil;
135 import com.android.camera.util.GalleryHelper;
136 import com.android.camera.util.GcamHelper;
137 import com.android.camera.util.GoogleHelpHelper;
138 import com.android.camera.util.IntentHelper;
139 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
140 import com.android.camera.util.QuickActivity;
141 import com.android.camera.util.ReleaseHelper;
142 import com.android.camera.widget.FilmstripView;
143 import com.android.camera.widget.Preloader;
144 import com.android.camera2.R;
145 import com.android.ex.camera2.portability.CameraAgent;
146 import com.android.ex.camera2.portability.CameraAgentFactory;
147 import com.android.ex.camera2.portability.CameraExceptionHandler;
148 import com.android.ex.camera2.portability.CameraSettings;
149 import com.bumptech.glide.Glide;
150 import com.bumptech.glide.GlideBuilder;
151 import com.bumptech.glide.MemoryCategory;
152 import com.bumptech.glide.load.DecodeFormat;
153 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
154 import com.bumptech.glide.load.engine.prefill.PreFillType;
155 import com.google.common.base.Optional;
156 import com.google.common.logging.eventprotos;
157 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
158 import com.google.common.logging.eventprotos.MediaInteraction;
159 import com.google.common.logging.eventprotos.NavigationChange;
162 import java.lang.ref.WeakReference;
163 import java.util.ArrayList;
164 import java.util.HashMap;
165 import java.util.List;
167 public class CameraActivity extends QuickActivity
168 implements AppController, CameraAgent.CameraOpenCallback,
169 ShareActionProvider.OnShareTargetSelectedListener {
171 private static final Log.Tag TAG = new Log.Tag("CameraActivity");
173 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
174 "android.media.action.STILL_IMAGE_CAMERA_SECURE";
175 public static final String ACTION_IMAGE_CAPTURE_SECURE =
176 "android.media.action.IMAGE_CAPTURE_SECURE";
178 // The intent extra for camera from secure lock screen. True if the gallery
179 // should only show newly captured pictures. sSecureAlbumId does not
180 // increment. This is used when switching between camera, camcorder, and
181 // panorama. If the extra is not set, it is in the normal camera mode.
182 public static final String SECURE_CAMERA_EXTRA = "secure_camera";
184 public static final String MODULE_SCOPE_PREFIX = "_preferences_module_";
185 public static final String CAMERA_SCOPE_PREFIX = "_preferences_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 OneCameraManager mCameraManager;
215 private SettingsManager mSettingsManager;
216 private ResolutionSetting mResolutionSetting;
217 private ModeListView mModeListView;
218 private boolean mModeListVisible = false;
219 private int mCurrentModeIndex;
220 private CameraModule mCurrentModule;
221 private ModuleManagerImpl mModuleManager;
222 private FrameLayout mAboveFilmstripControlLayout;
223 private FilmstripController mFilmstripController;
224 private boolean mFilmstripVisible;
225 /** Whether the filmstrip fully covers the preview. */
226 private boolean mFilmstripCoversPreview = false;
227 private int mResultCodeForTesting;
228 private Intent mResultDataForTesting;
229 private OnScreenHint mStorageHint;
230 private final Object mStorageSpaceLock = new Object();
231 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
232 private boolean mAutoRotateScreen;
233 private boolean mSecureCamera;
234 private OrientationManagerImpl mOrientationManager;
235 private LocationManager mLocationManager;
236 private ButtonManager mButtonManager;
237 private Handler mMainHandler;
238 private PanoramaViewHelper mPanoramaViewHelper;
239 private ActionBar mActionBar;
240 private ViewGroup mUndoDeletionBar;
241 private boolean mIsUndoingDeletion = false;
242 private boolean mIsActivityRunning = false;
244 private final Uri[] mNfcPushUris = new Uri[1];
246 private FilmstripContentObserver mLocalImagesObserver;
247 private FilmstripContentObserver mLocalVideosObserver;
249 private boolean mPendingDeletion = false;
251 private CameraController mCameraController;
252 private boolean mPaused;
253 private CameraAppUI mCameraAppUI;
255 private Intent mGalleryIntent;
256 private long mOnCreateTime;
258 private Menu mActionBarMenu;
259 private Preloader<Integer, AsyncTask> mPreloader;
261 /** Can be used to play custom sounds. */
262 private SoundPlayer mSoundPlayer;
264 /** Holds configuration for various OneCamera features. */
265 private OneCameraFeatureConfig mFeatureConfig;
267 private static final int LIGHTS_OUT_DELAY_MS = 4000;
268 private final int BASE_SYS_UI_VISIBILITY =
269 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
270 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
271 private final Runnable mLightsOutRunnable = new Runnable() {
274 getWindow().getDecorView().setSystemUiVisibility(
275 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE);
278 private MemoryManager mMemoryManager;
279 private MotionManager mMotionManager;
280 private final Profiler mProfiler = Profilers.instance().guard();
282 /** First run dialog */
283 private FirstRunDialog mFirstRunDialog;
286 public CameraAppUI getCameraAppUI() {
291 public ModuleManager getModuleManager() {
292 return mModuleManager;
296 * Close activity when secure app passes lock screen or screen turns
299 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
301 public void onReceive(Context context, Intent intent) {
307 * Whether the screen is kept turned on.
309 private boolean mKeepScreenOn;
310 private int mLastLayoutOrientation;
311 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
312 new CameraAppUI.BottomPanel.Listener() {
315 * If the current photo is a photo sphere, this will launch the
316 * Photo Sphere panorama viewer.
319 public void onExternalViewer() {
320 if (mPanoramaViewHelper == null) {
323 final FilmstripItem data = getCurrentLocalData();
325 Log.w(TAG, "Cannot open null data.");
328 final Uri contentUri = data.getData().getUri();
329 if (contentUri == Uri.EMPTY) {
330 Log.w(TAG, "Cannot open empty URL.");
334 if (data.getMetadata().isUsePanoramaViewer()) {
335 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
336 } else if (data.getMetadata().isHasRgbzData()) {
337 mPanoramaViewHelper.showRgbz(contentUri);
338 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
339 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
340 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
341 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false);
342 mCameraAppUI.clearClingForViewer(
343 CameraAppUI.BottomPanel.VIEWER_REFOCUS);
349 public void onEdit() {
350 FilmstripItem data = getCurrentLocalData();
352 Log.w(TAG, "Cannot edit null data.");
355 final int currentDataId = getCurrentDataId();
356 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
358 MediaInteraction.InteractionType.EDIT,
359 NavigationChange.InteractionCause.BUTTON,
360 fileAgeFromAdapterAtIndex(currentDataId));
365 public void onTinyPlanet() {
366 FilmstripItem data = getCurrentLocalData();
368 Log.w(TAG, "Cannot edit tiny planet on null data.");
371 launchTinyPlanetEditor(data);
375 public void onDelete() {
376 final int currentDataId = getCurrentDataId();
377 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
379 MediaInteraction.InteractionType.DELETE,
380 NavigationChange.InteractionCause.BUTTON,
381 fileAgeFromAdapterAtIndex(currentDataId));
382 removeItemAt(currentDataId);
386 public void onShare() {
387 final FilmstripItem data = getCurrentLocalData();
389 Log.w(TAG, "Cannot share null data.");
393 final int currentDataId = getCurrentDataId();
394 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
396 MediaInteraction.InteractionType.SHARE,
397 NavigationChange.InteractionCause.BUTTON,
398 fileAgeFromAdapterAtIndex(currentDataId));
399 // If applicable, show release information before this item
401 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
402 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
403 new Callback<Void>() {
405 public void onCallback(Void result) {
414 private void share(FilmstripItem data) {
415 Intent shareIntent = getShareIntentByData(data);
416 if (shareIntent != null) {
418 launchActivityByIntent(shareIntent);
419 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
420 } catch (ActivityNotFoundException ex) {
426 private int getCurrentDataId() {
427 return mFilmstripController.getCurrentAdapterIndex();
430 private FilmstripItem getCurrentLocalData() {
431 return mDataAdapter.getItemAt(getCurrentDataId());
435 * Sets up the share intent and NFC properly according to the
438 * @param item The data to be shared.
440 private Intent getShareIntentByData(final FilmstripItem item) {
441 Intent intent = null;
442 final Uri contentUri = item.getData().getUri();
443 final String msgShareTo = getResources().getString(R.string.share_to);
445 if (item.getMetadata().isPanorama360() &&
446 item.getData().getUri() != Uri.EMPTY) {
447 intent = new Intent(Intent.ACTION_SEND);
448 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE);
449 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
450 } else if (item.getAttributes().canShare()) {
451 final String mimeType = item.getData().getMimeType();
452 intent = getShareIntentFromType(mimeType);
453 if (intent != null) {
454 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
455 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
457 intent = Intent.createChooser(intent, msgShareTo);
463 * Get the share intent according to the mimeType
465 * @param mimeType The mimeType of current data.
466 * @return the video/image's ShareIntent or null if mimeType is
469 private Intent getShareIntentFromType(String mimeType) {
470 // Lazily create the intent object.
471 Intent intent = new Intent(Intent.ACTION_SEND);
472 if (mimeType.startsWith("video/")) {
473 intent.setType("video/*");
475 if (mimeType.startsWith("image/")) {
476 intent.setType("image/*");
478 Log.w(TAG, "unsupported mimeType " + mimeType);
485 public void onProgressErrorClicked() {
486 FilmstripItem data = getCurrentLocalData();
487 getServices().getCaptureSessionManager().removeErrorMessage(
488 data.getData().getUri());
489 updateBottomControlsByData(data);
494 public void onCameraOpened(CameraAgent.CameraProxy camera) {
495 Log.v(TAG, "onCameraOpened");
497 // We've paused, but just asynchronously opened the camera. Close it
498 // because we should be releasing the camera when paused to allow
499 // other apps to access it.
500 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera");
501 mCameraController.closeCamera(false);
505 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
506 // We shouldn't be here. Just close the camera and leave.
507 mCameraController.closeCamera(false);
508 throw new IllegalStateException("Camera opened but the module shouldn't be " +
511 if (mCurrentModule != null) {
512 resetExposureCompensationToDefault(camera);
513 mCurrentModule.onCameraAvailable(camera);
515 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
517 Log.v(TAG, "invoking onChangeCamera");
518 mCameraAppUI.onChangeCamera();
521 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) {
522 // Reset the exposure compensation before handing the camera to module.
523 CameraSettings cameraSettings = camera.getSettings();
524 cameraSettings.setExposureCompensationIndex(0);
525 camera.applySettings(cameraSettings);
529 public void onCameraDisabled(int cameraId) {
530 UsageStatistics.instance().cameraFailure(
531 eventprotos.CameraFailure.FailureReason.SECURITY, null,
532 UsageStatistics.NONE, UsageStatistics.NONE);
533 Log.w(TAG, "Camera disabled: " + cameraId);
534 CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
538 public void onDeviceOpenFailure(int cameraId, String info) {
539 UsageStatistics.instance().cameraFailure(
540 eventprotos.CameraFailure.FailureReason.OPEN_FAILURE, info,
541 UsageStatistics.NONE, UsageStatistics.NONE);
542 Log.w(TAG, "Camera open failure: " + info);
543 CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
547 public void onDeviceOpenedAlready(int cameraId, String info) {
548 Log.w(TAG, "Camera open already: " + cameraId + "," + info);
549 CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
553 public void onReconnectionFailure(CameraAgent mgr, String info) {
554 UsageStatistics.instance().cameraFailure(
555 eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE, null,
556 UsageStatistics.NONE, UsageStatistics.NONE);
557 Log.w(TAG, "Camera reconnection failure:" + info);
558 CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
561 private static class MainHandler extends Handler {
562 final WeakReference<CameraActivity> mActivity;
564 public MainHandler(CameraActivity activity, Looper looper) {
566 mActivity = new WeakReference<CameraActivity>(activity);
570 public void handleMessage(Message msg) {
571 CameraActivity activity = mActivity.get();
572 if (activity == null) {
577 case MSG_CLEAR_SCREEN_ON_FLAG: {
578 if (!activity.mPaused) {
579 activity.getWindow().clearFlags(
580 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
588 private String fileNameFromAdapterAtIndex(int index) {
589 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
590 if (filmstripItem == null) {
594 File localFile = new File(filmstripItem.getData().getFilePath());
595 return localFile.getName();
598 private float fileAgeFromAdapterAtIndex(int index) {
599 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
600 if (filmstripItem == null) {
604 File localFile = new File(filmstripItem.getData().getFilePath());
605 return 0.001f * (System.currentTimeMillis() - localFile.lastModified());
608 private final FilmstripContentPanel.Listener mFilmstripListener =
609 new FilmstripContentPanel.Listener() {
612 public void onSwipeOut() {
616 public void onSwipeOutBegin() {
618 mCameraAppUI.hideBottomControls();
619 mFilmstripCoversPreview = false;
620 updatePreviewVisibility();
624 public void onFilmstripHidden() {
625 mFilmstripVisible = false;
626 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
627 NavigationChange.InteractionCause.SWIPE_RIGHT);
628 CameraActivity.this.setFilmstripUiVisibility(false);
629 // When the user hide the filmstrip (either swipe out or
630 // tap on back key) we move to the first item so next time
631 // when the user swipe in the filmstrip, the most recent
633 mFilmstripController.goToFirstItem();
637 public void onFilmstripShown() {
638 mFilmstripVisible = true;
639 mCameraAppUI.hideCaptureIndicator();
640 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
641 NavigationChange.InteractionCause.SWIPE_LEFT);
642 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
646 public void onFocusedDataLongPressed(int adapterIndex) {
651 public void onFocusedDataPromoted(int adapterIndex) {
652 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
654 MediaInteraction.InteractionType.DELETE,
655 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex(
657 removeItemAt(adapterIndex);
661 public void onFocusedDataDemoted(int adapterIndex) {
662 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
664 MediaInteraction.InteractionType.DELETE,
665 NavigationChange.InteractionCause.SWIPE_DOWN,
666 fileAgeFromAdapterAtIndex(adapterIndex));
667 removeItemAt(adapterIndex);
671 public void onEnterFullScreenUiShown(int adapterIndex) {
672 if (mFilmstripVisible) {
673 CameraActivity.this.setFilmstripUiVisibility(true);
678 public void onLeaveFullScreenUiShown(int adapterIndex) {
683 public void onEnterFullScreenUiHidden(int adapterIndex) {
684 if (mFilmstripVisible) {
685 CameraActivity.this.setFilmstripUiVisibility(false);
690 public void onLeaveFullScreenUiHidden(int adapterIndex) {
695 public void onEnterFilmstrip(int adapterIndex) {
696 if (mFilmstripVisible) {
697 CameraActivity.this.setFilmstripUiVisibility(true);
702 public void onLeaveFilmstrip(int adapterIndex) {
707 public void onDataReloaded() {
708 if (!mFilmstripVisible) {
711 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
715 public void onDataUpdated(int adapterIndex) {
716 if (!mFilmstripVisible) {
719 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
723 public void onEnterZoomView(int adapterIndex) {
724 if (mFilmstripVisible) {
725 CameraActivity.this.setFilmstripUiVisibility(false);
730 public void onZoomAtIndexChanged(int adapterIndex, float zoom) {
731 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex);
732 long ageMillis = System.currentTimeMillis()
733 - filmstripItem.getData().getLastModifiedDate().getTime();
735 // Do not log if items is to old or does not have a path (which is
736 // being used as a key).
737 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) ||
738 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) {
741 File localFile = new File(filmstripItem.getData().getFilePath());
742 UsageStatistics.instance().mediaView(localFile.getName(),
743 filmstripItem.getData().getLastModifiedDate().getTime(), zoom);
747 public void onDataFocusChanged(final int prevIndex, final int newIndex) {
748 if (!mFilmstripVisible) {
751 // TODO: This callback is UI event callback, should always
752 // happen on UI thread. Find the reason for this
753 // runOnUiThread() and fix it.
754 runOnUiThread(new Runnable() {
757 updateUiByData(newIndex);
763 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
764 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
768 private final FilmstripItemListener mFilmstripItemListener =
769 new FilmstripItemListener() {
771 public void onMetadataUpdated(List<Integer> indexes) {
773 // Callback after the activity is paused.
776 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
777 for (Integer index : indexes) {
778 if (index == currentIndex) {
779 updateBottomControlsByData(mDataAdapter.getItemAt(index));
780 // Currently we have only 1 data can be matched.
781 // No need to look for more, break.
788 public void gotoGallery() {
789 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
790 NavigationChange.InteractionCause.BUTTON);
792 mFilmstripController.goToNextItem();
796 * If 'visible' is false, this hides the action bar. Also maintains
797 * lights-out at all times.
799 * @param visible is false, this hides the action bar and filmstrip bottom
802 private void setFilmstripUiVisibility(boolean visible) {
803 mLightsOutRunnable.run();
804 mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
805 if (visible != mActionBar.isShowing()) {
808 mCameraAppUI.showBottomControls();
811 mCameraAppUI.hideBottomControls();
814 mFilmstripCoversPreview = visible;
815 updatePreviewVisibility();
818 private void hideSessionProgress() {
819 mCameraAppUI.getFilmstripBottomControls().hideProgress();
822 private void showSessionProgress(CharSequence message) {
823 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
824 controls.setProgressText(message);
825 controls.hideControls();
826 controls.hideProgressError();
827 controls.showProgress();
830 private void showProcessError(CharSequence message) {
831 mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
834 private void updateSessionProgress(int progress) {
835 mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
838 private void updateSessionProgressText(CharSequence message) {
839 mCameraAppUI.getFilmstripBottomControls().setProgressText(message);
842 private void setupNfcBeamPush() {
843 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
844 if (adapter == null) {
848 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
850 adapter.setNdefPushMessage(null, CameraActivity.this);
854 adapter.setBeamPushUris(null, CameraActivity.this);
855 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
857 public Uri[] createBeamUris(NfcEvent event) {
860 }, CameraActivity.this);
864 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
865 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
866 if (currentIndex < 0) {
869 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex),
870 MediaInteraction.InteractionType.SHARE,
871 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex));
872 // TODO add intent.getComponent().getPackageName()
876 // Note: All callbacks come back on the main thread.
877 private final SessionListener mSessionListener =
878 new SessionListener() {
880 public void onSessionQueued(final Uri uri) {
881 Log.v(TAG, "onSessionQueued: " + uri);
882 if (!Storage.isSessionUri(uri)) {
885 SessionItem newData = new SessionItem(getApplicationContext(), uri);
886 mDataAdapter.addOrUpdate(newData);
890 public void onSessionUpdated(Uri uri) {
891 Log.v(TAG, "onSessionUpdated: " + uri);
892 mDataAdapter.refresh(uri);
896 public void onSessionDone(final Uri sessionUri) {
897 Log.v(TAG, "onSessionDone:" + sessionUri);
898 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
899 if (contentUri == null) {
900 mDataAdapter.refresh(sessionUri);
903 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri);
905 // This can be null if e.g. a session is canceled (e.g.
906 // through discard panorama). It might be worth adding
907 // onSessionCanceled or the like this interface.
908 if (newData == null) {
909 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri);
913 // Make the PhotoItem aware of the session placeholder, to
914 // allow it to make a smooth transition to its content.
915 newData.setSessionPlaceholderBitmap(
916 Storage.getPlaceholderForSession(sessionUri));
918 final int pos = mDataAdapter.findByContentUri(sessionUri);
920 // We do not have a placeholder for this image, perhaps
921 // due to the activity crashing or being killed.
922 mDataAdapter.addOrUpdate(newData);
924 mDataAdapter.updateItemAt(pos, newData);
929 public void onSessionProgress(final Uri uri, final int progress) {
931 // Do nothing, there is no task for this URI.
934 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
935 if (currentIndex == -1) {
939 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
940 updateSessionProgress(progress);
945 public void onSessionProgressText(final Uri uri, final CharSequence message) {
946 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
947 if (currentIndex == -1) {
951 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
952 updateSessionProgressText(message);
957 public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
958 // Don't show capture indicator in Photo Sphere.
959 final int photosphereModuleId = getApplicationContext().getResources()
961 R.integer.camera_mode_photosphere);
962 if (mCurrentModeIndex == photosphereModuleId) {
965 indicateCapture(indicator, rotationDegrees);
969 public void onSessionFailed(Uri uri, CharSequence reason) {
970 Log.v(TAG, "onSessionFailed:" + uri);
972 int failedIndex = mDataAdapter.findByContentUri(uri);
973 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
975 if (currentIndex == failedIndex) {
976 updateSessionProgress(0);
977 showProcessError(reason);
979 if (reason.equals("content")) {
980 UsageStatistics.instance().storageWarning(Storage.ACCESS_FAILURE);
981 CameraUtil.showError(CameraActivity.this, R.string.media_storage_failure,
982 R.string.feedback_description_save_photo, false);
986 mDataAdapter.refresh(uri);
990 public void onSessionThumbnailUpdate(Bitmap bitmap) {
994 public void onSessionPictureDataUpdate(byte[] pictureData, int orientation) {
999 public Context getAndroidContext() {
1004 public OneCameraFeatureConfig getCameraFeatureConfig() {
1005 return mFeatureConfig;
1009 public Dialog createDialog() {
1010 return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
1014 public void launchActivityByIntent(Intent intent) {
1015 // Starting from L, we prefer not to start edit activity within camera's task.
1016 mResetToPreviewOnResume = false;
1017 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
1019 startActivity(intent);
1023 public int getCurrentModuleIndex() {
1024 return mCurrentModeIndex;
1028 public int getCurrentCameraId() {
1029 return mCameraController.getCurrentCameraId();
1033 public String getModuleScope() {
1034 ModuleAgent agent = mModuleManager.getModuleAgent(mCurrentModeIndex);
1035 return MODULE_SCOPE_PREFIX + agent.getScopeNamespace();
1039 public String getCameraScope() {
1040 int currentCameraId = getCurrentCameraId();
1041 if (currentCameraId < 0) {
1042 // if an unopen camera i.e. negative ID is returned, which we've observed in
1043 // some automated scenarios, just return it as a valid separate scope
1044 // this could cause user issues, so log a stack trace noting the call path
1045 // which resulted in this scenario.
1046 Log.w(TAG, "getting camera scope with no open camera, using id: " + currentCameraId);
1048 return CAMERA_SCOPE_PREFIX + Integer.toString(currentCameraId);
1052 public ModuleController getCurrentModuleController() {
1053 return mCurrentModule;
1057 public int getQuickSwitchToModuleId(int currentModuleIndex) {
1058 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
1063 public SurfaceTexture getPreviewBuffer() {
1064 // TODO: implement this
1069 public void onPreviewReadyToStart() {
1070 mCameraAppUI.onPreviewReadyToStart();
1074 public void onPreviewStarted() {
1075 mCameraAppUI.onPreviewStarted();
1079 public void addPreviewAreaSizeChangedListener(
1080 PreviewStatusListener.PreviewAreaChangedListener listener) {
1081 mCameraAppUI.addPreviewAreaChangedListener(listener);
1085 public void removePreviewAreaSizeChangedListener(
1086 PreviewStatusListener.PreviewAreaChangedListener listener) {
1087 mCameraAppUI.removePreviewAreaChangedListener(listener);
1091 public void setupOneShotPreviewListener() {
1092 mCameraController.setOneShotPreviewCallback(mMainHandler,
1093 new CameraAgent.CameraPreviewDataCallback() {
1095 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) {
1096 mCurrentModule.onPreviewInitialDataReceived();
1097 mCameraAppUI.onNewPreviewFrame();
1104 public void updatePreviewAspectRatio(float aspectRatio) {
1105 mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
1109 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
1110 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio);
1114 public RectF getFullscreenRect() {
1115 return mCameraAppUI.getFullscreenRect();
1119 public void updatePreviewTransform(Matrix matrix) {
1120 mCameraAppUI.updatePreviewTransform(matrix);
1124 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1125 mCameraAppUI.setPreviewStatusListener(previewStatusListener);
1129 public FrameLayout getModuleLayoutRoot() {
1130 return mCameraAppUI.getModuleRootView();
1134 public void setShutterEventsListener(ShutterEventsListener listener) {
1135 // TODO: implement this
1139 public void setShutterEnabled(boolean enabled) {
1140 mCameraAppUI.setShutterButtonEnabled(enabled);
1144 public boolean isShutterEnabled() {
1145 return mCameraAppUI.isShutterButtonEnabled();
1149 public void startFlashAnimation(boolean shortFlash) {
1150 mCameraAppUI.startFlashAnimation(shortFlash);
1154 public void startPreCaptureAnimation() {
1155 // TODO: implement this
1159 public void cancelPreCaptureAnimation() {
1160 // TODO: implement this
1164 public void startPostCaptureAnimation() {
1165 // TODO: implement this
1169 public void startPostCaptureAnimation(Bitmap thumbnail) {
1170 // TODO: implement this
1174 public void cancelPostCaptureAnimation() {
1175 // TODO: implement this
1179 public OrientationManager getOrientationManager() {
1180 return mOrientationManager;
1184 public LocationManager getLocationManager() {
1185 return mLocationManager;
1189 public void lockOrientation() {
1190 if (mOrientationManager != null) {
1191 mOrientationManager.lockOrientation();
1196 public void unlockOrientation() {
1197 if (mOrientationManager != null) {
1198 mOrientationManager.unlockOrientation();
1203 * If not in filmstrip, this shows the capture indicator.
1205 private void indicateCapture(final Bitmap indicator, final int rotationDegrees) {
1206 if (mFilmstripVisible) {
1210 // Don't show capture indicator in Photo Sphere.
1211 // TODO: Don't reach into resources to figure out the current mode.
1212 final int photosphereModuleId = getApplicationContext().getResources().getInteger(
1213 R.integer.camera_mode_photosphere);
1214 if (mCurrentModeIndex == photosphereModuleId) {
1218 mMainHandler.post(new Runnable() {
1221 mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule
1222 .getPeekAccessibilityString());
1223 mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees);
1229 public void notifyNewMedia(Uri uri) {
1230 // TODO: This method is running on the main thread. Also we should get
1231 // rid of that AsyncTask.
1233 updateStorageSpaceAndHint(null);
1234 ContentResolver cr = getContentResolver();
1235 String mimeType = cr.getType(uri);
1236 FilmstripItem newData = null;
1237 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
1238 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1239 newData = mVideoItemFactory.queryContentUri(uri);
1240 if (newData == null) {
1241 Log.e(TAG, "Can't find video data in content resolver:" + uri);
1244 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
1245 CameraUtil.broadcastNewPicture(mAppContext, uri);
1246 newData = mPhotoItemFactory.queryContentUri(uri);
1247 if (newData == null) {
1248 Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1252 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1256 // We are preloading the metadata for new video since we need the
1257 // rotation info for the thumbnail.
1258 new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
1260 protected FilmstripItem doInBackground(FilmstripItem... params) {
1261 FilmstripItem data = params[0];
1262 MetadataLoader.loadMetadata(getAndroidContext(), data);
1267 protected void onPostExecute(final FilmstripItem data) {
1268 // TODO: Figure out why sometimes the data is aleady there.
1269 mDataAdapter.addOrUpdate(data);
1271 // Legacy modules don't use CaptureSession, so we show the capture indicator when
1272 // the item was safed.
1273 if (mCurrentModule instanceof PhotoModule ||
1274 mCurrentModule instanceof VideoModule) {
1275 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1278 final Optional<Bitmap> bitmap = data.generateThumbnail(
1279 mAboveFilmstripControlLayout.getWidth(),
1280 mAboveFilmstripControlLayout.getMeasuredHeight());
1281 if (bitmap.isPresent()) {
1282 indicateCapture(bitmap.get(), 0);
1288 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
1292 public void enableKeepScreenOn(boolean enabled) {
1297 mKeepScreenOn = enabled;
1298 if (mKeepScreenOn) {
1299 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1300 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1302 keepScreenOnForAWhile();
1307 public CameraProvider getCameraProvider() {
1308 return mCameraController;
1312 public OneCameraManager getCameraManager() {
1313 return mCameraManager;
1316 private void removeItemAt(int index) {
1317 mDataAdapter.removeAt(index);
1318 if (mDataAdapter.getTotalNumber() > 1) {
1319 showUndoDeletionBar();
1321 // If camera preview is the only view left in filmstrip,
1322 // no need to show undo bar.
1323 mPendingDeletion = true;
1325 if (mFilmstripVisible) {
1326 mCameraAppUI.getFilmstripContentPanel().animateHide();
1332 public boolean onOptionsItemSelected(MenuItem item) {
1333 // Handle presses on the action bar items
1334 switch (item.getItemId()) {
1335 case android.R.id.home:
1338 case R.id.action_details:
1339 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex());
1341 case R.id.action_help_and_feedback:
1342 mResetToPreviewOnResume = false;
1343 new GoogleHelpHelper(this).launchGoogleHelp();
1346 return super.onOptionsItemSelected(item);
1350 private boolean isCaptureIntent() {
1351 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1352 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1353 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1361 * Note: Make sure this callback is unregistered properly when the activity
1362 * is destroyed since we're otherwise leaking the Activity reference.
1364 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
1365 = new CameraExceptionHandler.CameraExceptionCallback() {
1367 public void onCameraError(int errorCode) {
1368 // Not a fatal error. only do Log.e().
1369 Log.e(TAG, "Camera error callback. error=" + errorCode);
1372 public void onCameraException(
1373 RuntimeException ex, String commandHistory, int action, int state) {
1374 Log.e(TAG, "Camera Exception", ex);
1375 UsageStatistics.instance().cameraFailure(
1376 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
1377 commandHistory, action, state);
1381 public void onDispatchThreadException(RuntimeException ex) {
1382 Log.e(TAG, "DispatchThread Exception", ex);
1383 UsageStatistics.instance().cameraFailure(
1384 eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
1385 null, UsageStatistics.NONE, UsageStatistics.NONE);
1388 private void onFatalError() {
1389 if (mCameraFatalError) {
1392 mCameraFatalError = true;
1394 // If the activity receives exception during onPause, just exit the app.
1395 if (mPaused && !isFinishing()) {
1396 Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
1399 CameraUtil.showError(CameraActivity.this, R.string.camera_disabled,
1400 R.string.feedback_description_camera_access, true);
1406 public void onNewIntentTasks(Intent intent) {
1407 onModeSelected(getModeIndex());
1411 public void onCreateTasks(Bundle state) {
1412 Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start();
1413 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1414 mOnCreateTime = System.currentTimeMillis();
1415 mAppContext = getApplicationContext();
1416 mMainHandler = new MainHandler(this, getMainLooper());
1417 mLocationManager = new LocationManager(mAppContext);
1418 mOrientationManager = new OrientationManagerImpl(this, mMainHandler);
1419 mSettingsManager = getServices().getSettingsManager();
1420 mSoundPlayer = new SoundPlayer(mAppContext);
1421 mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver());
1424 if (!Glide.isSetup()) {
1425 Context context = getAndroidContext();
1426 Glide.setup(new GlideBuilder(context)
1427 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888)
1428 .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
1430 Glide glide = Glide.get(context);
1432 // As a camera we will use a large amount of memory
1433 // for displaying images.
1434 glide.setMemoryCategory(MemoryCategory.HIGH);
1436 // Prefill glides bitmap pool to prevent excessive jank
1437 // when loading large images.
1438 glide.preFillBitmapPool(
1439 new PreFillType.Builder(GlideFilmstripManager.MAXIMUM_TEXTURE_SIZE)
1441 // It's more important for jank and GC to have
1442 // A larger weight of max texture size images than
1443 // media store sized images.
1444 new PreFillType.Builder(
1445 GlideFilmstripManager.MEDIASTORE_THUMB_WIDTH,
1446 GlideFilmstripManager.MEDIASTORE_THUMB_HEIGHT));
1448 profile.mark("Glide.setup");
1450 mCameraManager = OneCameraManager.get(this, ResolutionUtil.getDisplayMetrics(this),
1452 } catch (OneCameraException e) {
1453 // Log error and continue. Modules requiring OneCamera should check
1454 // and handle if null by showing error dialog or other treatment.
1455 Log.e(TAG, "Creating camera manager failed.", e);
1456 CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
1458 profile.mark("OneCameraManager.get");
1459 mCameraController = new CameraController(mAppContext, this, mMainHandler,
1460 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1461 CameraAgentFactory.CameraApi.API_1),
1462 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1463 CameraAgentFactory.CameraApi.AUTO));
1464 mCameraController.setCameraExceptionHandler(
1465 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
1467 // TODO: Try to move all the resources allocation to happen as soon as
1468 // possible so we can call module.init() at the earliest time.
1469 mModuleManager = new ModuleManagerImpl();
1471 ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig);
1473 AppUpgrader appUpgrader = new AppUpgrader(this);
1474 appUpgrader.upgrade(mSettingsManager);
1475 Keys.setDefaults(mSettingsManager, mAppContext);
1477 mResolutionSetting = new ResolutionSetting(mSettingsManager, mCameraManager);
1479 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1480 // We suppress this flag via theme when drawing the system preview
1481 // background, but once we create activity here, reactivate to the
1482 // default value. The default is important for L, we don't want to
1483 // change app behavior, just starting background drawable layout.
1484 if (ApiHelper.isLOrHigher()) {
1485 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1489 setContentView(R.layout.activity_main);
1490 profile.mark("setContentView()");
1491 // A window background is set in styles.xml for the system to show a
1492 // drawable background with gray color and camera icon before the
1493 // activity is created. We set the background to null here to prevent
1494 // overdraw, all views must take care of drawing backgrounds if
1495 // necessary. This call to setBackgroundDrawable must occur after
1496 // setContentView, otherwise a background may be set again from the
1498 getWindow().setBackgroundDrawable(null);
1500 mActionBar = getActionBar();
1501 // set actionbar background to 100% or 50% transparent
1502 if (ApiHelper.isLOrHigher()) {
1503 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000));
1505 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
1508 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1509 mModeListView.init(mModuleManager.getSupportedModeIndexList());
1510 if (ApiHelper.HAS_ROTATION_ANIMATION) {
1511 setRotationAnimation();
1513 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1515 public void onVisibilityChanged(boolean visible) {
1516 mModeListVisible = visible;
1517 mCameraAppUI.setShutterButtonImportantToA11y(!visible);
1518 updatePreviewVisibility();
1522 // Check if this is in the secure camera mode.
1523 Intent intent = getIntent();
1524 String action = intent.getAction();
1525 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1526 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1527 mSecureCamera = true;
1529 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1532 if (mSecureCamera) {
1533 // Change the window flags so that secure camera can show when
1535 Window win = getWindow();
1536 WindowManager.LayoutParams params = win.getAttributes();
1537 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1538 win.setAttributes(params);
1540 // Filter for screen off so that we can finish secure camera
1541 // activity when screen is off.
1542 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1543 registerReceiver(mShutdownReceiver, filter_screen_off);
1545 // Filter for phone unlock so that we can finish secure camera
1546 // via this UI path:
1547 // 1. from secure lock screen, user starts secure camera
1548 // 2. user presses home button
1549 // 3. user unlocks phone
1550 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
1551 registerReceiver(mShutdownReceiver, filter_user_unlock);
1553 mCameraAppUI = new CameraAppUI(this,
1554 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1556 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1558 mAboveFilmstripControlLayout =
1559 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1561 // Add the session listener so we can track the session progress
1563 getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1564 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1565 mFilmstripController.setImageGap(
1566 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1567 profile.mark("Configure Camera UI");
1569 mPanoramaViewHelper = new PanoramaViewHelper(this);
1570 mPanoramaViewHelper.onCreate();
1572 ContentResolver appContentResolver = mAppContext.getContentResolver();
1573 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
1574 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
1575 new PhotoDataFactory());
1576 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
1577 new VideoDataFactory());
1578 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
1579 mPhotoItemFactory, mVideoItemFactory);
1580 mDataAdapter.setLocalDataListener(mFilmstripItemListener);
1582 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1585 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1586 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1587 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1588 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1591 setModuleFromModeIndex(getModeIndex());
1594 mCameraAppUI.prepareModuleUI();
1595 profile.mark("Init Current Module UI");
1596 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1597 profile.mark("Init CurrentModule");
1599 if (!mSecureCamera) {
1600 mFilmstripController.setDataAdapter(mDataAdapter);
1601 if (!isCaptureIntent()) {
1602 mDataAdapter.requestLoad(new Callback<Void>() {
1604 public void onCallback(Void result) {
1605 fillTemporarySessions();
1610 // Put a lock placeholder as the last image by setting its date to
1612 ImageView v = (ImageView) getLayoutInflater().inflate(
1613 R.layout.secure_album_placeholder, null);
1614 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
1615 v.setOnClickListener(new View.OnClickListener() {
1617 public void onClick(View view) {
1618 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1619 NavigationChange.InteractionCause.BUTTON);
1624 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1625 mDataAdapter = new FixedLastProxyAdapter(
1628 new PlaceholderItem(
1630 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
1631 v.getDrawable().getIntrinsicWidth(),
1632 v.getDrawable().getIntrinsicHeight()));
1633 // Flush out all the original data.
1634 mDataAdapter.clear();
1635 mFilmstripController.setDataAdapter(mDataAdapter);
1640 mLocalImagesObserver = new FilmstripContentObserver();
1641 mLocalVideosObserver = new FilmstripContentObserver();
1643 getContentResolver().registerContentObserver(
1644 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1645 mLocalImagesObserver);
1646 getContentResolver().registerContentObserver(
1647 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1648 mLocalVideosObserver);
1650 mMemoryManager = getServices().getMemoryManager();
1652 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1655 HashMap memoryData = mMemoryManager.queryMemory();
1656 UsageStatistics.instance().reportMemoryConsumed(memoryData,
1657 MemoryQuery.REPORT_LABEL_LAUNCH);
1661 mMotionManager = getServices().getMotionManager();
1663 mFirstRunDialog = new FirstRunDialog(this, new FirstRunDialog.FirstRunDialogListener() {
1665 public void onFirstRunStateReady() {
1666 // Run normal resume tasks.
1671 public void onCameraAccessException() {
1672 CameraUtil.showError(CameraActivity.this, R.string.camera_disabled,
1673 R.string.feedback_description_camera_access, true);
1680 * Get the current mode index from the Intent or from persistent
1683 private int getModeIndex() {
1685 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1686 int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1687 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1688 int captureIntentIndex =
1689 getResources().getInteger(R.integer.camera_mode_capture_intent);
1690 String intentAction = getIntent().getAction();
1691 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction)
1692 || MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) {
1693 modeIndex = videoIndex;
1694 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction)
1695 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1697 modeIndex = captureIntentIndex;
1698 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction)
1699 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction)
1700 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1701 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1702 Keys.KEY_CAMERA_MODULE_LAST_USED);
1704 // For upgraders who have not seen the aspect ratio selection screen,
1705 // we need to drop them back in the photo module and have them select
1707 // TODO: Move this to SettingsManager as an upgrade procedure.
1708 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1709 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
1710 modeIndex = photoIndex;
1713 // If the activity has not been started using an explicit intent,
1714 // read the module index from the last time the user changed modes
1715 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1716 Keys.KEY_STARTUP_MODULE_INDEX);
1717 if ((modeIndex == gcamIndex &&
1718 !GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) || modeIndex < 0) {
1719 modeIndex = photoIndex;
1726 * Call this whenever the mode drawer or filmstrip change the visibility
1729 private void updatePreviewVisibility() {
1730 if (mCurrentModule == null) {
1734 int visibility = getPreviewVisibility();
1735 mCameraAppUI.onPreviewVisiblityChanged(visibility);
1736 updatePreviewRendering(visibility);
1737 mCurrentModule.onPreviewVisibilityChanged(visibility);
1740 private void updatePreviewRendering(int visibility) {
1741 if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1742 mCameraAppUI.pausePreviewRendering();
1744 mCameraAppUI.resumePreviewRendering();
1748 private int getPreviewVisibility() {
1749 if (mFilmstripCoversPreview) {
1750 return ModuleController.VISIBILITY_HIDDEN;
1751 } else if (mModeListVisible){
1752 return ModuleController.VISIBILITY_COVERED;
1754 return ModuleController.VISIBILITY_VISIBLE;
1758 private void setRotationAnimation() {
1759 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1760 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1761 Window win = getWindow();
1762 WindowManager.LayoutParams winParams = win.getAttributes();
1763 winParams.rotationAnimation = rotationAnimation;
1764 win.setAttributes(winParams);
1768 public void onUserInteraction() {
1769 super.onUserInteraction();
1770 if (!isFinishing()) {
1771 keepScreenOnForAWhile();
1776 public boolean dispatchTouchEvent(MotionEvent ev) {
1777 boolean result = super.dispatchTouchEvent(ev);
1778 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1779 // Real deletion is postponed until the next user interaction after
1780 // the gesture that triggers deletion. Until real deletion is
1781 // performed, users can click the undo button to bring back the
1782 // image that they chose to delete.
1783 if (mPendingDeletion && !mIsUndoingDeletion) {
1791 public void onPauseTasks() {
1792 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1793 Profile profile = mProfiler.create("CameraActivity.onPause").start();
1796 * Save the last module index after all secure camera and icon launches,
1797 * not just on mode switches.
1799 * Right now we exclude capture intents from this logic, because we also
1800 * ignore the cross-Activity recovery logic in onStart for capture intents.
1802 if (!isCaptureIntent()) {
1803 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
1804 Keys.KEY_STARTUP_MODULE_INDEX,
1809 mCameraAppUI.hideCaptureIndicator();
1810 mFirstRunDialog.dismiss();
1812 // Delete photos that are pending deletion
1814 mCurrentModule.pause();
1815 mOrientationManager.pause();
1816 mPanoramaViewHelper.onPause();
1818 mLocalImagesObserver.setForegroundChangeListener(null);
1819 mLocalImagesObserver.setActivityPaused(true);
1820 mLocalVideosObserver.setActivityPaused(true);
1821 mPreloader.cancelAllLoads();
1824 mMotionManager.stop();
1826 // Always stop recording location when paused. Resume will start
1827 // location recording again if the location setting is on.
1828 mLocationManager.recordLocation(false);
1830 UsageStatistics.instance().backgrounded();
1832 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
1833 // button. Let's just kill the process.
1834 if (mCameraFatalError && !isFinishing()) {
1835 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
1838 // Close the camera and wait for the operation done.
1839 Log.v(TAG, "onPause closing camera");
1840 mCameraController.closeCamera(true);
1847 public void onResumeTasks() {
1850 // Show the dialog if necessary. The rest resume logic will be invoked
1851 // at the onFirstRunStateReady() callback.
1852 mFirstRunDialog.showIfNecessary();
1855 private void resume() {
1856 Profile profile = mProfiler.create("CameraActivity.resume").start();
1857 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1858 Log.v(TAG, "Build info: " + Build.DISPLAY);
1860 updateStorageSpaceAndHint(null);
1862 mLastLayoutOrientation = getResources().getConfiguration().orientation;
1864 // TODO: Handle this in OrientationManager.
1866 if (Settings.System.getInt(getContentResolver(),
1867 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1868 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1869 mAutoRotateScreen = false;
1871 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1872 mAutoRotateScreen = true;
1875 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and
1876 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
1877 // lockscreen onResume->onPause->onResume sequence.
1879 String action = getIntent().getAction();
1880 if (action == null) {
1881 source = ForegroundSource.UNKNOWN_SOURCE;
1884 case MediaStore.ACTION_IMAGE_CAPTURE:
1885 source = ForegroundSource.ACTION_IMAGE_CAPTURE;
1887 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
1888 // was UNKNOWN_SOURCE in Fishlake.
1889 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
1891 case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
1892 // was UNKNOWN_SOURCE in Fishlake.
1893 source = ForegroundSource.ACTION_VIDEO_CAMERA;
1895 case MediaStore.ACTION_VIDEO_CAPTURE:
1896 source = ForegroundSource.ACTION_VIDEO_CAPTURE;
1898 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
1899 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
1900 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
1902 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
1903 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
1905 case Intent.ACTION_MAIN:
1906 source = ForegroundSource.ACTION_MAIN;
1909 source = ForegroundSource.UNKNOWN_SOURCE;
1913 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode());
1915 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
1916 if (ApiHelper.isLOrHigher()) {
1917 // hide the up affordance for L devices, it's not very Materially
1918 mActionBar.setDisplayShowHomeEnabled(false);
1921 mOrientationManager.resume();
1923 mCurrentModule.hardResetSettings(mSettingsManager);
1926 mCurrentModule.resume();
1927 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
1928 NavigationChange.InteractionCause.BUTTON);
1929 setSwipingEnabled(true);
1930 profile.mark("mCurrentModule.resume");
1932 if (!mResetToPreviewOnResume) {
1933 FilmstripItem item = mDataAdapter.getItemAt(
1934 mFilmstripController.getCurrentAdapterIndex());
1936 mDataAdapter.refresh(item.getData().getUri());
1940 // The share button might be disabled to avoid double tapping.
1941 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1942 // Default is showing the preview, unless disabled by explicitly
1943 // starting an activity we want to return from to the filmstrip rather
1944 // than the preview.
1945 mResetToPreviewOnResume = true;
1947 if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1948 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1949 if (!mSecureCamera) {
1950 // If it's secure camera, requestLoad() should not be called
1951 // as it will load all the data.
1952 if (!mFilmstripVisible) {
1953 mDataAdapter.requestLoad(new Callback<Void>() {
1955 public void onCallback(Void result) {
1956 fillTemporarySessions();
1960 mDataAdapter.requestLoadNewPhotos();
1964 mLocalImagesObserver.setActivityPaused(false);
1965 mLocalVideosObserver.setActivityPaused(false);
1966 if (!mSecureCamera) {
1967 mLocalImagesObserver.setForegroundChangeListener(
1968 new FilmstripContentObserver.ChangeListener() {
1970 public void onChange() {
1971 mDataAdapter.requestLoadNewPhotos();
1976 keepScreenOnForAWhile();
1978 // Lights-out mode at all times.
1979 final View rootView = findViewById(R.id.activity_root_view);
1980 mLightsOutRunnable.run();
1981 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
1982 new OnSystemUiVisibilityChangeListener() {
1984 public void onSystemUiVisibilityChange(int visibility) {
1985 mMainHandler.removeCallbacks(mLightsOutRunnable);
1986 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
1991 mPanoramaViewHelper.onResume();
1992 profile.mark("mPanoramaViewHelper.onResume()");
1994 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1995 // Enable location recording if the setting is on.
1996 final boolean locationRecordingEnabled =
1997 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
1998 mLocationManager.recordLocation(locationRecordingEnabled);
2000 final int previewVisibility = getPreviewVisibility();
2001 updatePreviewRendering(previewVisibility);
2003 mMotionManager.start();
2007 private void fillTemporarySessions() {
2008 if (mSecureCamera) {
2011 // There might be sessions still in flight (processed by our service).
2012 // Make sure they're added to the filmstrip.
2013 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
2017 public void onStartTasks() {
2018 mIsActivityRunning = true;
2019 mPanoramaViewHelper.onStart();
2022 * If we're starting after launching a different Activity (lockscreen),
2023 * we need to use the last mode used in the other Activity, and
2024 * not the old one from this Activity.
2026 * This needs to happen before CameraAppUI.resume() in order to set the
2027 * mode cover icon to the actual last mode used.
2029 * Right now we exclude capture intents from this logic.
2031 int modeIndex = getModeIndex();
2032 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
2033 onModeSelected(modeIndex);
2036 if (mResetToPreviewOnResume) {
2037 mCameraAppUI.resume();
2038 mResetToPreviewOnResume = false;
2043 protected void onStopTasks() {
2044 mIsActivityRunning = false;
2045 mPanoramaViewHelper.onStop();
2047 mLocationManager.disconnect();
2051 public void onDestroyTasks() {
2052 if (mSecureCamera) {
2053 unregisterReceiver(mShutdownReceiver);
2056 mSettingsManager.removeAllListeners();
2057 mCameraController.removeCallbackReceiver();
2058 mCameraController.setCameraExceptionHandler(null);
2059 getContentResolver().unregisterContentObserver(mLocalImagesObserver);
2060 getContentResolver().unregisterContentObserver(mLocalVideosObserver);
2061 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
2062 mCameraAppUI.onDestroy();
2063 mModeListView.setVisibilityChangedListener(null);
2064 mCameraController = null;
2065 mSettingsManager = null;
2066 mOrientationManager = null;
2067 mButtonManager = null;
2068 mSoundPlayer.release();
2069 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
2070 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
2074 public void onConfigurationChanged(Configuration config) {
2075 super.onConfigurationChanged(config);
2076 Log.v(TAG, "onConfigurationChanged");
2077 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
2081 if (mLastLayoutOrientation != config.orientation) {
2082 mLastLayoutOrientation = config.orientation;
2083 mCurrentModule.onLayoutOrientationChanged(
2084 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
2089 public boolean onKeyDown(int keyCode, KeyEvent event) {
2090 if (!mFilmstripVisible) {
2091 if (mCurrentModule.onKeyDown(keyCode, event)) {
2094 // Prevent software keyboard or voice search from showing up.
2095 if (keyCode == KeyEvent.KEYCODE_SEARCH
2096 || keyCode == KeyEvent.KEYCODE_MENU) {
2097 if (event.isLongPress()) {
2103 return super.onKeyDown(keyCode, event);
2107 public boolean onKeyUp(int keyCode, KeyEvent event) {
2108 if (!mFilmstripVisible) {
2109 // If a module is in the middle of capture, it should
2110 // consume the key event.
2111 if (mCurrentModule.onKeyUp(keyCode, event)) {
2113 } else if (keyCode == KeyEvent.KEYCODE_MENU
2114 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2115 // Let the mode list view consume the event.
2116 mCameraAppUI.openModeList();
2118 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2119 mCameraAppUI.showFilmstrip();
2123 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2124 mFilmstripController.goToNextItem();
2126 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2127 boolean wentToPrevious = mFilmstripController.goToPreviousItem();
2128 if (!wentToPrevious) {
2129 // at beginning of filmstrip, hide and go back to preview
2130 mCameraAppUI.hideFilmstrip();
2135 return super.onKeyUp(keyCode, event);
2139 public void onBackPressed() {
2140 if (!mCameraAppUI.onBackPressed()) {
2141 if (!mCurrentModule.onBackPressed()) {
2142 super.onBackPressed();
2148 public boolean isAutoRotateScreen() {
2149 // TODO: Move to OrientationManager.
2150 return mAutoRotateScreen;
2154 public boolean onCreateOptionsMenu(Menu menu) {
2155 MenuInflater inflater = getMenuInflater();
2156 inflater.inflate(R.menu.filmstrip_menu, menu);
2157 mActionBarMenu = menu;
2159 // add a button for launching the gallery
2160 if (mGalleryIntent != null) {
2161 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
2162 if (appName != null) {
2163 MenuItem menuItem = menu.add(appName);
2164 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2165 menuItem.setIntent(mGalleryIntent);
2167 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
2168 if (galleryLogo != null) {
2169 menuItem.setIcon(galleryLogo);
2174 return super.onCreateOptionsMenu(menu);
2178 public boolean onPrepareOptionsMenu(Menu menu) {
2179 if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
2180 // Compatibility pre-L: launching new activities right above
2181 // lockscreen does not reliably work, only show help if not secure
2182 menu.removeItem(R.id.action_help_and_feedback);
2185 return super.onPrepareOptionsMenu(menu);
2188 protected long getStorageSpaceBytes() {
2189 synchronized (mStorageSpaceLock) {
2190 return mStorageSpaceBytes;
2194 protected interface OnStorageUpdateDoneListener {
2195 public void onStorageUpdateDone(long bytes);
2198 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
2200 * We execute disk operations on a background thread in order to
2201 * free up the UI thread. Synchronizing on the lock below ensures
2202 * that when getStorageSpaceBytes is called, the main thread waits
2203 * until this method has completed.
2205 * However, .execute() does not ensure this execution block will be
2206 * run right away (.execute() schedules this AsyncTask for sometime
2207 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
2208 * tries to execute the task in parellel with other AsyncTasks, but
2209 * there's still no guarantee).
2210 * e.g. don't call this then immediately call getStorageSpaceBytes().
2211 * Instead, pass in an OnStorageUpdateDoneListener.
2213 (new AsyncTask<Void, Void, Long>() {
2215 protected Long doInBackground(Void ... arg) {
2216 synchronized (mStorageSpaceLock) {
2217 mStorageSpaceBytes = Storage.getAvailableSpace();
2218 return mStorageSpaceBytes;
2223 protected void onPostExecute(Long bytes) {
2224 updateStorageHint(bytes);
2225 // This callback returns after I/O to check disk, so we could be
2226 // pausing and shutting down. If so, don't bother invoking.
2227 if (callback != null && !mPaused) {
2228 callback.onStorageUpdateDone(bytes);
2230 Log.v(TAG, "ignoring storage callback after activity pause");
2233 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2236 protected void updateStorageHint(long storageSpace) {
2237 if (!mIsActivityRunning) {
2241 String message = null;
2242 if (storageSpace == Storage.UNAVAILABLE) {
2243 message = getString(R.string.no_storage);
2244 } else if (storageSpace == Storage.PREPARING) {
2245 message = getString(R.string.preparing_sd);
2246 } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2247 message = getString(R.string.access_sd_fail);
2248 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2249 message = getString(R.string.spaceIsLow_content);
2252 if (message != null) {
2253 Log.w(TAG, "Storage warning: " + message);
2254 if (mStorageHint == null) {
2255 mStorageHint = OnScreenHint.makeText(message);
2257 mStorageHint.setText(message);
2259 mStorageHint.show();
2260 UsageStatistics.instance().storageWarning(storageSpace);
2262 // Disable all user interactions,
2263 mCameraAppUI.setDisableAllUserInteractions(true);
2264 } else if (mStorageHint != null) {
2265 mStorageHint.cancel();
2266 mStorageHint = null;
2268 // Re-enable all user interactions.
2269 mCameraAppUI.setDisableAllUserInteractions(false);
2273 protected void setResultEx(int resultCode) {
2274 mResultCodeForTesting = resultCode;
2275 setResult(resultCode);
2278 protected void setResultEx(int resultCode, Intent data) {
2279 mResultCodeForTesting = resultCode;
2280 mResultDataForTesting = data;
2281 setResult(resultCode, data);
2284 public int getResultCode() {
2285 return mResultCodeForTesting;
2288 public Intent getResultData() {
2289 return mResultDataForTesting;
2292 public boolean isSecureCamera() {
2293 return mSecureCamera;
2297 public boolean isPaused() {
2302 public int getPreferredChildModeIndex(int modeIndex) {
2303 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2304 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2305 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) {
2306 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2313 public void onModeSelected(int modeIndex) {
2314 if (mCurrentModeIndex == modeIndex) {
2318 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2319 // Record last used camera mode for quick switching
2320 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2321 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2322 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2323 Keys.KEY_CAMERA_MODULE_LAST_USED,
2327 closeModule(mCurrentModule);
2329 // Select the correct module index from the mode switcher index.
2330 modeIndex = getPreferredChildModeIndex(modeIndex);
2331 setModuleFromModeIndex(modeIndex);
2333 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2334 mCameraAppUI.addShutterListener(mCurrentModule);
2335 openModule(mCurrentModule);
2336 // Store the module index so we can use it the next time the Camera
2338 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2339 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2343 * Shows the settings dialog.
2346 public void onSettingsSelected() {
2347 UsageStatistics.instance().controlUsed(
2348 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2349 Intent intent = new Intent(this, CameraSettingsActivity.class);
2350 startActivity(intent);
2354 public void freezeScreenUntilPreviewReady() {
2355 mCameraAppUI.freezeScreenUntilPreviewReady();
2359 public int getModuleId(int modeIndex) {
2360 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2361 if (agent == null) {
2364 return agent.getModuleId();
2368 * Sets the mCurrentModuleIndex, creates a new module instance for the given
2369 * index an sets it as mCurrentModule.
2371 private void setModuleFromModeIndex(int modeIndex) {
2372 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2373 if (agent == null) {
2376 if (!agent.requestAppForCamera()) {
2377 mCameraController.closeCamera(true);
2379 mCurrentModeIndex = agent.getModuleId();
2380 mCurrentModule = (CameraModule) agent.createModule(this, getIntent());
2384 public SettingsManager getSettingsManager() {
2385 return mSettingsManager;
2389 public ResolutionSetting getResolutionSetting() {
2390 return mResolutionSetting;
2394 public CameraServices getServices() {
2395 return CameraServicesImpl.instance();
2398 public List<String> getSupportedModeNames() {
2399 List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2400 List<String> supported = new ArrayList<String>();
2402 for (Integer modeIndex : indices) {
2403 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2404 if (name != null && !name.equals("")) {
2405 supported.add(name);
2412 public ButtonManager getButtonManager() {
2413 if (mButtonManager == null) {
2414 mButtonManager = new ButtonManager(this);
2416 return mButtonManager;
2420 public SoundPlayer getSoundPlayer() {
2421 return mSoundPlayer;
2425 * Launches an ACTION_EDIT intent for the given local data item. If
2426 * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2427 * the user start either the tiny planet editor or another photo editor.
2429 * @param data The data item to edit.
2431 public void launchEditor(FilmstripItem data) {
2432 Intent intent = new Intent(Intent.ACTION_EDIT)
2433 .setDataAndType(data.getData().getUri(), data.getData().getMimeType())
2434 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2436 launchActivityByIntent(intent);
2437 } catch (ActivityNotFoundException e) {
2438 final String msgEditWith = getResources().getString(R.string.edit_with);
2439 launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2444 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2445 super.onCreateContextMenu(menu, v, menuInfo);
2447 MenuInflater inflater = getMenuInflater();
2448 inflater.inflate(R.menu.filmstrip_context_menu, menu);
2452 public boolean onContextItemSelected(MenuItem item) {
2453 switch (item.getItemId()) {
2454 case R.id.tiny_planet_editor:
2455 mMyFilmstripBottomControlListener.onTinyPlanet();
2457 case R.id.photo_editor:
2458 mMyFilmstripBottomControlListener.onEdit();
2465 * Launch the tiny planet editor.
2467 * @param data The data must be a 360 degree stereographically mapped
2468 * panoramic image. It will not be modified, instead a new item
2469 * with the result will be added to the filmstrip.
2471 public void launchTinyPlanetEditor(FilmstripItem data) {
2472 TinyPlanetFragment fragment = new TinyPlanetFragment();
2473 Bundle bundle = new Bundle();
2474 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString());
2475 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle());
2476 fragment.setArguments(bundle);
2477 fragment.show(getFragmentManager(), "tiny_planet");
2481 * Returns what UI mode (capture mode or filmstrip) we are in.
2482 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2484 private int currentUserInterfaceMode() {
2485 int mode = NavigationChange.Mode.UNKNOWN_MODE;
2486 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2487 mode = NavigationChange.Mode.PHOTO_CAPTURE;
2489 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2490 mode = NavigationChange.Mode.VIDEO_CAPTURE;
2492 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2493 mode = NavigationChange.Mode.LENS_BLUR;
2495 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2496 mode = NavigationChange.Mode.HDR_PLUS;
2498 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2499 mode = NavigationChange.Mode.PHOTO_SPHERE;
2501 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2502 mode = NavigationChange.Mode.PANORAMA;
2504 if (mFilmstripVisible) {
2505 mode = NavigationChange.Mode.FILMSTRIP;
2510 private void openModule(CameraModule module) {
2511 module.init(this, isSecureCamera(), isCaptureIntent());
2512 module.hardResetSettings(mSettingsManager);
2515 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2516 NavigationChange.InteractionCause.BUTTON);
2517 updatePreviewVisibility();
2521 private void closeModule(CameraModule module) {
2523 mCameraAppUI.clearModuleUI();
2526 private void performDeletion() {
2527 if (!mPendingDeletion) {
2530 hideUndoDeletionBar(false);
2531 mDataAdapter.executeDeletion();
2534 public void showUndoDeletionBar() {
2535 if (mPendingDeletion) {
2538 Log.v(TAG, "showing undo bar");
2539 mPendingDeletion = true;
2540 if (mUndoDeletionBar == null) {
2541 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2542 mAboveFilmstripControlLayout, true);
2543 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2544 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2545 button.setOnClickListener(new View.OnClickListener() {
2547 public void onClick(View view) {
2548 mDataAdapter.undoDeletion();
2549 hideUndoDeletionBar(true);
2552 // Setting undo bar clickable to avoid touch events going through
2553 // the bar to the buttons (eg. edit button, etc) underneath the bar.
2554 mUndoDeletionBar.setClickable(true);
2555 // When there is user interaction going on with the undo button, we
2556 // do not want to hide the undo bar.
2557 button.setOnTouchListener(new View.OnTouchListener() {
2559 public boolean onTouch(View v, MotionEvent event) {
2560 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2561 mIsUndoingDeletion = true;
2562 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2563 mIsUndoingDeletion = false;
2569 mUndoDeletionBar.setAlpha(0f);
2570 mUndoDeletionBar.setVisibility(View.VISIBLE);
2571 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2574 private void hideUndoDeletionBar(boolean withAnimation) {
2575 Log.v(TAG, "Hiding undo deletion bar");
2576 mPendingDeletion = false;
2577 if (mUndoDeletionBar != null) {
2578 if (withAnimation) {
2579 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2580 .setListener(new Animator.AnimatorListener() {
2582 public void onAnimationStart(Animator animation) {
2587 public void onAnimationEnd(Animator animation) {
2588 mUndoDeletionBar.setVisibility(View.GONE);
2592 public void onAnimationCancel(Animator animation) {
2597 public void onAnimationRepeat(Animator animation) {
2602 mUndoDeletionBar.setVisibility(View.GONE);
2608 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2611 * @param enable {@code true} to enable swipe.
2613 public void setSwipingEnabled(boolean enable) {
2614 // TODO: Bring back the functionality.
2615 if (isCaptureIntent()) {
2616 // lockPreview(true);
2618 // lockPreview(!enable);
2622 // Accessor methods for getting latency times used in performance testing
2623 public long getFirstPreviewTime() {
2624 if (mCurrentModule instanceof PhotoModule) {
2625 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2626 if (coverHiddenTime != -1) {
2627 return coverHiddenTime - mOnCreateTime;
2633 public long getAutoFocusTime() {
2634 return (mCurrentModule instanceof PhotoModule) ?
2635 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2638 public long getShutterLag() {
2639 return (mCurrentModule instanceof PhotoModule) ?
2640 ((PhotoModule) mCurrentModule).mShutterLag : -1;
2643 public long getShutterToPictureDisplayedTime() {
2644 return (mCurrentModule instanceof PhotoModule) ?
2645 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2648 public long getPictureDisplayedToJpegCallbackTime() {
2649 return (mCurrentModule instanceof PhotoModule) ?
2650 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2653 public long getJpegCallbackFinishTime() {
2654 return (mCurrentModule instanceof PhotoModule) ?
2655 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2658 public long getCaptureStartTime() {
2659 return (mCurrentModule instanceof PhotoModule) ?
2660 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2663 public boolean isRecording() {
2664 return (mCurrentModule instanceof VideoModule) ?
2665 ((VideoModule) mCurrentModule).isRecording() : false;
2668 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2669 return mCameraController;
2672 // For debugging purposes only.
2673 public CameraModule getCurrentModule() {
2674 return mCurrentModule;
2678 public void showTutorial(AbstractTutorialOverlay tutorial) {
2679 mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2683 public void showErrorAndFinish(int messageId) {
2684 CameraUtil.showError(this, messageId, R.string.feedback_description_camera_access, true);
2688 public void finishActivityWithIntentCompleted(Intent resultIntent) {
2689 finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent);
2693 public void finishActivityWithIntentCanceled() {
2694 finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent());
2697 private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) {
2698 mResultCodeForTesting = resultCode;
2699 mResultDataForTesting = resultIntent;
2700 setResult(resultCode, resultIntent);
2704 private void keepScreenOnForAWhile() {
2705 if (mKeepScreenOn) {
2708 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2709 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2710 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2713 private void resetScreenOn() {
2714 mKeepScreenOn = false;
2715 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2716 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2720 * @return {@code true} if the Gallery is launched successfully.
2722 private boolean startGallery() {
2723 if (mGalleryIntent == null) {
2727 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2728 NavigationChange.InteractionCause.BUTTON);
2729 Intent startGalleryIntent = new Intent(mGalleryIntent);
2730 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
2731 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex);
2732 if (currentFilmstripItem != null) {
2733 GalleryHelper.setContentUri(startGalleryIntent,
2734 currentFilmstripItem.getData().getUri());
2736 launchActivityByIntent(startGalleryIntent);
2737 } catch (ActivityNotFoundException e) {
2738 Log.w(TAG, "Failed to launch gallery activity, closing");
2743 private void setNfcBeamPushUriFromData(FilmstripItem data) {
2744 final Uri uri = data.getData().getUri();
2745 if (uri != Uri.EMPTY) {
2746 mNfcPushUris[0] = uri;
2748 mNfcPushUris[0] = null;
2753 * Updates the visibility of the filmstrip bottom controls and action bar.
2755 private void updateUiByData(final int index) {
2756 final FilmstripItem currentData = mDataAdapter.getItemAt(index);
2757 if (currentData == null) {
2758 Log.w(TAG, "Current data ID not found.");
2759 hideSessionProgress();
2762 updateActionBarMenu(currentData);
2764 /* Bottom controls. */
2765 updateBottomControlsByData(currentData);
2767 if (isSecureCamera()) {
2768 // We cannot show buttons in secure camera since go to other
2769 // activities might create a security hole.
2770 mCameraAppUI.getFilmstripBottomControls().hideControls();
2774 setNfcBeamPushUriFromData(currentData);
2776 if (!mDataAdapter.isMetadataUpdatedAt(index)) {
2777 mDataAdapter.updateMetadataAt(index);
2782 * Updates the bottom controls based on the data.
2784 private void updateBottomControlsByData(final FilmstripItem currentData) {
2786 final CameraAppUI.BottomPanel filmstripBottomPanel =
2787 mCameraAppUI.getFilmstripBottomControls();
2788 filmstripBottomPanel.showControls();
2789 filmstripBottomPanel.setEditButtonVisibility(
2790 currentData.getAttributes().canEdit());
2791 filmstripBottomPanel.setShareButtonVisibility(
2792 currentData.getAttributes().canShare());
2793 filmstripBottomPanel.setDeleteButtonVisibility(
2794 currentData.getAttributes().canDelete());
2798 Uri contentUri = currentData.getData().getUri();
2799 CaptureSessionManager sessionManager = getServices()
2800 .getCaptureSessionManager();
2802 if (sessionManager.hasErrorMessage(contentUri)) {
2803 showProcessError(sessionManager.getErrorMessage(contentUri));
2805 filmstripBottomPanel.hideProgressError();
2806 CaptureSession session = sessionManager.getSession(contentUri);
2808 if (session != null) {
2809 int sessionProgress = session.getProgress();
2811 if (sessionProgress < 0) {
2812 hideSessionProgress();
2814 CharSequence progressMessage = session.getProgressMessage();
2815 showSessionProgress(progressMessage);
2816 updateSessionProgress(sessionProgress);
2819 hideSessionProgress();
2825 // We need to add this to a separate DB.
2826 final int viewButtonVisibility;
2827 if (currentData.getMetadata().isUsePanoramaViewer()) {
2828 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2829 } else if (currentData.getMetadata().isHasRgbzData()) {
2830 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2832 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2835 filmstripBottomPanel.setTinyPlanetEnabled(
2836 currentData.getMetadata().isPanorama360());
2837 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2840 private void showDetailsDialog(int index) {
2841 final FilmstripItem data = mDataAdapter.getItemAt(index);
2845 Optional<MediaDetails> details = data.getMediaDetails();
2846 if (!details.isPresent()) {
2849 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get());
2850 detailDialog.show();
2851 UsageStatistics.instance().mediaInteraction(
2852 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS,
2853 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index));
2857 * Show or hide action bar items depending on current data type.
2859 private void updateActionBarMenu(FilmstripItem data) {
2860 if (mActionBarMenu == null) {
2864 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2865 if (detailsMenuItem == null) {
2869 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo();
2870 detailsMenuItem.setVisible(showDetails);