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.Button;
65 import android.widget.FrameLayout;
66 import android.widget.ImageView;
67 import android.widget.ShareActionProvider;
69 import com.android.camera.app.AppController;
70 import com.android.camera.app.CameraAppUI;
71 import com.android.camera.app.CameraController;
72 import com.android.camera.app.CameraProvider;
73 import com.android.camera.app.CameraServices;
74 import com.android.camera.app.CameraServicesImpl;
75 import com.android.camera.app.FirstRunDialog;
76 import com.android.camera.app.LocationManager;
77 import com.android.camera.app.MemoryManager;
78 import com.android.camera.app.MemoryQuery;
79 import com.android.camera.app.ModuleManager;
80 import com.android.camera.app.ModuleManager.ModuleAgent;
81 import com.android.camera.app.ModuleManagerImpl;
82 import com.android.camera.app.MotionManager;
83 import com.android.camera.app.OrientationManager;
84 import com.android.camera.app.OrientationManagerImpl;
85 import com.android.camera.data.CameraFilmstripDataAdapter;
86 import com.android.camera.data.FilmstripContentObserver;
87 import com.android.camera.data.FilmstripItem;
88 import com.android.camera.data.FilmstripItemData;
89 import com.android.camera.data.FilmstripItemType;
90 import com.android.camera.data.FilmstripItemUtils;
91 import com.android.camera.data.FixedLastProxyAdapter;
92 import com.android.camera.data.GlideFilmstripManager;
93 import com.android.camera.data.LocalFilmstripDataAdapter;
94 import com.android.camera.data.LocalFilmstripDataAdapter.FilmstripItemListener;
95 import com.android.camera.data.MediaDetails;
96 import com.android.camera.data.MetadataLoader;
97 import com.android.camera.data.PhotoDataFactory;
98 import com.android.camera.data.PhotoItem;
99 import com.android.camera.data.PhotoItemFactory;
100 import com.android.camera.data.PlaceholderItem;
101 import com.android.camera.data.SessionItem;
102 import com.android.camera.data.VideoDataFactory;
103 import com.android.camera.data.VideoItemFactory;
104 import com.android.camera.debug.Log;
105 import com.android.camera.filmstrip.FilmstripContentPanel;
106 import com.android.camera.filmstrip.FilmstripController;
107 import com.android.camera.module.ModuleController;
108 import com.android.camera.module.ModulesInfo;
109 import com.android.camera.one.OneCameraException;
110 import com.android.camera.one.OneCameraManager;
111 import com.android.camera.one.config.OneCameraFeatureConfig;
112 import com.android.camera.one.config.OneCameraFeatureConfigCreator;
113 import com.android.camera.session.CaptureSession;
114 import com.android.camera.session.CaptureSessionManager;
115 import com.android.camera.session.CaptureSessionManager.SessionListener;
116 import com.android.camera.settings.AppUpgrader;
117 import com.android.camera.settings.CameraSettingsActivity;
118 import com.android.camera.settings.Keys;
119 import com.android.camera.settings.ResolutionSetting;
120 import com.android.camera.settings.ResolutionUtil;
121 import com.android.camera.settings.SettingsManager;
122 import com.android.camera.stats.UsageStatistics;
123 import com.android.camera.stats.profiler.Profile;
124 import com.android.camera.stats.profiler.Profiler;
125 import com.android.camera.stats.profiler.Profilers;
126 import com.android.camera.tinyplanet.TinyPlanetFragment;
127 import com.android.camera.ui.AbstractTutorialOverlay;
128 import com.android.camera.ui.DetailsDialog;
129 import com.android.camera.ui.MainActivityLayout;
130 import com.android.camera.ui.ModeListView;
131 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
132 import com.android.camera.ui.PreviewStatusListener;
133 import com.android.camera.util.ApiHelper;
134 import com.android.camera.util.Callback;
135 import com.android.camera.util.CameraUtil;
136 import com.android.camera.util.GalleryHelper;
137 import com.android.camera.util.GcamHelper;
138 import com.android.camera.util.GoogleHelpHelper;
139 import com.android.camera.util.IntentHelper;
140 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
141 import com.android.camera.util.QuickActivity;
142 import com.android.camera.util.ReleaseHelper;
143 import com.android.camera.widget.FilmstripView;
144 import com.android.camera.widget.Preloader;
145 import com.android.camera2.R;
146 import com.android.ex.camera2.portability.CameraAgent;
147 import com.android.ex.camera2.portability.CameraAgentFactory;
148 import com.android.ex.camera2.portability.CameraExceptionHandler;
149 import com.android.ex.camera2.portability.CameraSettings;
150 import com.bumptech.glide.Glide;
151 import com.bumptech.glide.GlideBuilder;
152 import com.bumptech.glide.MemoryCategory;
153 import com.bumptech.glide.load.DecodeFormat;
154 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
155 import com.bumptech.glide.load.engine.prefill.PreFillType;
156 import com.google.common.base.Optional;
157 import com.google.common.logging.eventprotos;
158 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
159 import com.google.common.logging.eventprotos.MediaInteraction;
160 import com.google.common.logging.eventprotos.NavigationChange;
163 import java.lang.ref.WeakReference;
164 import java.util.ArrayList;
165 import java.util.HashMap;
166 import java.util.List;
168 public class CameraActivity extends QuickActivity
169 implements AppController, CameraAgent.CameraOpenCallback,
170 ShareActionProvider.OnShareTargetSelectedListener {
172 private static final Log.Tag TAG = new Log.Tag("CameraActivity");
174 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
175 "android.media.action.STILL_IMAGE_CAMERA_SECURE";
176 public static final String ACTION_IMAGE_CAPTURE_SECURE =
177 "android.media.action.IMAGE_CAPTURE_SECURE";
179 // The intent extra for camera from secure lock screen. True if the gallery
180 // should only show newly captured pictures. sSecureAlbumId does not
181 // increment. This is used when switching between camera, camcorder, and
182 // panorama. If the extra is not set, it is in the normal camera mode.
183 public static final String SECURE_CAMERA_EXTRA = "secure_camera";
185 public static final String MODULE_SCOPE_PREFIX = "_preferences_module_";
186 public static final String CAMERA_SCOPE_PREFIX = "_preferences_camera_";
188 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
189 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
190 /** Load metadata for 10 items ahead of our current. */
191 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
193 /** Should be used wherever a context is needed. */
194 private Context mAppContext;
197 * Camera fatal error handling:
198 * 1) Present error dialog to guide users to exit the app.
199 * 2) If users hit home button, onPause should just call finish() to exit the app.
201 private boolean mCameraFatalError = false;
204 * Whether onResume should reset the view to the preview.
206 private boolean mResetToPreviewOnResume = true;
209 * This data adapter is used by FilmStripView.
211 private VideoItemFactory mVideoItemFactory;
212 private PhotoItemFactory mPhotoItemFactory;
213 private LocalFilmstripDataAdapter mDataAdapter;
215 private OneCameraManager mCameraManager;
216 private SettingsManager mSettingsManager;
217 private ResolutionSetting mResolutionSetting;
218 private ModeListView mModeListView;
219 private boolean mModeListVisible = false;
220 private int mCurrentModeIndex;
221 private CameraModule mCurrentModule;
222 private ModuleManagerImpl mModuleManager;
223 private FrameLayout mAboveFilmstripControlLayout;
224 private FilmstripController mFilmstripController;
225 private boolean mFilmstripVisible;
226 /** Whether the filmstrip fully covers the preview. */
227 private boolean mFilmstripCoversPreview = false;
228 private int mResultCodeForTesting;
229 private Intent mResultDataForTesting;
230 private OnScreenHint mStorageHint;
231 private final Object mStorageSpaceLock = new Object();
232 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
233 private boolean mAutoRotateScreen;
234 private boolean mSecureCamera;
235 private OrientationManagerImpl mOrientationManager;
236 private LocationManager mLocationManager;
237 private ButtonManager mButtonManager;
238 private Handler mMainHandler;
239 private PanoramaViewHelper mPanoramaViewHelper;
240 private ActionBar mActionBar;
241 private ViewGroup mUndoDeletionBar;
242 private boolean mIsUndoingDeletion = false;
243 private boolean mIsActivityRunning = false;
245 private final Uri[] mNfcPushUris = new Uri[1];
247 private FilmstripContentObserver mLocalImagesObserver;
248 private FilmstripContentObserver mLocalVideosObserver;
250 private boolean mPendingDeletion = false;
252 private CameraController mCameraController;
253 private boolean mPaused;
254 private CameraAppUI mCameraAppUI;
256 private Intent mGalleryIntent;
257 private long mOnCreateTime;
259 private Menu mActionBarMenu;
260 private Preloader<Integer, AsyncTask> mPreloader;
262 /** Can be used to play custom sounds. */
263 private SoundPlayer mSoundPlayer;
265 /** Holds configuration for various OneCamera features. */
266 private OneCameraFeatureConfig mFeatureConfig;
268 private static final int LIGHTS_OUT_DELAY_MS = 4000;
269 private final int BASE_SYS_UI_VISIBILITY =
270 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
271 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
272 private final Runnable mLightsOutRunnable = new Runnable() {
275 getWindow().getDecorView().setSystemUiVisibility(
276 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE);
279 private MemoryManager mMemoryManager;
280 private MotionManager mMotionManager;
281 private final Profiler mProfiler = Profilers.instance().guard();
283 /** First run dialog */
284 private FirstRunDialog mFirstRunDialog;
287 public CameraAppUI getCameraAppUI() {
292 public ModuleManager getModuleManager() {
293 return mModuleManager;
297 * Close activity when secure app passes lock screen or screen turns
300 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
302 public void onReceive(Context context, Intent intent) {
308 * Whether the screen is kept turned on.
310 private boolean mKeepScreenOn;
311 private int mLastLayoutOrientation;
312 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
313 new CameraAppUI.BottomPanel.Listener() {
316 * If the current photo is a photo sphere, this will launch the
317 * Photo Sphere panorama viewer.
320 public void onExternalViewer() {
321 if (mPanoramaViewHelper == null) {
324 final FilmstripItem data = getCurrentLocalData();
326 Log.w(TAG, "Cannot open null data.");
329 final Uri contentUri = data.getData().getUri();
330 if (contentUri == Uri.EMPTY) {
331 Log.w(TAG, "Cannot open empty URL.");
335 if (data.getMetadata().isUsePanoramaViewer()) {
336 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
337 } else if (data.getMetadata().isHasRgbzData()) {
338 mPanoramaViewHelper.showRgbz(contentUri);
339 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
340 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
341 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
342 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false);
343 mCameraAppUI.clearClingForViewer(
344 CameraAppUI.BottomPanel.VIEWER_REFOCUS);
350 public void onEdit() {
351 FilmstripItem data = getCurrentLocalData();
353 Log.w(TAG, "Cannot edit null data.");
356 final int currentDataId = getCurrentDataId();
357 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
359 MediaInteraction.InteractionType.EDIT,
360 NavigationChange.InteractionCause.BUTTON,
361 fileAgeFromAdapterAtIndex(currentDataId));
366 public void onTinyPlanet() {
367 FilmstripItem data = getCurrentLocalData();
369 Log.w(TAG, "Cannot edit tiny planet on null data.");
372 launchTinyPlanetEditor(data);
376 public void onDelete() {
377 final int currentDataId = getCurrentDataId();
378 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
380 MediaInteraction.InteractionType.DELETE,
381 NavigationChange.InteractionCause.BUTTON,
382 fileAgeFromAdapterAtIndex(currentDataId));
383 removeItemAt(currentDataId);
387 public void onShare() {
388 final FilmstripItem data = getCurrentLocalData();
390 Log.w(TAG, "Cannot share null data.");
394 final int currentDataId = getCurrentDataId();
395 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
397 MediaInteraction.InteractionType.SHARE,
398 NavigationChange.InteractionCause.BUTTON,
399 fileAgeFromAdapterAtIndex(currentDataId));
400 // If applicable, show release information before this item
402 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
403 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
404 new Callback<Void>() {
406 public void onCallback(Void result) {
415 private void share(FilmstripItem data) {
416 Intent shareIntent = getShareIntentByData(data);
417 if (shareIntent != null) {
419 launchActivityByIntent(shareIntent);
420 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
421 } catch (ActivityNotFoundException ex) {
427 private int getCurrentDataId() {
428 return mFilmstripController.getCurrentAdapterIndex();
431 private FilmstripItem getCurrentLocalData() {
432 return mDataAdapter.getItemAt(getCurrentDataId());
436 * Sets up the share intent and NFC properly according to the
439 * @param item The data to be shared.
441 private Intent getShareIntentByData(final FilmstripItem item) {
442 Intent intent = null;
443 final Uri contentUri = item.getData().getUri();
444 final String msgShareTo = getResources().getString(R.string.share_to);
446 if (item.getMetadata().isPanorama360() &&
447 item.getData().getUri() != Uri.EMPTY) {
448 intent = new Intent(Intent.ACTION_SEND);
449 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE);
450 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
451 } else if (item.getAttributes().canShare()) {
452 final String mimeType = item.getData().getMimeType();
453 intent = getShareIntentFromType(mimeType);
454 if (intent != null) {
455 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
456 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
458 intent = Intent.createChooser(intent, msgShareTo);
464 * Get the share intent according to the mimeType
466 * @param mimeType The mimeType of current data.
467 * @return the video/image's ShareIntent or null if mimeType is
470 private Intent getShareIntentFromType(String mimeType) {
471 // Lazily create the intent object.
472 Intent intent = new Intent(Intent.ACTION_SEND);
473 if (mimeType.startsWith("video/")) {
474 intent.setType("video/*");
476 if (mimeType.startsWith("image/")) {
477 intent.setType("image/*");
479 Log.w(TAG, "unsupported mimeType " + mimeType);
486 public void onProgressErrorClicked() {
487 FilmstripItem data = getCurrentLocalData();
488 getServices().getCaptureSessionManager().removeErrorMessage(
489 data.getData().getUri());
490 updateBottomControlsByData(data);
495 public void onCameraOpened(CameraAgent.CameraProxy camera) {
496 Log.v(TAG, "onCameraOpened");
498 // We've paused, but just asynchronously opened the camera. Close it
499 // because we should be releasing the camera when paused to allow
500 // other apps to access it.
501 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera");
502 mCameraController.closeCamera(false);
506 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
507 // We shouldn't be here. Just close the camera and leave.
508 mCameraController.closeCamera(false);
509 throw new IllegalStateException("Camera opened but the module shouldn't be " +
512 if (mCurrentModule != null) {
513 resetExposureCompensationToDefault(camera);
514 mCurrentModule.onCameraAvailable(camera);
516 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
518 Log.v(TAG, "invoking onChangeCamera");
519 mCameraAppUI.onChangeCamera();
522 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) {
523 // Reset the exposure compensation before handing the camera to module.
524 CameraSettings cameraSettings = camera.getSettings();
525 cameraSettings.setExposureCompensationIndex(0);
526 camera.applySettings(cameraSettings);
530 public void onCameraDisabled(int cameraId) {
531 UsageStatistics.instance().cameraFailure(
532 eventprotos.CameraFailure.FailureReason.SECURITY, null,
533 UsageStatistics.NONE, UsageStatistics.NONE);
534 Log.w(TAG, "Camera disabled: " + cameraId);
535 CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
539 public void onDeviceOpenFailure(int cameraId, String info) {
540 UsageStatistics.instance().cameraFailure(
541 eventprotos.CameraFailure.FailureReason.OPEN_FAILURE, info,
542 UsageStatistics.NONE, UsageStatistics.NONE);
543 Log.w(TAG, "Camera open failure: " + info);
544 CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
548 public void onDeviceOpenedAlready(int cameraId, String info) {
549 Log.w(TAG, "Camera open already: " + cameraId + "," + info);
550 CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
554 public void onReconnectionFailure(CameraAgent mgr, String info) {
555 UsageStatistics.instance().cameraFailure(
556 eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE, null,
557 UsageStatistics.NONE, UsageStatistics.NONE);
558 Log.w(TAG, "Camera reconnection failure:" + info);
559 CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
562 private static class MainHandler extends Handler {
563 final WeakReference<CameraActivity> mActivity;
565 public MainHandler(CameraActivity activity, Looper looper) {
567 mActivity = new WeakReference<CameraActivity>(activity);
571 public void handleMessage(Message msg) {
572 CameraActivity activity = mActivity.get();
573 if (activity == null) {
578 case MSG_CLEAR_SCREEN_ON_FLAG: {
579 if (!activity.mPaused) {
580 activity.getWindow().clearFlags(
581 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
589 private String fileNameFromAdapterAtIndex(int index) {
590 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
591 if (filmstripItem == null) {
595 File localFile = new File(filmstripItem.getData().getFilePath());
596 return localFile.getName();
599 private float fileAgeFromAdapterAtIndex(int index) {
600 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
601 if (filmstripItem == null) {
605 File localFile = new File(filmstripItem.getData().getFilePath());
606 return 0.001f * (System.currentTimeMillis() - localFile.lastModified());
609 private final FilmstripContentPanel.Listener mFilmstripListener =
610 new FilmstripContentPanel.Listener() {
613 public void onSwipeOut() {
617 public void onSwipeOutBegin() {
619 mCameraAppUI.hideBottomControls();
620 mFilmstripCoversPreview = false;
621 updatePreviewVisibility();
625 public void onFilmstripHidden() {
626 mFilmstripVisible = false;
627 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
628 NavigationChange.InteractionCause.SWIPE_RIGHT);
629 CameraActivity.this.setFilmstripUiVisibility(false);
630 // When the user hide the filmstrip (either swipe out or
631 // tap on back key) we move to the first item so next time
632 // when the user swipe in the filmstrip, the most recent
634 mFilmstripController.goToFirstItem();
638 public void onFilmstripShown() {
639 mFilmstripVisible = true;
640 mCameraAppUI.hideCaptureIndicator();
641 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
642 NavigationChange.InteractionCause.SWIPE_LEFT);
643 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
647 public void onFocusedDataLongPressed(int adapterIndex) {
652 public void onFocusedDataPromoted(int adapterIndex) {
653 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
655 MediaInteraction.InteractionType.DELETE,
656 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex(
658 removeItemAt(adapterIndex);
662 public void onFocusedDataDemoted(int adapterIndex) {
663 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
665 MediaInteraction.InteractionType.DELETE,
666 NavigationChange.InteractionCause.SWIPE_DOWN,
667 fileAgeFromAdapterAtIndex(adapterIndex));
668 removeItemAt(adapterIndex);
672 public void onEnterFullScreenUiShown(int adapterIndex) {
673 if (mFilmstripVisible) {
674 CameraActivity.this.setFilmstripUiVisibility(true);
679 public void onLeaveFullScreenUiShown(int adapterIndex) {
684 public void onEnterFullScreenUiHidden(int adapterIndex) {
685 if (mFilmstripVisible) {
686 CameraActivity.this.setFilmstripUiVisibility(false);
691 public void onLeaveFullScreenUiHidden(int adapterIndex) {
696 public void onEnterFilmstrip(int adapterIndex) {
697 if (mFilmstripVisible) {
698 CameraActivity.this.setFilmstripUiVisibility(true);
703 public void onLeaveFilmstrip(int adapterIndex) {
708 public void onDataReloaded() {
709 if (!mFilmstripVisible) {
712 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
716 public void onDataUpdated(int adapterIndex) {
717 if (!mFilmstripVisible) {
720 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
724 public void onEnterZoomView(int adapterIndex) {
725 if (mFilmstripVisible) {
726 CameraActivity.this.setFilmstripUiVisibility(false);
731 public void onZoomAtIndexChanged(int adapterIndex, float zoom) {
732 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex);
733 long ageMillis = System.currentTimeMillis()
734 - filmstripItem.getData().getLastModifiedDate().getTime();
736 // Do not log if items is to old or does not have a path (which is
737 // being used as a key).
738 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) ||
739 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) {
742 File localFile = new File(filmstripItem.getData().getFilePath());
743 UsageStatistics.instance().mediaView(localFile.getName(),
744 filmstripItem.getData().getLastModifiedDate().getTime(), zoom);
748 public void onDataFocusChanged(final int prevIndex, final int newIndex) {
749 if (!mFilmstripVisible) {
752 // TODO: This callback is UI event callback, should always
753 // happen on UI thread. Find the reason for this
754 // runOnUiThread() and fix it.
755 runOnUiThread(new Runnable() {
758 updateUiByData(newIndex);
764 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
765 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
769 private final FilmstripItemListener mFilmstripItemListener =
770 new FilmstripItemListener() {
772 public void onMetadataUpdated(List<Integer> indexes) {
774 // Callback after the activity is paused.
777 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
778 for (Integer index : indexes) {
779 if (index == currentIndex) {
780 updateBottomControlsByData(mDataAdapter.getItemAt(index));
781 // Currently we have only 1 data can be matched.
782 // No need to look for more, break.
789 public void gotoGallery() {
790 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
791 NavigationChange.InteractionCause.BUTTON);
793 mFilmstripController.goToNextItem();
797 * If 'visible' is false, this hides the action bar. Also maintains
798 * lights-out at all times.
800 * @param visible is false, this hides the action bar and filmstrip bottom
803 private void setFilmstripUiVisibility(boolean visible) {
804 mLightsOutRunnable.run();
805 mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
806 if (visible != mActionBar.isShowing()) {
809 mCameraAppUI.showBottomControls();
812 mCameraAppUI.hideBottomControls();
815 mFilmstripCoversPreview = visible;
816 updatePreviewVisibility();
819 private void hideSessionProgress() {
820 mCameraAppUI.getFilmstripBottomControls().hideProgress();
823 private void showSessionProgress(CharSequence message) {
824 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
825 controls.setProgressText(message);
826 controls.hideControls();
827 controls.hideProgressError();
828 controls.showProgress();
831 private void showProcessError(CharSequence message) {
832 mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
835 private void updateSessionProgress(int progress) {
836 mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
839 private void updateSessionProgressText(CharSequence message) {
840 mCameraAppUI.getFilmstripBottomControls().setProgressText(message);
843 private void setupNfcBeamPush() {
844 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
845 if (adapter == null) {
849 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
851 adapter.setNdefPushMessage(null, CameraActivity.this);
855 adapter.setBeamPushUris(null, CameraActivity.this);
856 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
858 public Uri[] createBeamUris(NfcEvent event) {
861 }, CameraActivity.this);
865 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
866 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
867 if (currentIndex < 0) {
870 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex),
871 MediaInteraction.InteractionType.SHARE,
872 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex));
873 // TODO add intent.getComponent().getPackageName()
877 // Note: All callbacks come back on the main thread.
878 private final SessionListener mSessionListener =
879 new SessionListener() {
881 public void onSessionQueued(final Uri uri) {
882 Log.v(TAG, "onSessionQueued: " + uri);
883 if (!Storage.isSessionUri(uri)) {
886 SessionItem newData = new SessionItem(getApplicationContext(), uri);
887 mDataAdapter.addOrUpdate(newData);
891 public void onSessionUpdated(Uri uri) {
892 Log.v(TAG, "onSessionUpdated: " + uri);
893 mDataAdapter.refresh(uri);
897 public void onSessionDone(final Uri sessionUri) {
898 Log.v(TAG, "onSessionDone:" + sessionUri);
899 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
900 if (contentUri == null) {
901 mDataAdapter.refresh(sessionUri);
904 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri);
906 // This can be null if e.g. a session is canceled (e.g.
907 // through discard panorama). It might be worth adding
908 // onSessionCanceled or the like this interface.
909 if (newData == null) {
910 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri);
914 // Make the PhotoItem aware of the session placeholder, to
915 // allow it to make a smooth transition to its content.
916 newData.setSessionPlaceholderBitmap(
917 Storage.getPlaceholderForSession(sessionUri));
919 final int pos = mDataAdapter.findByContentUri(sessionUri);
921 // We do not have a placeholder for this image, perhaps
922 // due to the activity crashing or being killed.
923 mDataAdapter.addOrUpdate(newData);
925 mDataAdapter.updateItemAt(pos, newData);
930 public void onSessionProgress(final Uri uri, final int progress) {
932 // Do nothing, there is no task for this URI.
935 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
936 if (currentIndex == -1) {
940 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
941 updateSessionProgress(progress);
946 public void onSessionProgressText(final Uri uri, final CharSequence message) {
947 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
948 if (currentIndex == -1) {
952 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
953 updateSessionProgressText(message);
958 public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
959 // Don't show capture indicator in Photo Sphere.
960 final int photosphereModuleId = getApplicationContext().getResources()
962 R.integer.camera_mode_photosphere);
963 if (mCurrentModeIndex == photosphereModuleId) {
966 indicateCapture(indicator, rotationDegrees);
970 public void onSessionFailed(Uri uri, CharSequence reason) {
971 Log.v(TAG, "onSessionFailed:" + uri);
973 int failedIndex = mDataAdapter.findByContentUri(uri);
974 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
976 if (currentIndex == failedIndex) {
977 updateSessionProgress(0);
978 showProcessError(reason);
980 if (reason.equals("content")) {
981 UsageStatistics.instance().storageWarning(Storage.ACCESS_FAILURE);
982 CameraUtil.showError(CameraActivity.this, R.string.media_storage_failure,
983 R.string.feedback_description_save_photo, false);
987 mDataAdapter.refresh(uri);
991 public void onSessionThumbnailUpdate(Bitmap bitmap) {
995 public void onSessionPictureDataUpdate(byte[] pictureData, int orientation) {
1000 public Context getAndroidContext() {
1005 public OneCameraFeatureConfig getCameraFeatureConfig() {
1006 return mFeatureConfig;
1010 public Dialog createDialog() {
1011 return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
1015 public void launchActivityByIntent(Intent intent) {
1016 // Starting from L, we prefer not to start edit activity within camera's task.
1017 mResetToPreviewOnResume = false;
1018 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
1020 startActivity(intent);
1024 public int getCurrentModuleIndex() {
1025 return mCurrentModeIndex;
1029 public int getCurrentCameraId() {
1030 return mCameraController.getCurrentCameraId();
1034 public String getModuleScope() {
1035 ModuleAgent agent = mModuleManager.getModuleAgent(mCurrentModeIndex);
1036 return MODULE_SCOPE_PREFIX + agent.getScopeNamespace();
1040 public String getCameraScope() {
1041 int currentCameraId = getCurrentCameraId();
1042 if (currentCameraId < 0) {
1043 // if an unopen camera i.e. negative ID is returned, which we've observed in
1044 // some automated scenarios, just return it as a valid separate scope
1045 // this could cause user issues, so log a stack trace noting the call path
1046 // which resulted in this scenario.
1047 Log.w(TAG, "getting camera scope with no open camera, using id: " + currentCameraId);
1049 return CAMERA_SCOPE_PREFIX + Integer.toString(currentCameraId);
1053 public ModuleController getCurrentModuleController() {
1054 return mCurrentModule;
1058 public int getQuickSwitchToModuleId(int currentModuleIndex) {
1059 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
1064 public SurfaceTexture getPreviewBuffer() {
1065 // TODO: implement this
1070 public void onPreviewReadyToStart() {
1071 mCameraAppUI.onPreviewReadyToStart();
1075 public void onPreviewStarted() {
1076 mCameraAppUI.onPreviewStarted();
1080 public void addPreviewAreaSizeChangedListener(
1081 PreviewStatusListener.PreviewAreaChangedListener listener) {
1082 mCameraAppUI.addPreviewAreaChangedListener(listener);
1086 public void removePreviewAreaSizeChangedListener(
1087 PreviewStatusListener.PreviewAreaChangedListener listener) {
1088 mCameraAppUI.removePreviewAreaChangedListener(listener);
1092 public void setupOneShotPreviewListener() {
1093 mCameraController.setOneShotPreviewCallback(mMainHandler,
1094 new CameraAgent.CameraPreviewDataCallback() {
1096 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) {
1097 mCurrentModule.onPreviewInitialDataReceived();
1098 mCameraAppUI.onNewPreviewFrame();
1105 public void updatePreviewAspectRatio(float aspectRatio) {
1106 mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
1110 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
1111 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio);
1115 public RectF getFullscreenRect() {
1116 return mCameraAppUI.getFullscreenRect();
1120 public void updatePreviewTransform(Matrix matrix) {
1121 mCameraAppUI.updatePreviewTransform(matrix);
1125 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1126 mCameraAppUI.setPreviewStatusListener(previewStatusListener);
1130 public FrameLayout getModuleLayoutRoot() {
1131 return mCameraAppUI.getModuleRootView();
1135 public void setShutterEventsListener(ShutterEventsListener listener) {
1136 // TODO: implement this
1140 public void setShutterEnabled(boolean enabled) {
1141 mCameraAppUI.setShutterButtonEnabled(enabled);
1145 public boolean isShutterEnabled() {
1146 return mCameraAppUI.isShutterButtonEnabled();
1150 public void startFlashAnimation(boolean shortFlash) {
1151 mCameraAppUI.startFlashAnimation(shortFlash);
1155 public void startPreCaptureAnimation() {
1156 // TODO: implement this
1160 public void cancelPreCaptureAnimation() {
1161 // TODO: implement this
1165 public void startPostCaptureAnimation() {
1166 // TODO: implement this
1170 public void startPostCaptureAnimation(Bitmap thumbnail) {
1171 // TODO: implement this
1175 public void cancelPostCaptureAnimation() {
1176 // TODO: implement this
1180 public OrientationManager getOrientationManager() {
1181 return mOrientationManager;
1185 public LocationManager getLocationManager() {
1186 return mLocationManager;
1190 public void lockOrientation() {
1191 if (mOrientationManager != null) {
1192 mOrientationManager.lockOrientation();
1197 public void unlockOrientation() {
1198 if (mOrientationManager != null) {
1199 mOrientationManager.unlockOrientation();
1204 * If not in filmstrip, this shows the capture indicator.
1206 private void indicateCapture(final Bitmap indicator, final int rotationDegrees) {
1207 if (mFilmstripVisible) {
1211 // Don't show capture indicator in Photo Sphere.
1212 // TODO: Don't reach into resources to figure out the current mode.
1213 final int photosphereModuleId = getApplicationContext().getResources().getInteger(
1214 R.integer.camera_mode_photosphere);
1215 if (mCurrentModeIndex == photosphereModuleId) {
1219 mMainHandler.post(new Runnable() {
1222 mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule
1223 .getPeekAccessibilityString());
1224 mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees);
1230 public void notifyNewMedia(Uri uri) {
1231 // TODO: This method is running on the main thread. Also we should get
1232 // rid of that AsyncTask.
1234 updateStorageSpaceAndHint(null);
1235 ContentResolver cr = getContentResolver();
1236 String mimeType = cr.getType(uri);
1237 FilmstripItem newData = null;
1238 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
1239 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1240 newData = mVideoItemFactory.queryContentUri(uri);
1241 if (newData == null) {
1242 Log.e(TAG, "Can't find video data in content resolver:" + uri);
1245 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
1246 CameraUtil.broadcastNewPicture(mAppContext, uri);
1247 newData = mPhotoItemFactory.queryContentUri(uri);
1248 if (newData == null) {
1249 Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1253 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1257 // We are preloading the metadata for new video since we need the
1258 // rotation info for the thumbnail.
1259 new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
1261 protected FilmstripItem doInBackground(FilmstripItem... params) {
1262 FilmstripItem data = params[0];
1263 MetadataLoader.loadMetadata(getAndroidContext(), data);
1268 protected void onPostExecute(final FilmstripItem data) {
1269 // TODO: Figure out why sometimes the data is aleady there.
1270 mDataAdapter.addOrUpdate(data);
1272 // Legacy modules don't use CaptureSession, so we show the capture indicator when
1273 // the item was safed.
1274 if (mCurrentModule instanceof PhotoModule ||
1275 mCurrentModule instanceof VideoModule) {
1276 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1279 final Optional<Bitmap> bitmap = data.generateThumbnail(
1280 mAboveFilmstripControlLayout.getWidth(),
1281 mAboveFilmstripControlLayout.getMeasuredHeight());
1282 if (bitmap.isPresent()) {
1283 indicateCapture(bitmap.get(), 0);
1289 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
1293 public void enableKeepScreenOn(boolean enabled) {
1298 mKeepScreenOn = enabled;
1299 if (mKeepScreenOn) {
1300 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1301 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1303 keepScreenOnForAWhile();
1308 public CameraProvider getCameraProvider() {
1309 return mCameraController;
1313 public OneCameraManager getCameraManager() {
1314 return mCameraManager;
1317 private void removeItemAt(int index) {
1318 mDataAdapter.removeAt(index);
1319 if (mDataAdapter.getTotalNumber() > 1) {
1320 showUndoDeletionBar();
1322 // If camera preview is the only view left in filmstrip,
1323 // no need to show undo bar.
1324 mPendingDeletion = true;
1326 if (mFilmstripVisible) {
1327 mCameraAppUI.getFilmstripContentPanel().animateHide();
1333 public boolean onOptionsItemSelected(MenuItem item) {
1334 // Handle presses on the action bar items
1335 switch (item.getItemId()) {
1336 case android.R.id.home:
1339 case R.id.action_details:
1340 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex());
1342 case R.id.action_help_and_feedback:
1343 mResetToPreviewOnResume = false;
1344 new GoogleHelpHelper(this).launchGoogleHelp();
1347 return super.onOptionsItemSelected(item);
1351 private boolean isCaptureIntent() {
1352 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1353 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1354 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1362 * Note: Make sure this callback is unregistered properly when the activity
1363 * is destroyed since we're otherwise leaking the Activity reference.
1365 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
1366 = new CameraExceptionHandler.CameraExceptionCallback() {
1368 public void onCameraError(int errorCode) {
1369 // Not a fatal error. only do Log.e().
1370 Log.e(TAG, "Camera error callback. error=" + errorCode);
1373 public void onCameraException(
1374 RuntimeException ex, String commandHistory, int action, int state) {
1375 Log.e(TAG, "Camera Exception", ex);
1376 UsageStatistics.instance().cameraFailure(
1377 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
1378 commandHistory, action, state);
1382 public void onDispatchThreadException(RuntimeException ex) {
1383 Log.e(TAG, "DispatchThread Exception", ex);
1384 UsageStatistics.instance().cameraFailure(
1385 eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
1386 null, UsageStatistics.NONE, UsageStatistics.NONE);
1389 private void onFatalError() {
1390 if (mCameraFatalError) {
1393 mCameraFatalError = true;
1395 // If the activity receives exception during onPause, just exit the app.
1396 if (mPaused && !isFinishing()) {
1397 Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
1400 CameraUtil.showError(CameraActivity.this, R.string.camera_disabled,
1401 R.string.feedback_description_camera_access, true);
1407 public void onNewIntentTasks(Intent intent) {
1408 onModeSelected(getModeIndex());
1412 public void onCreateTasks(Bundle state) {
1413 Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start();
1414 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1415 mOnCreateTime = System.currentTimeMillis();
1416 mAppContext = getApplicationContext();
1417 mMainHandler = new MainHandler(this, getMainLooper());
1418 mLocationManager = new LocationManager(mAppContext);
1419 mOrientationManager = new OrientationManagerImpl(this, mMainHandler);
1420 mSettingsManager = getServices().getSettingsManager();
1421 mSoundPlayer = new SoundPlayer(mAppContext);
1422 mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver(),
1423 getServices().getMemoryManager());
1426 if (!Glide.isSetup()) {
1427 Context context = getAndroidContext();
1428 Glide.setup(new GlideBuilder(context)
1429 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888)
1430 .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
1432 Glide glide = Glide.get(context);
1434 // As a camera we will use a large amount of memory
1435 // for displaying images.
1436 glide.setMemoryCategory(MemoryCategory.HIGH);
1438 // Prefill glides bitmap pool to prevent excessive jank
1439 // when loading large images.
1440 glide.preFillBitmapPool(
1441 new PreFillType.Builder(GlideFilmstripManager.MAXIMUM_TEXTURE_SIZE)
1443 // It's more important for jank and GC to have
1444 // A larger weight of max texture size images than
1445 // media store sized images.
1446 new PreFillType.Builder(
1447 GlideFilmstripManager.MEDIASTORE_THUMB_WIDTH,
1448 GlideFilmstripManager.MEDIASTORE_THUMB_HEIGHT));
1450 profile.mark("Glide.setup");
1452 mCameraManager = OneCameraManager.get(
1453 mFeatureConfig, mAppContext, ResolutionUtil.getDisplayMetrics(this));
1454 } catch (OneCameraException e) {
1455 // Log error and continue. Modules requiring OneCamera should check
1456 // and handle if null by showing error dialog or other treatment.
1457 Log.e(TAG, "Creating camera manager failed.", e);
1458 CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
1460 profile.mark("OneCameraManager.get");
1461 mCameraController = new CameraController(mAppContext, this, mMainHandler,
1462 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1463 CameraAgentFactory.CameraApi.API_1),
1464 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1465 CameraAgentFactory.CameraApi.AUTO));
1466 mCameraController.setCameraExceptionHandler(
1467 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
1469 // TODO: Try to move all the resources allocation to happen as soon as
1470 // possible so we can call module.init() at the earliest time.
1471 mModuleManager = new ModuleManagerImpl();
1473 ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig);
1475 AppUpgrader appUpgrader = new AppUpgrader(this);
1476 appUpgrader.upgrade(mSettingsManager);
1477 Keys.setDefaults(mSettingsManager, mAppContext);
1479 mResolutionSetting = new ResolutionSetting(mSettingsManager, mCameraManager);
1481 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1482 // We suppress this flag via theme when drawing the system preview
1483 // background, but once we create activity here, reactivate to the
1484 // default value. The default is important for L, we don't want to
1485 // change app behavior, just starting background drawable layout.
1486 if (ApiHelper.isLOrHigher()) {
1487 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1491 setContentView(R.layout.activity_main);
1492 profile.mark("setContentView()");
1493 // A window background is set in styles.xml for the system to show a
1494 // drawable background with gray color and camera icon before the
1495 // activity is created. We set the background to null here to prevent
1496 // overdraw, all views must take care of drawing backgrounds if
1497 // necessary. This call to setBackgroundDrawable must occur after
1498 // setContentView, otherwise a background may be set again from the
1500 getWindow().setBackgroundDrawable(null);
1502 mActionBar = getActionBar();
1503 // set actionbar background to 100% or 50% transparent
1504 if (ApiHelper.isLOrHigher()) {
1505 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000));
1507 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
1510 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1511 mModeListView.init(mModuleManager.getSupportedModeIndexList());
1512 if (ApiHelper.HAS_ROTATION_ANIMATION) {
1513 setRotationAnimation();
1515 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1517 public void onVisibilityChanged(boolean visible) {
1518 mModeListVisible = visible;
1519 mCameraAppUI.setShutterButtonImportantToA11y(!visible);
1520 updatePreviewVisibility();
1524 // Check if this is in the secure camera mode.
1525 Intent intent = getIntent();
1526 String action = intent.getAction();
1527 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1528 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1529 mSecureCamera = true;
1531 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1534 if (mSecureCamera) {
1535 // Change the window flags so that secure camera can show when
1537 Window win = getWindow();
1538 WindowManager.LayoutParams params = win.getAttributes();
1539 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1540 win.setAttributes(params);
1542 // Filter for screen off so that we can finish secure camera
1543 // activity when screen is off.
1544 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1545 registerReceiver(mShutdownReceiver, filter_screen_off);
1547 // Filter for phone unlock so that we can finish secure camera
1548 // via this UI path:
1549 // 1. from secure lock screen, user starts secure camera
1550 // 2. user presses home button
1551 // 3. user unlocks phone
1552 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
1553 registerReceiver(mShutdownReceiver, filter_user_unlock);
1555 mCameraAppUI = new CameraAppUI(this,
1556 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1558 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1560 mAboveFilmstripControlLayout =
1561 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1563 // Add the session listener so we can track the session progress
1565 getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1566 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1567 mFilmstripController.setImageGap(
1568 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1569 profile.mark("Configure Camera UI");
1571 mPanoramaViewHelper = new PanoramaViewHelper(this);
1572 mPanoramaViewHelper.onCreate();
1574 ContentResolver appContentResolver = mAppContext.getContentResolver();
1575 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
1576 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
1577 new PhotoDataFactory());
1578 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
1579 new VideoDataFactory());
1580 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
1581 mPhotoItemFactory, mVideoItemFactory);
1582 mDataAdapter.setLocalDataListener(mFilmstripItemListener);
1584 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1587 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1588 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1589 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1590 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1593 setModuleFromModeIndex(getModeIndex());
1596 mCameraAppUI.prepareModuleUI();
1597 profile.mark("Init Current Module UI");
1598 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1599 profile.mark("Init CurrentModule");
1601 if (!mSecureCamera) {
1602 mFilmstripController.setDataAdapter(mDataAdapter);
1603 if (!isCaptureIntent()) {
1604 mDataAdapter.requestLoad(new Callback<Void>() {
1606 public void onCallback(Void result) {
1607 fillTemporarySessions();
1612 // Put a lock placeholder as the last image by setting its date to
1614 ImageView v = (ImageView) getLayoutInflater().inflate(
1615 R.layout.secure_album_placeholder, null);
1616 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
1617 v.setOnClickListener(new View.OnClickListener() {
1619 public void onClick(View view) {
1620 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1621 NavigationChange.InteractionCause.BUTTON);
1626 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1627 mDataAdapter = new FixedLastProxyAdapter(
1630 new PlaceholderItem(
1632 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
1633 v.getDrawable().getIntrinsicWidth(),
1634 v.getDrawable().getIntrinsicHeight()));
1635 // Flush out all the original data.
1636 mDataAdapter.clear();
1637 mFilmstripController.setDataAdapter(mDataAdapter);
1642 mLocalImagesObserver = new FilmstripContentObserver();
1643 mLocalVideosObserver = new FilmstripContentObserver();
1645 getContentResolver().registerContentObserver(
1646 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1647 mLocalImagesObserver);
1648 getContentResolver().registerContentObserver(
1649 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1650 mLocalVideosObserver);
1652 mMemoryManager = getServices().getMemoryManager();
1654 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1657 HashMap memoryData = mMemoryManager.queryMemory();
1658 UsageStatistics.instance().reportMemoryConsumed(memoryData,
1659 MemoryQuery.REPORT_LABEL_LAUNCH);
1663 mMotionManager = getServices().getMotionManager();
1665 mFirstRunDialog = new FirstRunDialog(this, new FirstRunDialog.FirstRunDialogListener() {
1667 public void onFirstRunStateReady() {
1668 // Run normal resume tasks.
1673 public void onFirstRunDialogCancelled() {
1674 // App isn't functional until users finish first run dialog.
1675 // We need to finish here since users hit back button during
1676 // first run dialog (b/19593942).
1681 public void onCameraAccessException() {
1682 CameraUtil.showError(CameraActivity.this, R.string.camera_disabled,
1683 R.string.feedback_description_camera_access, true);
1690 * Get the current mode index from the Intent or from persistent
1693 private int getModeIndex() {
1695 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1696 int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1697 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1698 int captureIntentIndex =
1699 getResources().getInteger(R.integer.camera_mode_capture_intent);
1700 String intentAction = getIntent().getAction();
1701 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction)
1702 || MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) {
1703 modeIndex = videoIndex;
1704 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction)
1705 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1707 modeIndex = captureIntentIndex;
1708 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction)
1709 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction)
1710 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1711 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1712 Keys.KEY_CAMERA_MODULE_LAST_USED);
1714 // For upgraders who have not seen the aspect ratio selection screen,
1715 // we need to drop them back in the photo module and have them select
1717 // TODO: Move this to SettingsManager as an upgrade procedure.
1718 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1719 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
1720 modeIndex = photoIndex;
1723 // If the activity has not been started using an explicit intent,
1724 // read the module index from the last time the user changed modes
1725 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1726 Keys.KEY_STARTUP_MODULE_INDEX);
1727 if ((modeIndex == gcamIndex &&
1728 !GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) || modeIndex < 0) {
1729 modeIndex = photoIndex;
1736 * Call this whenever the mode drawer or filmstrip change the visibility
1739 private void updatePreviewVisibility() {
1740 if (mCurrentModule == null) {
1744 int visibility = getPreviewVisibility();
1745 mCameraAppUI.onPreviewVisiblityChanged(visibility);
1746 updatePreviewRendering(visibility);
1747 mCurrentModule.onPreviewVisibilityChanged(visibility);
1750 private void updatePreviewRendering(int visibility) {
1751 if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1752 mCameraAppUI.pausePreviewRendering();
1754 mCameraAppUI.resumePreviewRendering();
1758 private int getPreviewVisibility() {
1759 if (mFilmstripCoversPreview) {
1760 return ModuleController.VISIBILITY_HIDDEN;
1761 } else if (mModeListVisible){
1762 return ModuleController.VISIBILITY_COVERED;
1764 return ModuleController.VISIBILITY_VISIBLE;
1768 private void setRotationAnimation() {
1769 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1770 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1771 Window win = getWindow();
1772 WindowManager.LayoutParams winParams = win.getAttributes();
1773 winParams.rotationAnimation = rotationAnimation;
1774 win.setAttributes(winParams);
1778 public void onUserInteraction() {
1779 super.onUserInteraction();
1780 if (!isFinishing()) {
1781 keepScreenOnForAWhile();
1786 public boolean dispatchTouchEvent(MotionEvent ev) {
1787 boolean result = super.dispatchTouchEvent(ev);
1788 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1789 // Real deletion is postponed until the next user interaction after
1790 // the gesture that triggers deletion. Until real deletion is
1791 // performed, users can click the undo button to bring back the
1792 // image that they chose to delete.
1793 if (mPendingDeletion && !mIsUndoingDeletion) {
1801 public void onPauseTasks() {
1802 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1803 Profile profile = mProfiler.create("CameraActivity.onPause").start();
1806 * Save the last module index after all secure camera and icon launches,
1807 * not just on mode switches.
1809 * Right now we exclude capture intents from this logic, because we also
1810 * ignore the cross-Activity recovery logic in onStart for capture intents.
1812 if (!isCaptureIntent()) {
1813 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
1814 Keys.KEY_STARTUP_MODULE_INDEX,
1819 mCameraAppUI.hideCaptureIndicator();
1820 mFirstRunDialog.dismiss();
1822 // Delete photos that are pending deletion
1824 mCurrentModule.pause();
1825 mOrientationManager.pause();
1826 mPanoramaViewHelper.onPause();
1828 mLocalImagesObserver.setForegroundChangeListener(null);
1829 mLocalImagesObserver.setActivityPaused(true);
1830 mLocalVideosObserver.setActivityPaused(true);
1831 mPreloader.cancelAllLoads();
1834 mMotionManager.stop();
1836 // Always stop recording location when paused. Resume will start
1837 // location recording again if the location setting is on.
1838 mLocationManager.recordLocation(false);
1840 UsageStatistics.instance().backgrounded();
1842 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
1843 // button. Let's just kill the process.
1844 if (mCameraFatalError && !isFinishing()) {
1845 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
1848 // Close the camera and wait for the operation done.
1849 Log.v(TAG, "onPause closing camera");
1850 mCameraController.closeCamera(true);
1857 public void onResumeTasks() {
1860 // Show the dialog if necessary. The rest resume logic will be invoked
1861 // at the onFirstRunStateReady() callback.
1862 mFirstRunDialog.showIfNecessary();
1865 private void resume() {
1866 Profile profile = mProfiler.create("CameraActivity.resume").start();
1867 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1868 Log.v(TAG, "Build info: " + Build.DISPLAY);
1870 updateStorageSpaceAndHint(null);
1872 mLastLayoutOrientation = getResources().getConfiguration().orientation;
1874 // TODO: Handle this in OrientationManager.
1876 if (Settings.System.getInt(getContentResolver(),
1877 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1878 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1879 mAutoRotateScreen = false;
1881 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1882 mAutoRotateScreen = true;
1885 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and
1886 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
1887 // lockscreen onResume->onPause->onResume sequence.
1889 String action = getIntent().getAction();
1890 if (action == null) {
1891 source = ForegroundSource.UNKNOWN_SOURCE;
1894 case MediaStore.ACTION_IMAGE_CAPTURE:
1895 source = ForegroundSource.ACTION_IMAGE_CAPTURE;
1897 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
1898 // was UNKNOWN_SOURCE in Fishlake.
1899 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
1901 case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
1902 // was UNKNOWN_SOURCE in Fishlake.
1903 source = ForegroundSource.ACTION_VIDEO_CAMERA;
1905 case MediaStore.ACTION_VIDEO_CAPTURE:
1906 source = ForegroundSource.ACTION_VIDEO_CAPTURE;
1908 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
1909 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
1910 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
1912 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
1913 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
1915 case Intent.ACTION_MAIN:
1916 source = ForegroundSource.ACTION_MAIN;
1919 source = ForegroundSource.UNKNOWN_SOURCE;
1923 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode());
1925 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
1926 if (ApiHelper.isLOrHigher()) {
1927 // hide the up affordance for L devices, it's not very Materially
1928 mActionBar.setDisplayShowHomeEnabled(false);
1931 mOrientationManager.resume();
1933 mCurrentModule.hardResetSettings(mSettingsManager);
1936 mCurrentModule.resume();
1937 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
1938 NavigationChange.InteractionCause.BUTTON);
1939 setSwipingEnabled(true);
1940 profile.mark("mCurrentModule.resume");
1942 if (!mResetToPreviewOnResume) {
1943 FilmstripItem item = mDataAdapter.getItemAt(
1944 mFilmstripController.getCurrentAdapterIndex());
1946 mDataAdapter.refresh(item.getData().getUri());
1950 // The share button might be disabled to avoid double tapping.
1951 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1952 // Default is showing the preview, unless disabled by explicitly
1953 // starting an activity we want to return from to the filmstrip rather
1954 // than the preview.
1955 mResetToPreviewOnResume = true;
1957 if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1958 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1959 if (!mSecureCamera) {
1960 // If it's secure camera, requestLoad() should not be called
1961 // as it will load all the data.
1962 if (!mFilmstripVisible) {
1963 mDataAdapter.requestLoad(new Callback<Void>() {
1965 public void onCallback(Void result) {
1966 fillTemporarySessions();
1970 mDataAdapter.requestLoadNewPhotos();
1974 mLocalImagesObserver.setActivityPaused(false);
1975 mLocalVideosObserver.setActivityPaused(false);
1976 if (!mSecureCamera) {
1977 mLocalImagesObserver.setForegroundChangeListener(
1978 new FilmstripContentObserver.ChangeListener() {
1980 public void onChange() {
1981 mDataAdapter.requestLoadNewPhotos();
1986 keepScreenOnForAWhile();
1988 // Lights-out mode at all times.
1989 final View rootView = findViewById(R.id.activity_root_view);
1990 mLightsOutRunnable.run();
1991 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
1992 new OnSystemUiVisibilityChangeListener() {
1994 public void onSystemUiVisibilityChange(int visibility) {
1995 mMainHandler.removeCallbacks(mLightsOutRunnable);
1996 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
2001 mPanoramaViewHelper.onResume();
2002 profile.mark("mPanoramaViewHelper.onResume()");
2004 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
2005 // Enable location recording if the setting is on.
2006 final boolean locationRecordingEnabled =
2007 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
2008 mLocationManager.recordLocation(locationRecordingEnabled);
2010 final int previewVisibility = getPreviewVisibility();
2011 updatePreviewRendering(previewVisibility);
2013 mMotionManager.start();
2017 private void fillTemporarySessions() {
2018 if (mSecureCamera) {
2021 // There might be sessions still in flight (processed by our service).
2022 // Make sure they're added to the filmstrip.
2023 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
2027 public void onStartTasks() {
2028 mIsActivityRunning = true;
2029 mPanoramaViewHelper.onStart();
2032 * If we're starting after launching a different Activity (lockscreen),
2033 * we need to use the last mode used in the other Activity, and
2034 * not the old one from this Activity.
2036 * This needs to happen before CameraAppUI.resume() in order to set the
2037 * mode cover icon to the actual last mode used.
2039 * Right now we exclude capture intents from this logic.
2041 int modeIndex = getModeIndex();
2042 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
2043 onModeSelected(modeIndex);
2046 if (mResetToPreviewOnResume) {
2047 mCameraAppUI.resume();
2048 mResetToPreviewOnResume = false;
2053 protected void onStopTasks() {
2054 mIsActivityRunning = false;
2055 mPanoramaViewHelper.onStop();
2057 mLocationManager.disconnect();
2061 public void onDestroyTasks() {
2062 if (mSecureCamera) {
2063 unregisterReceiver(mShutdownReceiver);
2066 mSettingsManager.removeAllListeners();
2067 mCameraController.removeCallbackReceiver();
2068 mCameraController.setCameraExceptionHandler(null);
2069 getContentResolver().unregisterContentObserver(mLocalImagesObserver);
2070 getContentResolver().unregisterContentObserver(mLocalVideosObserver);
2071 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
2072 mCameraAppUI.onDestroy();
2073 mModeListView.setVisibilityChangedListener(null);
2074 mCameraController = null;
2075 mSettingsManager = null;
2076 mOrientationManager = null;
2077 mButtonManager = null;
2078 mSoundPlayer.release();
2079 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
2080 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
2084 public void onConfigurationChanged(Configuration config) {
2085 super.onConfigurationChanged(config);
2086 Log.v(TAG, "onConfigurationChanged");
2087 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
2091 if (mLastLayoutOrientation != config.orientation) {
2092 mLastLayoutOrientation = config.orientation;
2093 mCurrentModule.onLayoutOrientationChanged(
2094 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
2099 public boolean onKeyDown(int keyCode, KeyEvent event) {
2100 if (!mFilmstripVisible) {
2101 if (mCurrentModule.onKeyDown(keyCode, event)) {
2104 // Prevent software keyboard or voice search from showing up.
2105 if (keyCode == KeyEvent.KEYCODE_SEARCH
2106 || keyCode == KeyEvent.KEYCODE_MENU) {
2107 if (event.isLongPress()) {
2113 return super.onKeyDown(keyCode, event);
2117 public boolean onKeyUp(int keyCode, KeyEvent event) {
2118 if (!mFilmstripVisible) {
2119 // If a module is in the middle of capture, it should
2120 // consume the key event.
2121 if (mCurrentModule.onKeyUp(keyCode, event)) {
2123 } else if (keyCode == KeyEvent.KEYCODE_MENU
2124 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2125 // Let the mode list view consume the event.
2126 mCameraAppUI.openModeList();
2128 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2129 mCameraAppUI.showFilmstrip();
2133 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2134 mFilmstripController.goToNextItem();
2136 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2137 boolean wentToPrevious = mFilmstripController.goToPreviousItem();
2138 if (!wentToPrevious) {
2139 // at beginning of filmstrip, hide and go back to preview
2140 mCameraAppUI.hideFilmstrip();
2145 return super.onKeyUp(keyCode, event);
2149 public void onBackPressed() {
2150 if (!mCameraAppUI.onBackPressed()) {
2151 if (!mCurrentModule.onBackPressed()) {
2152 super.onBackPressed();
2158 public boolean isAutoRotateScreen() {
2159 // TODO: Move to OrientationManager.
2160 return mAutoRotateScreen;
2164 public boolean onCreateOptionsMenu(Menu menu) {
2165 MenuInflater inflater = getMenuInflater();
2166 inflater.inflate(R.menu.filmstrip_menu, menu);
2167 mActionBarMenu = menu;
2169 // add a button for launching the gallery
2170 if (mGalleryIntent != null) {
2171 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
2172 if (appName != null) {
2173 MenuItem menuItem = menu.add(appName);
2174 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2175 menuItem.setIntent(mGalleryIntent);
2177 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
2178 if (galleryLogo != null) {
2179 menuItem.setIcon(galleryLogo);
2184 return super.onCreateOptionsMenu(menu);
2188 public boolean onPrepareOptionsMenu(Menu menu) {
2189 if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
2190 // Compatibility pre-L: launching new activities right above
2191 // lockscreen does not reliably work, only show help if not secure
2192 menu.removeItem(R.id.action_help_and_feedback);
2195 return super.onPrepareOptionsMenu(menu);
2198 protected long getStorageSpaceBytes() {
2199 synchronized (mStorageSpaceLock) {
2200 return mStorageSpaceBytes;
2204 protected interface OnStorageUpdateDoneListener {
2205 public void onStorageUpdateDone(long bytes);
2208 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
2210 * We execute disk operations on a background thread in order to
2211 * free up the UI thread. Synchronizing on the lock below ensures
2212 * that when getStorageSpaceBytes is called, the main thread waits
2213 * until this method has completed.
2215 * However, .execute() does not ensure this execution block will be
2216 * run right away (.execute() schedules this AsyncTask for sometime
2217 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
2218 * tries to execute the task in parellel with other AsyncTasks, but
2219 * there's still no guarantee).
2220 * e.g. don't call this then immediately call getStorageSpaceBytes().
2221 * Instead, pass in an OnStorageUpdateDoneListener.
2223 (new AsyncTask<Void, Void, Long>() {
2225 protected Long doInBackground(Void ... arg) {
2226 synchronized (mStorageSpaceLock) {
2227 mStorageSpaceBytes = Storage.getAvailableSpace();
2228 return mStorageSpaceBytes;
2233 protected void onPostExecute(Long bytes) {
2234 updateStorageHint(bytes);
2235 // This callback returns after I/O to check disk, so we could be
2236 // pausing and shutting down. If so, don't bother invoking.
2237 if (callback != null && !mPaused) {
2238 callback.onStorageUpdateDone(bytes);
2240 Log.v(TAG, "ignoring storage callback after activity pause");
2243 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2246 protected void updateStorageHint(long storageSpace) {
2247 if (!mIsActivityRunning) {
2251 String message = null;
2252 if (storageSpace == Storage.UNAVAILABLE) {
2253 message = getString(R.string.no_storage);
2254 } else if (storageSpace == Storage.PREPARING) {
2255 message = getString(R.string.preparing_sd);
2256 } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2257 message = getString(R.string.access_sd_fail);
2258 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2259 message = getString(R.string.spaceIsLow_content);
2262 if (message != null) {
2263 Log.w(TAG, "Storage warning: " + message);
2264 if (mStorageHint == null) {
2265 mStorageHint = OnScreenHint.makeText(message);
2267 mStorageHint.setText(message);
2269 mStorageHint.show();
2270 UsageStatistics.instance().storageWarning(storageSpace);
2272 // Disable all user interactions,
2273 mCameraAppUI.setDisableAllUserInteractions(true);
2274 } else if (mStorageHint != null) {
2275 mStorageHint.cancel();
2276 mStorageHint = null;
2278 // Re-enable all user interactions.
2279 mCameraAppUI.setDisableAllUserInteractions(false);
2283 protected void setResultEx(int resultCode) {
2284 mResultCodeForTesting = resultCode;
2285 setResult(resultCode);
2288 protected void setResultEx(int resultCode, Intent data) {
2289 mResultCodeForTesting = resultCode;
2290 mResultDataForTesting = data;
2291 setResult(resultCode, data);
2294 public int getResultCode() {
2295 return mResultCodeForTesting;
2298 public Intent getResultData() {
2299 return mResultDataForTesting;
2302 public boolean isSecureCamera() {
2303 return mSecureCamera;
2307 public boolean isPaused() {
2312 public int getPreferredChildModeIndex(int modeIndex) {
2313 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2314 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2315 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) {
2316 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2323 public void onModeSelected(int modeIndex) {
2324 if (mCurrentModeIndex == modeIndex) {
2328 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2329 // Record last used camera mode for quick switching
2330 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2331 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2332 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2333 Keys.KEY_CAMERA_MODULE_LAST_USED,
2337 closeModule(mCurrentModule);
2339 // Select the correct module index from the mode switcher index.
2340 modeIndex = getPreferredChildModeIndex(modeIndex);
2341 setModuleFromModeIndex(modeIndex);
2343 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2344 mCameraAppUI.addShutterListener(mCurrentModule);
2345 openModule(mCurrentModule);
2346 // Store the module index so we can use it the next time the Camera
2348 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2349 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2353 * Shows the settings dialog.
2356 public void onSettingsSelected() {
2357 UsageStatistics.instance().controlUsed(
2358 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2359 Intent intent = new Intent(this, CameraSettingsActivity.class);
2360 startActivity(intent);
2364 public void freezeScreenUntilPreviewReady() {
2365 mCameraAppUI.freezeScreenUntilPreviewReady();
2369 public int getModuleId(int modeIndex) {
2370 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2371 if (agent == null) {
2374 return agent.getModuleId();
2378 * Sets the mCurrentModuleIndex, creates a new module instance for the given
2379 * index an sets it as mCurrentModule.
2381 private void setModuleFromModeIndex(int modeIndex) {
2382 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2383 if (agent == null) {
2386 if (!agent.requestAppForCamera()) {
2387 mCameraController.closeCamera(true);
2389 mCurrentModeIndex = agent.getModuleId();
2390 mCurrentModule = (CameraModule) agent.createModule(this, getIntent());
2394 public SettingsManager getSettingsManager() {
2395 return mSettingsManager;
2399 public ResolutionSetting getResolutionSetting() {
2400 return mResolutionSetting;
2404 public CameraServices getServices() {
2405 return CameraServicesImpl.instance();
2408 public List<String> getSupportedModeNames() {
2409 List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2410 List<String> supported = new ArrayList<String>();
2412 for (Integer modeIndex : indices) {
2413 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2414 if (name != null && !name.equals("")) {
2415 supported.add(name);
2422 public ButtonManager getButtonManager() {
2423 if (mButtonManager == null) {
2424 mButtonManager = new ButtonManager(this);
2426 return mButtonManager;
2430 public SoundPlayer getSoundPlayer() {
2431 return mSoundPlayer;
2435 * Launches an ACTION_EDIT intent for the given local data item. If
2436 * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2437 * the user start either the tiny planet editor or another photo editor.
2439 * @param data The data item to edit.
2441 public void launchEditor(FilmstripItem data) {
2442 Intent intent = new Intent(Intent.ACTION_EDIT)
2443 .setDataAndType(data.getData().getUri(), data.getData().getMimeType())
2444 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2446 launchActivityByIntent(intent);
2447 } catch (ActivityNotFoundException e) {
2448 final String msgEditWith = getResources().getString(R.string.edit_with);
2449 launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2454 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2455 super.onCreateContextMenu(menu, v, menuInfo);
2457 MenuInflater inflater = getMenuInflater();
2458 inflater.inflate(R.menu.filmstrip_context_menu, menu);
2462 public boolean onContextItemSelected(MenuItem item) {
2463 switch (item.getItemId()) {
2464 case R.id.tiny_planet_editor:
2465 mMyFilmstripBottomControlListener.onTinyPlanet();
2467 case R.id.photo_editor:
2468 mMyFilmstripBottomControlListener.onEdit();
2475 * Launch the tiny planet editor.
2477 * @param data The data must be a 360 degree stereographically mapped
2478 * panoramic image. It will not be modified, instead a new item
2479 * with the result will be added to the filmstrip.
2481 public void launchTinyPlanetEditor(FilmstripItem data) {
2482 TinyPlanetFragment fragment = new TinyPlanetFragment();
2483 Bundle bundle = new Bundle();
2484 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString());
2485 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle());
2486 fragment.setArguments(bundle);
2487 fragment.show(getFragmentManager(), "tiny_planet");
2491 * Returns what UI mode (capture mode or filmstrip) we are in.
2492 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2494 private int currentUserInterfaceMode() {
2495 int mode = NavigationChange.Mode.UNKNOWN_MODE;
2496 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2497 mode = NavigationChange.Mode.PHOTO_CAPTURE;
2499 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2500 mode = NavigationChange.Mode.VIDEO_CAPTURE;
2502 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2503 mode = NavigationChange.Mode.LENS_BLUR;
2505 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2506 mode = NavigationChange.Mode.HDR_PLUS;
2508 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2509 mode = NavigationChange.Mode.PHOTO_SPHERE;
2511 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2512 mode = NavigationChange.Mode.PANORAMA;
2514 if (mFilmstripVisible) {
2515 mode = NavigationChange.Mode.FILMSTRIP;
2520 private void openModule(CameraModule module) {
2521 module.init(this, isSecureCamera(), isCaptureIntent());
2522 module.hardResetSettings(mSettingsManager);
2523 // Hide accessibility zoom UI by default. Modules will enable it themselves if required.
2524 getCameraAppUI().hideAccessibilityZoomUI();
2527 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2528 NavigationChange.InteractionCause.BUTTON);
2529 updatePreviewVisibility();
2533 private void closeModule(CameraModule module) {
2535 mCameraAppUI.clearModuleUI();
2538 private void performDeletion() {
2539 if (!mPendingDeletion) {
2542 hideUndoDeletionBar(false);
2543 mDataAdapter.executeDeletion();
2546 public void showUndoDeletionBar() {
2547 if (mPendingDeletion) {
2550 Log.v(TAG, "showing undo bar");
2551 mPendingDeletion = true;
2552 if (mUndoDeletionBar == null) {
2553 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2554 mAboveFilmstripControlLayout, true);
2555 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2556 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2557 button.setOnClickListener(new View.OnClickListener() {
2559 public void onClick(View view) {
2560 mDataAdapter.undoDeletion();
2561 hideUndoDeletionBar(true);
2564 // Setting undo bar clickable to avoid touch events going through
2565 // the bar to the buttons (eg. edit button, etc) underneath the bar.
2566 mUndoDeletionBar.setClickable(true);
2567 // When there is user interaction going on with the undo button, we
2568 // do not want to hide the undo bar.
2569 button.setOnTouchListener(new View.OnTouchListener() {
2571 public boolean onTouch(View v, MotionEvent event) {
2572 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2573 mIsUndoingDeletion = true;
2574 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2575 mIsUndoingDeletion = false;
2581 mUndoDeletionBar.setAlpha(0f);
2582 mUndoDeletionBar.setVisibility(View.VISIBLE);
2583 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2586 private void hideUndoDeletionBar(boolean withAnimation) {
2587 Log.v(TAG, "Hiding undo deletion bar");
2588 mPendingDeletion = false;
2589 if (mUndoDeletionBar != null) {
2590 if (withAnimation) {
2591 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2592 .setListener(new Animator.AnimatorListener() {
2594 public void onAnimationStart(Animator animation) {
2599 public void onAnimationEnd(Animator animation) {
2600 mUndoDeletionBar.setVisibility(View.GONE);
2604 public void onAnimationCancel(Animator animation) {
2609 public void onAnimationRepeat(Animator animation) {
2614 mUndoDeletionBar.setVisibility(View.GONE);
2620 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2623 * @param enable {@code true} to enable swipe.
2625 public void setSwipingEnabled(boolean enable) {
2626 // TODO: Bring back the functionality.
2627 if (isCaptureIntent()) {
2628 // lockPreview(true);
2630 // lockPreview(!enable);
2634 // Accessor methods for getting latency times used in performance testing
2635 public long getFirstPreviewTime() {
2636 if (mCurrentModule instanceof PhotoModule) {
2637 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2638 if (coverHiddenTime != -1) {
2639 return coverHiddenTime - mOnCreateTime;
2645 public long getAutoFocusTime() {
2646 return (mCurrentModule instanceof PhotoModule) ?
2647 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2650 public long getShutterLag() {
2651 return (mCurrentModule instanceof PhotoModule) ?
2652 ((PhotoModule) mCurrentModule).mShutterLag : -1;
2655 public long getShutterToPictureDisplayedTime() {
2656 return (mCurrentModule instanceof PhotoModule) ?
2657 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2660 public long getPictureDisplayedToJpegCallbackTime() {
2661 return (mCurrentModule instanceof PhotoModule) ?
2662 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2665 public long getJpegCallbackFinishTime() {
2666 return (mCurrentModule instanceof PhotoModule) ?
2667 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2670 public long getCaptureStartTime() {
2671 return (mCurrentModule instanceof PhotoModule) ?
2672 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2675 public boolean isRecording() {
2676 return (mCurrentModule instanceof VideoModule) ?
2677 ((VideoModule) mCurrentModule).isRecording() : false;
2680 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2681 return mCameraController;
2684 // For debugging purposes only.
2685 public CameraModule getCurrentModule() {
2686 return mCurrentModule;
2690 public void showTutorial(AbstractTutorialOverlay tutorial) {
2691 mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2695 public void showErrorAndFinish(int messageId) {
2696 CameraUtil.showError(this, messageId, R.string.feedback_description_camera_access, true);
2700 public void finishActivityWithIntentCompleted(Intent resultIntent) {
2701 finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent);
2705 public void finishActivityWithIntentCanceled() {
2706 finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent());
2709 private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) {
2710 mResultCodeForTesting = resultCode;
2711 mResultDataForTesting = resultIntent;
2712 setResult(resultCode, resultIntent);
2716 private void keepScreenOnForAWhile() {
2717 if (mKeepScreenOn) {
2720 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2721 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2722 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2725 private void resetScreenOn() {
2726 mKeepScreenOn = false;
2727 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2728 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2732 * @return {@code true} if the Gallery is launched successfully.
2734 private boolean startGallery() {
2735 if (mGalleryIntent == null) {
2739 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2740 NavigationChange.InteractionCause.BUTTON);
2741 Intent startGalleryIntent = new Intent(mGalleryIntent);
2742 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
2743 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex);
2744 if (currentFilmstripItem != null) {
2745 GalleryHelper.setContentUri(startGalleryIntent,
2746 currentFilmstripItem.getData().getUri());
2748 launchActivityByIntent(startGalleryIntent);
2749 } catch (ActivityNotFoundException e) {
2750 Log.w(TAG, "Failed to launch gallery activity, closing");
2755 private void setNfcBeamPushUriFromData(FilmstripItem data) {
2756 final Uri uri = data.getData().getUri();
2757 if (uri != Uri.EMPTY) {
2758 mNfcPushUris[0] = uri;
2760 mNfcPushUris[0] = null;
2765 * Updates the visibility of the filmstrip bottom controls and action bar.
2767 private void updateUiByData(final int index) {
2768 final FilmstripItem currentData = mDataAdapter.getItemAt(index);
2769 if (currentData == null) {
2770 Log.w(TAG, "Current data ID not found.");
2771 hideSessionProgress();
2774 updateActionBarMenu(currentData);
2776 /* Bottom controls. */
2777 updateBottomControlsByData(currentData);
2779 if (isSecureCamera()) {
2780 // We cannot show buttons in secure camera since go to other
2781 // activities might create a security hole.
2782 mCameraAppUI.getFilmstripBottomControls().hideControls();
2786 setNfcBeamPushUriFromData(currentData);
2788 if (!mDataAdapter.isMetadataUpdatedAt(index)) {
2789 mDataAdapter.updateMetadataAt(index);
2794 * Updates the bottom controls based on the data.
2796 private void updateBottomControlsByData(final FilmstripItem currentData) {
2798 final CameraAppUI.BottomPanel filmstripBottomPanel =
2799 mCameraAppUI.getFilmstripBottomControls();
2800 filmstripBottomPanel.showControls();
2801 filmstripBottomPanel.setEditButtonVisibility(
2802 currentData.getAttributes().canEdit());
2803 filmstripBottomPanel.setShareButtonVisibility(
2804 currentData.getAttributes().canShare());
2805 filmstripBottomPanel.setDeleteButtonVisibility(
2806 currentData.getAttributes().canDelete());
2810 Uri contentUri = currentData.getData().getUri();
2811 CaptureSessionManager sessionManager = getServices()
2812 .getCaptureSessionManager();
2814 if (sessionManager.hasErrorMessage(contentUri)) {
2815 showProcessError(sessionManager.getErrorMessage(contentUri));
2817 filmstripBottomPanel.hideProgressError();
2818 CaptureSession session = sessionManager.getSession(contentUri);
2820 if (session != null) {
2821 int sessionProgress = session.getProgress();
2823 if (sessionProgress < 0) {
2824 hideSessionProgress();
2826 CharSequence progressMessage = session.getProgressMessage();
2827 showSessionProgress(progressMessage);
2828 updateSessionProgress(sessionProgress);
2831 hideSessionProgress();
2837 // We need to add this to a separate DB.
2838 final int viewButtonVisibility;
2839 if (currentData.getMetadata().isUsePanoramaViewer()) {
2840 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2841 } else if (currentData.getMetadata().isHasRgbzData()) {
2842 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2844 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2847 filmstripBottomPanel.setTinyPlanetEnabled(
2848 currentData.getMetadata().isPanorama360());
2849 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2852 private void showDetailsDialog(int index) {
2853 final FilmstripItem data = mDataAdapter.getItemAt(index);
2857 Optional<MediaDetails> details = data.getMediaDetails();
2858 if (!details.isPresent()) {
2861 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get());
2862 detailDialog.show();
2863 UsageStatistics.instance().mediaInteraction(
2864 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS,
2865 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index));
2869 * Show or hide action bar items depending on current data type.
2871 private void updateActionBarMenu(FilmstripItem data) {
2872 if (mActionBarMenu == null) {
2876 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2877 if (detailsMenuItem == null) {
2881 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo();
2882 detailsMenuItem.setVisible(showDetails);