2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 package com.android.camera;
20 import android.animation.Animator;
21 import android.app.ActionBar;
22 import android.app.Activity;
23 import android.app.Dialog;
24 import android.content.ActivityNotFoundException;
25 import android.content.BroadcastReceiver;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.pm.ActivityInfo;
31 import android.content.res.Configuration;
32 import android.graphics.Bitmap;
33 import android.graphics.Matrix;
34 import android.graphics.RectF;
35 import android.graphics.SurfaceTexture;
36 import android.graphics.drawable.ColorDrawable;
37 import android.graphics.drawable.Drawable;
38 import android.net.Uri;
39 import android.nfc.NfcAdapter;
40 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
41 import android.nfc.NfcEvent;
42 import android.os.AsyncTask;
43 import android.os.Build;
44 import android.os.Bundle;
45 import android.os.Handler;
46 import android.os.Looper;
47 import android.os.Message;
48 import android.provider.MediaStore;
49 import android.provider.Settings;
50 import android.text.TextUtils;
51 import android.util.CameraPerformanceTracker;
52 import android.view.ContextMenu;
53 import android.view.ContextMenu.ContextMenuInfo;
54 import android.view.KeyEvent;
55 import android.view.Menu;
56 import android.view.MenuInflater;
57 import android.view.MenuItem;
58 import android.view.MotionEvent;
59 import android.view.View;
60 import android.view.View.OnSystemUiVisibilityChangeListener;
61 import android.view.ViewGroup;
62 import android.view.Window;
63 import android.view.WindowManager;
64 import android.widget.FrameLayout;
65 import android.widget.ImageView;
66 import android.widget.ShareActionProvider;
68 import com.android.camera.app.AppController;
69 import com.android.camera.app.CameraAppUI;
70 import com.android.camera.app.CameraController;
71 import com.android.camera.app.CameraProvider;
72 import com.android.camera.app.CameraServices;
73 import com.android.camera.app.CameraServicesImpl;
74 import com.android.camera.app.FirstRunDialog;
75 import com.android.camera.app.LocationManager;
76 import com.android.camera.app.MemoryManager;
77 import com.android.camera.app.MemoryQuery;
78 import com.android.camera.app.ModuleManager;
79 import com.android.camera.app.ModuleManager.ModuleAgent;
80 import com.android.camera.app.ModuleManagerImpl;
81 import com.android.camera.app.MotionManager;
82 import com.android.camera.app.OrientationManager;
83 import com.android.camera.app.OrientationManagerImpl;
84 import com.android.camera.data.CameraFilmstripDataAdapter;
85 import com.android.camera.data.FilmstripContentObserver;
86 import com.android.camera.data.FilmstripItem;
87 import com.android.camera.data.FilmstripItemData;
88 import com.android.camera.data.FilmstripItemType;
89 import com.android.camera.data.FilmstripItemUtils;
90 import com.android.camera.data.FixedLastProxyAdapter;
91 import com.android.camera.data.GlideFilmstripManager;
92 import com.android.camera.data.LocalFilmstripDataAdapter;
93 import com.android.camera.data.LocalFilmstripDataAdapter.FilmstripItemListener;
94 import com.android.camera.data.MediaDetails;
95 import com.android.camera.data.MetadataLoader;
96 import com.android.camera.data.PhotoDataFactory;
97 import com.android.camera.data.PhotoItem;
98 import com.android.camera.data.PhotoItemFactory;
99 import com.android.camera.data.PlaceholderItem;
100 import com.android.camera.data.SessionItem;
101 import com.android.camera.data.VideoDataFactory;
102 import com.android.camera.data.VideoItemFactory;
103 import com.android.camera.debug.Log;
104 import com.android.camera.device.ActiveCameraDeviceTracker;
105 import com.android.camera.filmstrip.FilmstripContentPanel;
106 import com.android.camera.filmstrip.FilmstripController;
107 import com.android.camera.module.ModuleController;
108 import com.android.camera.module.ModulesInfo;
109 import com.android.camera.one.OneCameraException;
110 import com.android.camera.one.OneCameraManager;
111 import com.android.camera.one.OneCameraModule;
112 import com.android.camera.one.OneCameraOpener;
113 import com.android.camera.one.config.OneCameraFeatureConfig;
114 import com.android.camera.one.config.OneCameraFeatureConfigCreator;
115 import com.android.camera.session.CaptureSession;
116 import com.android.camera.session.CaptureSessionManager;
117 import com.android.camera.session.CaptureSessionManager.SessionListener;
118 import com.android.camera.settings.AppUpgrader;
119 import com.android.camera.settings.CameraSettingsActivity;
120 import com.android.camera.settings.Keys;
121 import com.android.camera.settings.ResolutionSetting;
122 import com.android.camera.settings.ResolutionUtil;
123 import com.android.camera.settings.SettingsManager;
124 import com.android.camera.stats.UsageStatistics;
125 import com.android.camera.stats.profiler.Profile;
126 import com.android.camera.stats.profiler.Profiler;
127 import com.android.camera.stats.profiler.Profilers;
128 import com.android.camera.tinyplanet.TinyPlanetFragment;
129 import com.android.camera.ui.AbstractTutorialOverlay;
130 import com.android.camera.ui.DetailsDialog;
131 import com.android.camera.ui.MainActivityLayout;
132 import com.android.camera.ui.ModeListView;
133 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
134 import com.android.camera.ui.PreviewStatusListener;
135 import com.android.camera.util.ApiHelper;
136 import com.android.camera.util.Callback;
137 import com.android.camera.util.CameraSettingsActivityHelper;
138 import com.android.camera.util.CameraUtil;
139 import com.android.camera.util.GalleryHelper;
140 import com.android.camera.util.GcamHelper;
141 import com.android.camera.util.GoogleHelpHelper;
142 import com.android.camera.util.IntentHelper;
143 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
144 import com.android.camera.util.QuickActivity;
145 import com.android.camera.util.ReleaseHelper;
146 import com.android.camera.util.Size;
147 import com.android.camera.widget.FilmstripView;
148 import com.android.camera.widget.Preloader;
149 import com.android.camera2.R;
150 import com.android.ex.camera2.portability.CameraAgent;
151 import com.android.ex.camera2.portability.CameraAgentFactory;
152 import com.android.ex.camera2.portability.CameraExceptionHandler;
153 import com.android.ex.camera2.portability.CameraSettings;
154 import com.bumptech.glide.Glide;
155 import com.bumptech.glide.GlideBuilder;
156 import com.bumptech.glide.MemoryCategory;
157 import com.bumptech.glide.load.DecodeFormat;
158 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
159 import com.bumptech.glide.load.engine.prefill.PreFillType;
160 import com.google.common.base.Optional;
161 import com.google.common.logging.eventprotos;
162 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
163 import com.google.common.logging.eventprotos.MediaInteraction;
164 import com.google.common.logging.eventprotos.NavigationChange;
167 import java.lang.ref.WeakReference;
168 import java.util.ArrayList;
169 import java.util.HashMap;
170 import java.util.List;
172 public class CameraActivity extends QuickActivity
173 implements AppController, CameraAgent.CameraOpenCallback,
174 ShareActionProvider.OnShareTargetSelectedListener {
176 private static final Log.Tag TAG = new Log.Tag("CameraActivity");
178 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
179 "android.media.action.STILL_IMAGE_CAMERA_SECURE";
180 public static final String ACTION_IMAGE_CAPTURE_SECURE =
181 "android.media.action.IMAGE_CAPTURE_SECURE";
183 // The intent extra for camera from secure lock screen. True if the gallery
184 // should only show newly captured pictures. sSecureAlbumId does not
185 // increment. This is used when switching between camera, camcorder, and
186 // panorama. If the extra is not set, it is in the normal camera mode.
187 public static final String SECURE_CAMERA_EXTRA = "secure_camera";
189 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
190 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
191 /** Load metadata for 10 items ahead of our current. */
192 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
194 /** Should be used wherever a context is needed. */
195 private Context mAppContext;
198 * Camera fatal error handling:
199 * 1) Present error dialog to guide users to exit the app.
200 * 2) If users hit home button, onPause should just call finish() to exit the app.
202 private boolean mCameraFatalError = false;
205 * Whether onResume should reset the view to the preview.
207 private boolean mResetToPreviewOnResume = true;
210 * This data adapter is used by FilmStripView.
212 private VideoItemFactory mVideoItemFactory;
213 private PhotoItemFactory mPhotoItemFactory;
214 private LocalFilmstripDataAdapter mDataAdapter;
216 private ActiveCameraDeviceTracker mActiveCameraDeviceTracker;
217 private OneCameraOpener mOneCameraOpener;
218 private OneCameraManager mOneCameraManager;
219 private SettingsManager mSettingsManager;
220 private ResolutionSetting mResolutionSetting;
221 private ModeListView mModeListView;
222 private boolean mModeListVisible = false;
223 private int mCurrentModeIndex;
224 private CameraModule mCurrentModule;
225 private ModuleManagerImpl mModuleManager;
226 private FrameLayout mAboveFilmstripControlLayout;
227 private FilmstripController mFilmstripController;
228 private boolean mFilmstripVisible;
229 /** Whether the filmstrip fully covers the preview. */
230 private boolean mFilmstripCoversPreview = false;
231 private int mResultCodeForTesting;
232 private Intent mResultDataForTesting;
233 private OnScreenHint mStorageHint;
234 private final Object mStorageSpaceLock = new Object();
235 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
236 private boolean mAutoRotateScreen;
237 private boolean mSecureCamera;
238 private OrientationManagerImpl mOrientationManager;
239 private LocationManager mLocationManager;
240 private ButtonManager mButtonManager;
241 private Handler mMainHandler;
242 private PanoramaViewHelper mPanoramaViewHelper;
243 private ActionBar mActionBar;
244 private ViewGroup mUndoDeletionBar;
245 private boolean mIsUndoingDeletion = false;
246 private boolean mIsActivityRunning = false;
247 private FatalErrorHandler mFatalErrorHandler;
249 private final Uri[] mNfcPushUris = new Uri[1];
251 private FilmstripContentObserver mLocalImagesObserver;
252 private FilmstripContentObserver mLocalVideosObserver;
254 private boolean mPendingDeletion = false;
256 private CameraController mCameraController;
257 private boolean mPaused;
258 private CameraAppUI mCameraAppUI;
260 private Intent mGalleryIntent;
261 private long mOnCreateTime;
263 private Menu mActionBarMenu;
264 private Preloader<Integer, AsyncTask> mPreloader;
266 /** Can be used to play custom sounds. */
267 private SoundPlayer mSoundPlayer;
269 /** Holds configuration for various OneCamera features. */
270 private OneCameraFeatureConfig mFeatureConfig;
272 private static final int LIGHTS_OUT_DELAY_MS = 4000;
273 private final int BASE_SYS_UI_VISIBILITY =
274 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
275 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
276 private final Runnable mLightsOutRunnable = new Runnable() {
279 getWindow().getDecorView().setSystemUiVisibility(
280 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE);
283 private MemoryManager mMemoryManager;
284 private MotionManager mMotionManager;
285 private final Profiler mProfiler = Profilers.instance().guard();
287 /** First run dialog */
288 private FirstRunDialog mFirstRunDialog;
291 public CameraAppUI getCameraAppUI() {
296 public ModuleManager getModuleManager() {
297 return mModuleManager;
301 * Close activity when secure app passes lock screen or screen turns
304 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
306 public void onReceive(Context context, Intent intent) {
312 * Whether the screen is kept turned on.
314 private boolean mKeepScreenOn;
315 private int mLastLayoutOrientation;
316 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
317 new CameraAppUI.BottomPanel.Listener() {
320 * If the current photo is a photo sphere, this will launch the
321 * Photo Sphere panorama viewer.
324 public void onExternalViewer() {
325 if (mPanoramaViewHelper == null) {
328 final FilmstripItem data = getCurrentLocalData();
330 Log.w(TAG, "Cannot open null data.");
333 final Uri contentUri = data.getData().getUri();
334 if (contentUri == Uri.EMPTY) {
335 Log.w(TAG, "Cannot open empty URL.");
339 if (data.getMetadata().isUsePanoramaViewer()) {
340 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
341 } else if (data.getMetadata().isHasRgbzData()) {
342 mPanoramaViewHelper.showRgbz(contentUri);
343 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
344 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
345 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
346 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false);
347 mCameraAppUI.clearClingForViewer(
348 CameraAppUI.BottomPanel.VIEWER_REFOCUS);
354 public void onEdit() {
355 FilmstripItem data = getCurrentLocalData();
357 Log.w(TAG, "Cannot edit null data.");
360 final int currentDataId = getCurrentDataId();
361 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
363 MediaInteraction.InteractionType.EDIT,
364 NavigationChange.InteractionCause.BUTTON,
365 fileAgeFromAdapterAtIndex(currentDataId));
370 public void onTinyPlanet() {
371 FilmstripItem data = getCurrentLocalData();
373 Log.w(TAG, "Cannot edit tiny planet on null data.");
376 launchTinyPlanetEditor(data);
380 public void onDelete() {
381 final int currentDataId = getCurrentDataId();
382 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
384 MediaInteraction.InteractionType.DELETE,
385 NavigationChange.InteractionCause.BUTTON,
386 fileAgeFromAdapterAtIndex(currentDataId));
387 removeItemAt(currentDataId);
391 public void onShare() {
392 final FilmstripItem data = getCurrentLocalData();
394 Log.w(TAG, "Cannot share null data.");
398 final int currentDataId = getCurrentDataId();
399 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
401 MediaInteraction.InteractionType.SHARE,
402 NavigationChange.InteractionCause.BUTTON,
403 fileAgeFromAdapterAtIndex(currentDataId));
404 // If applicable, show release information before this item
406 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
407 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
408 new Callback<Void>() {
410 public void onCallback(Void result) {
419 private void share(FilmstripItem data) {
420 Intent shareIntent = getShareIntentByData(data);
421 if (shareIntent != null) {
423 launchActivityByIntent(shareIntent);
424 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
425 } catch (ActivityNotFoundException ex) {
431 private int getCurrentDataId() {
432 return mFilmstripController.getCurrentAdapterIndex();
435 private FilmstripItem getCurrentLocalData() {
436 return mDataAdapter.getItemAt(getCurrentDataId());
440 * Sets up the share intent and NFC properly according to the
443 * @param item The data to be shared.
445 private Intent getShareIntentByData(final FilmstripItem item) {
446 Intent intent = null;
447 final Uri contentUri = item.getData().getUri();
448 final String msgShareTo = getResources().getString(R.string.share_to);
450 if (item.getMetadata().isPanorama360() &&
451 item.getData().getUri() != Uri.EMPTY) {
452 intent = new Intent(Intent.ACTION_SEND);
453 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE);
454 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
455 } else if (item.getAttributes().canShare()) {
456 final String mimeType = item.getData().getMimeType();
457 intent = getShareIntentFromType(mimeType);
458 if (intent != null) {
459 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
460 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
462 intent = Intent.createChooser(intent, msgShareTo);
468 * Get the share intent according to the mimeType
470 * @param mimeType The mimeType of current data.
471 * @return the video/image's ShareIntent or null if mimeType is
474 private Intent getShareIntentFromType(String mimeType) {
475 // Lazily create the intent object.
476 Intent intent = new Intent(Intent.ACTION_SEND);
477 if (mimeType.startsWith("video/")) {
478 intent.setType("video/*");
480 if (mimeType.startsWith("image/")) {
481 intent.setType("image/*");
483 Log.w(TAG, "unsupported mimeType " + mimeType);
490 public void onProgressErrorClicked() {
491 FilmstripItem data = getCurrentLocalData();
492 getServices().getCaptureSessionManager().removeErrorMessage(
493 data.getData().getUri());
494 updateBottomControlsByData(data);
499 public void onCameraOpened(CameraAgent.CameraProxy camera) {
500 Log.v(TAG, "onCameraOpened");
502 // We've paused, but just asynchronously opened the camera. Close it
503 // because we should be releasing the camera when paused to allow
504 // other apps to access it.
505 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera");
506 mCameraController.closeCamera(false);
510 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
511 // We shouldn't be here. Just close the camera and leave.
512 mCameraController.closeCamera(false);
513 throw new IllegalStateException("Camera opened but the module shouldn't be " +
516 if (mCurrentModule != null) {
517 resetExposureCompensationToDefault(camera);
518 mCurrentModule.onCameraAvailable(camera);
520 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
522 Log.v(TAG, "invoking onChangeCamera");
523 mCameraAppUI.onChangeCamera();
526 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) {
527 // Reset the exposure compensation before handing the camera to module.
528 CameraSettings cameraSettings = camera.getSettings();
529 cameraSettings.setExposureCompensationIndex(0);
530 camera.applySettings(cameraSettings);
534 public void onCameraDisabled(int cameraId) {
535 Log.w(TAG, "Camera disabled: " + cameraId);
536 mFatalErrorHandler.onCameraDisabledFailure();
540 public void onDeviceOpenFailure(int cameraId, String info) {
541 Log.w(TAG, "Camera open failure: " + info);
542 mFatalErrorHandler.onCameraOpenFailure();
546 public void onDeviceOpenedAlready(int cameraId, String info) {
547 Log.w(TAG, "Camera open already: " + cameraId + "," + info);
548 mFatalErrorHandler.onGenericCameraAccessFailure();
552 public void onReconnectionFailure(CameraAgent mgr, String info) {
553 Log.w(TAG, "Camera reconnection failure:" + info);
554 mFatalErrorHandler.onCameraReconnectFailure();
557 private static class MainHandler extends Handler {
558 final WeakReference<CameraActivity> mActivity;
560 public MainHandler(CameraActivity activity, Looper looper) {
562 mActivity = new WeakReference<CameraActivity>(activity);
566 public void handleMessage(Message msg) {
567 CameraActivity activity = mActivity.get();
568 if (activity == null) {
573 case MSG_CLEAR_SCREEN_ON_FLAG: {
574 if (!activity.mPaused) {
575 activity.getWindow().clearFlags(
576 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
584 private String fileNameFromAdapterAtIndex(int index) {
585 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
586 if (filmstripItem == null) {
590 File localFile = new File(filmstripItem.getData().getFilePath());
591 return localFile.getName();
594 private float fileAgeFromAdapterAtIndex(int index) {
595 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
596 if (filmstripItem == null) {
600 File localFile = new File(filmstripItem.getData().getFilePath());
601 return 0.001f * (System.currentTimeMillis() - localFile.lastModified());
604 private final FilmstripContentPanel.Listener mFilmstripListener =
605 new FilmstripContentPanel.Listener() {
608 public void onSwipeOut() {
612 public void onSwipeOutBegin() {
614 mCameraAppUI.hideBottomControls();
615 mFilmstripCoversPreview = false;
616 updatePreviewVisibility();
620 public void onFilmstripHidden() {
621 mFilmstripVisible = false;
622 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
623 NavigationChange.InteractionCause.SWIPE_RIGHT);
624 CameraActivity.this.setFilmstripUiVisibility(false);
625 // When the user hide the filmstrip (either swipe out or
626 // tap on back key) we move to the first item so next time
627 // when the user swipe in the filmstrip, the most recent
629 mFilmstripController.goToFirstItem();
633 public void onFilmstripShown() {
634 mFilmstripVisible = true;
635 mCameraAppUI.hideCaptureIndicator();
636 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
637 NavigationChange.InteractionCause.SWIPE_LEFT);
638 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
642 public void onFocusedDataLongPressed(int adapterIndex) {
647 public void onFocusedDataPromoted(int adapterIndex) {
648 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
650 MediaInteraction.InteractionType.DELETE,
651 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex(
653 removeItemAt(adapterIndex);
657 public void onFocusedDataDemoted(int adapterIndex) {
658 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
660 MediaInteraction.InteractionType.DELETE,
661 NavigationChange.InteractionCause.SWIPE_DOWN,
662 fileAgeFromAdapterAtIndex(adapterIndex));
663 removeItemAt(adapterIndex);
667 public void onEnterFullScreenUiShown(int adapterIndex) {
668 if (mFilmstripVisible) {
669 CameraActivity.this.setFilmstripUiVisibility(true);
674 public void onLeaveFullScreenUiShown(int adapterIndex) {
679 public void onEnterFullScreenUiHidden(int adapterIndex) {
680 if (mFilmstripVisible) {
681 CameraActivity.this.setFilmstripUiVisibility(false);
686 public void onLeaveFullScreenUiHidden(int adapterIndex) {
691 public void onEnterFilmstrip(int adapterIndex) {
692 if (mFilmstripVisible) {
693 CameraActivity.this.setFilmstripUiVisibility(true);
698 public void onLeaveFilmstrip(int adapterIndex) {
703 public void onDataReloaded() {
704 if (!mFilmstripVisible) {
707 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
711 public void onDataUpdated(int adapterIndex) {
712 if (!mFilmstripVisible) {
715 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
719 public void onEnterZoomView(int adapterIndex) {
720 if (mFilmstripVisible) {
721 CameraActivity.this.setFilmstripUiVisibility(false);
726 public void onZoomAtIndexChanged(int adapterIndex, float zoom) {
727 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex);
728 long ageMillis = System.currentTimeMillis()
729 - filmstripItem.getData().getLastModifiedDate().getTime();
731 // Do not log if items is to old or does not have a path (which is
732 // being used as a key).
733 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) ||
734 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) {
737 File localFile = new File(filmstripItem.getData().getFilePath());
738 UsageStatistics.instance().mediaView(localFile.getName(),
739 filmstripItem.getData().getLastModifiedDate().getTime(), zoom);
743 public void onDataFocusChanged(final int prevIndex, final int newIndex) {
744 if (!mFilmstripVisible) {
747 // TODO: This callback is UI event callback, should always
748 // happen on UI thread. Find the reason for this
749 // runOnUiThread() and fix it.
750 runOnUiThread(new Runnable() {
753 updateUiByData(newIndex);
759 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
760 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
764 private final FilmstripItemListener mFilmstripItemListener =
765 new FilmstripItemListener() {
767 public void onMetadataUpdated(List<Integer> indexes) {
769 // Callback after the activity is paused.
772 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
773 for (Integer index : indexes) {
774 if (index == currentIndex) {
775 updateBottomControlsByData(mDataAdapter.getItemAt(index));
776 // Currently we have only 1 data can be matched.
777 // No need to look for more, break.
784 public void gotoGallery() {
785 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
786 NavigationChange.InteractionCause.BUTTON);
788 mFilmstripController.goToNextItem();
792 * If 'visible' is false, this hides the action bar. Also maintains
793 * lights-out at all times.
795 * @param visible is false, this hides the action bar and filmstrip bottom
798 private void setFilmstripUiVisibility(boolean visible) {
799 mLightsOutRunnable.run();
800 mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
801 if (visible != mActionBar.isShowing()) {
804 mCameraAppUI.showBottomControls();
807 mCameraAppUI.hideBottomControls();
810 mFilmstripCoversPreview = visible;
811 updatePreviewVisibility();
814 private void hideSessionProgress() {
815 mCameraAppUI.getFilmstripBottomControls().hideProgress();
818 private void showSessionProgress(int messageId) {
819 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
820 controls.setProgressText(messageId > 0 ? getString(messageId) : "");
821 controls.hideControls();
822 controls.hideProgressError();
823 controls.showProgress();
826 private void showProcessError(int messageId) {
827 mCameraAppUI.getFilmstripBottomControls().showProgressError(
828 messageId > 0 ? getString(messageId) : "");
831 private void updateSessionProgress(int progress) {
832 mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
835 private void updateSessionProgressText(int messageId) {
836 mCameraAppUI.getFilmstripBottomControls().setProgressText(
837 messageId > 0 ? getString(messageId) : "");
840 private void setupNfcBeamPush() {
841 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
842 if (adapter == null) {
846 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
848 adapter.setNdefPushMessage(null, CameraActivity.this);
852 adapter.setBeamPushUris(null, CameraActivity.this);
853 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
855 public Uri[] createBeamUris(NfcEvent event) {
858 }, CameraActivity.this);
862 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
863 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
864 if (currentIndex < 0) {
867 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex),
868 MediaInteraction.InteractionType.SHARE,
869 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex));
870 // TODO add intent.getComponent().getPackageName()
874 // Note: All callbacks come back on the main thread.
875 private final SessionListener mSessionListener =
876 new SessionListener() {
878 public void onSessionQueued(final Uri uri) {
879 Log.v(TAG, "onSessionQueued: " + uri);
880 if (!Storage.isSessionUri(uri)) {
883 SessionItem newData = new SessionItem(getApplicationContext(), uri);
884 mDataAdapter.addOrUpdate(newData);
888 public void onSessionUpdated(Uri uri) {
889 Log.v(TAG, "onSessionUpdated: " + uri);
890 mDataAdapter.refresh(uri);
894 public void onSessionDone(final Uri sessionUri) {
895 Log.v(TAG, "onSessionDone:" + sessionUri);
896 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
897 if (contentUri == null) {
898 mDataAdapter.refresh(sessionUri);
901 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri);
903 // This can be null if e.g. a session is canceled (e.g.
904 // through discard panorama). It might be worth adding
905 // onSessionCanceled or the like this interface.
906 if (newData == null) {
907 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri);
911 final int pos = mDataAdapter.findByContentUri(sessionUri);
913 // We do not have a placeholder for this image, perhaps
914 // due to the activity crashing or being killed.
915 mDataAdapter.addOrUpdate(newData);
917 // Make the PhotoItem aware of the session placeholder, to
918 // allow it to make a smooth transition to its content if it
919 // the session item is currently visible.
920 FilmstripItem oldSessionData = mDataAdapter.getFilmstripItemAt(pos);
921 if (mCameraAppUI.getFilmstripVisibility() == View.VISIBLE
922 && mFilmstripController.isVisible(oldSessionData)) {
923 Log.v(TAG, "session item visible, setting transition placeholder");
924 newData.setSessionPlaceholderBitmap(
925 Storage.getPlaceholderForSession(sessionUri));
927 mDataAdapter.updateItemAt(pos, newData);
932 public void onSessionProgress(final Uri uri, final int progress) {
934 // Do nothing, there is no task for this URI.
937 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
938 if (currentIndex == -1) {
942 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
943 updateSessionProgress(progress);
948 public void onSessionProgressText(final Uri uri, final int messageId) {
949 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
950 if (currentIndex == -1) {
954 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
955 updateSessionProgressText(messageId);
960 public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
961 // Don't show capture indicator in Photo Sphere.
962 final int photosphereModuleId = getApplicationContext().getResources()
964 R.integer.camera_mode_photosphere);
965 if (mCurrentModeIndex == photosphereModuleId) {
968 indicateCapture(indicator, rotationDegrees);
972 public void onSessionFailed(Uri uri, int failureMessageId,
973 boolean removeFromFilmstrip) {
974 Log.v(TAG, "onSessionFailed:" + uri);
976 int failedIndex = mDataAdapter.findByContentUri(uri);
977 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
979 if (currentIndex == failedIndex) {
980 updateSessionProgress(0);
981 showProcessError(failureMessageId);
982 mDataAdapter.refresh(uri);
984 if (removeFromFilmstrip) {
985 mFatalErrorHandler.onMediaStorageFailure();
986 mDataAdapter.removeAt(failedIndex);
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 String getModuleScope() {
1030 ModuleAgent agent = mModuleManager.getModuleAgent(mCurrentModeIndex);
1031 return SettingsManager.getModuleSettingScope(agent.getScopeNamespace());
1035 public String getCameraScope() {
1036 // if an unopen camera i.e. negative ID is returned, which we've observed in
1037 // some automated scenarios, just return it as a valid separate scope
1038 // this could cause user issues, so log a stack trace noting the call path
1039 // which resulted in this scenario.
1041 return SettingsManager.getCameraSettingScope(
1042 mCameraController.getCurrentCameraId().getValue());
1046 public ModuleController getCurrentModuleController() {
1047 return mCurrentModule;
1051 public int getQuickSwitchToModuleId(int currentModuleIndex) {
1052 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
1057 public SurfaceTexture getPreviewBuffer() {
1058 // TODO: implement this
1063 public void onPreviewReadyToStart() {
1064 mCameraAppUI.onPreviewReadyToStart();
1068 public void onPreviewStarted() {
1069 mCameraAppUI.onPreviewStarted();
1073 public void addPreviewAreaSizeChangedListener(
1074 PreviewStatusListener.PreviewAreaChangedListener listener) {
1075 mCameraAppUI.addPreviewAreaChangedListener(listener);
1079 public void removePreviewAreaSizeChangedListener(
1080 PreviewStatusListener.PreviewAreaChangedListener listener) {
1081 mCameraAppUI.removePreviewAreaChangedListener(listener);
1085 public void setupOneShotPreviewListener() {
1086 mCameraController.setOneShotPreviewCallback(mMainHandler,
1087 new CameraAgent.CameraPreviewDataCallback() {
1089 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) {
1090 mCurrentModule.onPreviewInitialDataReceived();
1091 mCameraAppUI.onNewPreviewFrame();
1098 public void updatePreviewAspectRatio(float aspectRatio) {
1099 mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
1103 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
1104 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio);
1108 public RectF getFullscreenRect() {
1109 return mCameraAppUI.getFullscreenRect();
1113 public void updatePreviewTransform(Matrix matrix) {
1114 mCameraAppUI.updatePreviewTransform(matrix);
1118 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1119 mCameraAppUI.setPreviewStatusListener(previewStatusListener);
1123 public FrameLayout getModuleLayoutRoot() {
1124 return mCameraAppUI.getModuleRootView();
1128 public void setShutterEventsListener(ShutterEventsListener listener) {
1129 // TODO: implement this
1133 public void setShutterEnabled(boolean enabled) {
1134 mCameraAppUI.setShutterButtonEnabled(enabled);
1138 public boolean isShutterEnabled() {
1139 return mCameraAppUI.isShutterButtonEnabled();
1143 public void startFlashAnimation(boolean shortFlash) {
1144 mCameraAppUI.startFlashAnimation(shortFlash);
1148 public void startPreCaptureAnimation() {
1149 // TODO: implement this
1153 public void cancelPreCaptureAnimation() {
1154 // TODO: implement this
1158 public void startPostCaptureAnimation() {
1159 // TODO: implement this
1163 public void startPostCaptureAnimation(Bitmap thumbnail) {
1164 // TODO: implement this
1168 public void cancelPostCaptureAnimation() {
1169 // TODO: implement this
1173 public OrientationManager getOrientationManager() {
1174 return mOrientationManager;
1178 public LocationManager getLocationManager() {
1179 return mLocationManager;
1183 public void lockOrientation() {
1184 if (mOrientationManager != null) {
1185 mOrientationManager.lockOrientation();
1190 public void unlockOrientation() {
1191 if (mOrientationManager != null) {
1192 mOrientationManager.unlockOrientation();
1197 * If not in filmstrip, this shows the capture indicator.
1199 private void indicateCapture(final Bitmap indicator, final int rotationDegrees) {
1200 if (mFilmstripVisible) {
1204 // Don't show capture indicator in Photo Sphere.
1205 // TODO: Don't reach into resources to figure out the current mode.
1206 final int photosphereModuleId = getApplicationContext().getResources().getInteger(
1207 R.integer.camera_mode_photosphere);
1208 if (mCurrentModeIndex == photosphereModuleId) {
1212 mMainHandler.post(new Runnable() {
1215 mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule
1216 .getPeekAccessibilityString());
1217 mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees);
1223 public void notifyNewMedia(Uri uri) {
1224 // TODO: This method is running on the main thread. Also we should get
1225 // rid of that AsyncTask.
1227 updateStorageSpaceAndHint(null);
1228 ContentResolver cr = getContentResolver();
1229 String mimeType = cr.getType(uri);
1230 FilmstripItem newData = null;
1231 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
1232 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1233 newData = mVideoItemFactory.queryContentUri(uri);
1234 if (newData == null) {
1235 Log.e(TAG, "Can't find video data in content resolver:" + uri);
1238 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
1239 CameraUtil.broadcastNewPicture(mAppContext, uri);
1240 newData = mPhotoItemFactory.queryContentUri(uri);
1241 if (newData == null) {
1242 Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1246 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1250 // We are preloading the metadata for new video since we need the
1251 // rotation info for the thumbnail.
1252 new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
1254 protected FilmstripItem doInBackground(FilmstripItem... params) {
1255 FilmstripItem data = params[0];
1256 MetadataLoader.loadMetadata(getAndroidContext(), data);
1261 protected void onPostExecute(final FilmstripItem data) {
1262 // TODO: Figure out why sometimes the data is aleady there.
1263 mDataAdapter.addOrUpdate(data);
1265 // Legacy modules don't use CaptureSession, so we show the capture indicator when
1266 // the item was safed.
1267 if (mCurrentModule instanceof PhotoModule ||
1268 mCurrentModule instanceof VideoModule) {
1269 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1272 final Optional<Bitmap> bitmap = data.generateThumbnail(
1273 mAboveFilmstripControlLayout.getWidth(),
1274 mAboveFilmstripControlLayout.getMeasuredHeight());
1275 if (bitmap.isPresent()) {
1276 indicateCapture(bitmap.get(), 0);
1282 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
1286 public void enableKeepScreenOn(boolean enabled) {
1291 mKeepScreenOn = enabled;
1292 if (mKeepScreenOn) {
1293 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1294 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1296 keepScreenOnForAWhile();
1301 public CameraProvider getCameraProvider() {
1302 return mCameraController;
1306 public OneCameraOpener getCameraOpener() {
1307 return mOneCameraOpener;
1310 private void removeItemAt(int index) {
1311 mDataAdapter.removeAt(index);
1312 if (mDataAdapter.getTotalNumber() > 1) {
1313 showUndoDeletionBar();
1315 // If camera preview is the only view left in filmstrip,
1316 // no need to show undo bar.
1317 mPendingDeletion = true;
1319 if (mFilmstripVisible) {
1320 mCameraAppUI.getFilmstripContentPanel().animateHide();
1326 public boolean onOptionsItemSelected(MenuItem item) {
1327 // Handle presses on the action bar items
1328 switch (item.getItemId()) {
1329 case android.R.id.home:
1332 case R.id.action_details:
1333 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex());
1335 case R.id.action_help_and_feedback:
1336 mResetToPreviewOnResume = false;
1337 new GoogleHelpHelper(this).launchGoogleHelp();
1340 return super.onOptionsItemSelected(item);
1344 private boolean isCaptureIntent() {
1345 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1346 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1347 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1355 * Note: Make sure this callback is unregistered properly when the activity
1356 * is destroyed since we're otherwise leaking the Activity reference.
1358 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
1359 = new CameraExceptionHandler.CameraExceptionCallback() {
1361 public void onCameraError(int errorCode) {
1362 // Not a fatal error. only do Log.e().
1363 Log.e(TAG, "Camera error callback. error=" + errorCode);
1366 public void onCameraException(
1367 RuntimeException ex, String commandHistory, int action, int state) {
1368 Log.e(TAG, "Camera Exception", ex);
1369 UsageStatistics.instance().cameraFailure(
1370 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
1371 commandHistory, action, state);
1375 public void onDispatchThreadException(RuntimeException ex) {
1376 Log.e(TAG, "DispatchThread Exception", ex);
1377 UsageStatistics.instance().cameraFailure(
1378 eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
1379 null, UsageStatistics.NONE, UsageStatistics.NONE);
1382 private void onFatalError() {
1383 if (mCameraFatalError) {
1386 mCameraFatalError = true;
1388 // If the activity receives exception during onPause, just exit the app.
1389 if (mPaused && !isFinishing()) {
1390 Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
1393 mFatalErrorHandler.handleFatalError(FatalErrorHandler.Reason.CANNOT_CONNECT_TO_CAMERA);
1399 public void onNewIntentTasks(Intent intent) {
1400 onModeSelected(getModeIndex());
1404 public void onCreateTasks(Bundle state) {
1405 Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start();
1406 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1407 mOnCreateTime = System.currentTimeMillis();
1408 mAppContext = getApplicationContext();
1409 mMainHandler = new MainHandler(this, getMainLooper());
1410 mLocationManager = new LocationManager(mAppContext);
1411 mOrientationManager = new OrientationManagerImpl(this, mMainHandler);
1412 mSettingsManager = getServices().getSettingsManager();
1413 mSoundPlayer = new SoundPlayer(mAppContext);
1414 mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver(),
1415 getServices().getMemoryManager());
1416 mFatalErrorHandler = new FatalErrorHandlerImpl(this);
1419 if (!Glide.isSetup()) {
1420 Context context = getAndroidContext();
1421 Glide.setup(new GlideBuilder(context)
1422 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888)
1423 .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
1425 Glide glide = Glide.get(context);
1427 // As a camera we will use a large amount of memory
1428 // for displaying images.
1429 glide.setMemoryCategory(MemoryCategory.HIGH);
1431 Size maxDisplaySize = GlideFilmstripManager.getMaxImageDisplaySize();
1433 // Prefill glides bitmap pool to prevent excessive jank
1434 // when loading large images.
1435 glide.preFillBitmapPool(
1436 new PreFillType.Builder(
1437 maxDisplaySize.width(),
1438 maxDisplaySize.height())
1440 // It's more important for jank and GC to have
1441 // A larger weight of max texture size images than
1442 // media store sized images.
1443 new PreFillType.Builder(
1444 GlideFilmstripManager.MEDIASTORE_THUMB_SIZE.width(),
1445 GlideFilmstripManager.MEDIASTORE_THUMB_SIZE.height()));
1447 profile.mark("Glide.setup");
1449 mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance();
1451 mOneCameraOpener = OneCameraModule.provideOneCameraOpener(
1454 mActiveCameraDeviceTracker,
1455 ResolutionUtil.getDisplayMetrics(this));
1456 mOneCameraManager = OneCameraModule.provideOneCameraManager();
1457 } catch (OneCameraException e) {
1458 // Log error and continue start process while showing error dialog..
1459 Log.e(TAG, "Creating camera manager failed.", e);
1460 mFatalErrorHandler.onGenericCameraAccessFailure();
1462 profile.mark("OneCameraManager.get");
1464 mCameraController = new CameraController(mAppContext, this, mMainHandler,
1465 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1466 CameraAgentFactory.CameraApi.API_1),
1467 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1468 CameraAgentFactory.CameraApi.AUTO),
1469 mActiveCameraDeviceTracker);
1470 mCameraController.setCameraExceptionHandler(
1471 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
1473 // TODO: Try to move all the resources allocation to happen as soon as
1474 // possible so we can call module.init() at the earliest time.
1475 mModuleManager = new ModuleManagerImpl();
1477 ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig);
1479 AppUpgrader appUpgrader = new AppUpgrader(this);
1480 appUpgrader.upgrade(mSettingsManager);
1481 Keys.setDefaults(mSettingsManager, mAppContext);
1483 mResolutionSetting = new ResolutionSetting(mSettingsManager, mOneCameraManager,
1484 getContentResolver());
1486 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1487 // We suppress this flag via theme when drawing the system preview
1488 // background, but once we create activity here, reactivate to the
1489 // default value. The default is important for L, we don't want to
1490 // change app behavior, just starting background drawable layout.
1491 if (ApiHelper.isLOrHigher()) {
1492 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1496 setContentView(R.layout.activity_main);
1497 profile.mark("setContentView()");
1498 // A window background is set in styles.xml for the system to show a
1499 // drawable background with gray color and camera icon before the
1500 // activity is created. We set the background to null here to prevent
1501 // overdraw, all views must take care of drawing backgrounds if
1502 // necessary. This call to setBackgroundDrawable must occur after
1503 // setContentView, otherwise a background may be set again from the
1505 getWindow().setBackgroundDrawable(null);
1507 mActionBar = getActionBar();
1508 // set actionbar background to 100% or 50% transparent
1509 if (ApiHelper.isLOrHigher()) {
1510 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000));
1512 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
1515 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1516 mModeListView.init(mModuleManager.getSupportedModeIndexList());
1517 if (ApiHelper.HAS_ROTATION_ANIMATION) {
1518 setRotationAnimation();
1520 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1522 public void onVisibilityChanged(boolean visible) {
1523 mModeListVisible = visible;
1524 mCameraAppUI.setShutterButtonImportantToA11y(!visible);
1525 updatePreviewVisibility();
1529 // Check if this is in the secure camera mode.
1530 Intent intent = getIntent();
1531 String action = intent.getAction();
1532 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1533 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1534 mSecureCamera = true;
1536 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1539 if (mSecureCamera) {
1540 // Change the window flags so that secure camera can show when
1542 Window win = getWindow();
1543 WindowManager.LayoutParams params = win.getAttributes();
1544 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1545 win.setAttributes(params);
1547 // Filter for screen off so that we can finish secure camera
1548 // activity when screen is off.
1549 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1550 registerReceiver(mShutdownReceiver, filter_screen_off);
1552 // Filter for phone unlock so that we can finish secure camera
1553 // via this UI path:
1554 // 1. from secure lock screen, user starts secure camera
1555 // 2. user presses home button
1556 // 3. user unlocks phone
1557 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
1558 registerReceiver(mShutdownReceiver, filter_user_unlock);
1560 mCameraAppUI = new CameraAppUI(this,
1561 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1563 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1565 mAboveFilmstripControlLayout =
1566 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1568 // Add the session listener so we can track the session progress
1570 getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1571 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1572 mFilmstripController.setImageGap(
1573 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1574 profile.mark("Configure Camera UI");
1576 mPanoramaViewHelper = new PanoramaViewHelper(this);
1577 mPanoramaViewHelper.onCreate();
1579 ContentResolver appContentResolver = mAppContext.getContentResolver();
1580 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
1581 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
1582 new PhotoDataFactory());
1583 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
1584 new VideoDataFactory());
1585 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
1586 mPhotoItemFactory, mVideoItemFactory);
1587 mDataAdapter.setLocalDataListener(mFilmstripItemListener);
1589 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1592 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1593 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1594 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1595 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1598 setModuleFromModeIndex(getModeIndex());
1601 mCameraAppUI.prepareModuleUI();
1602 profile.mark("Init Current Module UI");
1603 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1604 profile.mark("Init CurrentModule");
1606 if (!mSecureCamera) {
1607 mFilmstripController.setDataAdapter(mDataAdapter);
1608 if (!isCaptureIntent()) {
1609 mDataAdapter.requestLoad(new Callback<Void>() {
1611 public void onCallback(Void result) {
1612 fillTemporarySessions();
1617 // Put a lock placeholder as the last image by setting its date to
1619 ImageView v = (ImageView) getLayoutInflater().inflate(
1620 R.layout.secure_album_placeholder, null);
1621 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
1622 v.setOnClickListener(new View.OnClickListener() {
1624 public void onClick(View view) {
1625 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1626 NavigationChange.InteractionCause.BUTTON);
1631 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1632 mDataAdapter = new FixedLastProxyAdapter(
1635 new PlaceholderItem(
1637 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
1638 v.getDrawable().getIntrinsicWidth(),
1639 v.getDrawable().getIntrinsicHeight()));
1640 // Flush out all the original data.
1641 mDataAdapter.clear();
1642 mFilmstripController.setDataAdapter(mDataAdapter);
1647 mLocalImagesObserver = new FilmstripContentObserver();
1648 mLocalVideosObserver = new FilmstripContentObserver();
1650 getContentResolver().registerContentObserver(
1651 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1652 mLocalImagesObserver);
1653 getContentResolver().registerContentObserver(
1654 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1655 mLocalVideosObserver);
1657 mMemoryManager = getServices().getMemoryManager();
1659 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1662 HashMap memoryData = mMemoryManager.queryMemory();
1663 UsageStatistics.instance().reportMemoryConsumed(memoryData,
1664 MemoryQuery.REPORT_LABEL_LAUNCH);
1668 mMotionManager = getServices().getMotionManager();
1670 mFirstRunDialog = new FirstRunDialog(this,
1671 getAndroidContext(),
1675 new FirstRunDialog.FirstRunDialogListener() {
1677 public void onFirstRunStateReady() {
1678 // Make sure additional preferences have the correct resolution selected
1679 CameraSettingsActivityHelper.verifyDefaults(getSettingsManager(),
1680 getAndroidContext());
1682 // Run normal resume tasks.
1687 public void onFirstRunDialogCancelled() {
1688 // App isn't functional until users finish first run dialog.
1689 // We need to finish here since users hit back button during
1690 // first run dialog (b/19593942).
1695 public void onCameraAccessException() {
1696 mFatalErrorHandler.onGenericCameraAccessFailure();
1703 * Get the current mode index from the Intent or from persistent
1706 private int getModeIndex() {
1708 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1709 int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1710 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1711 int captureIntentIndex =
1712 getResources().getInteger(R.integer.camera_mode_capture_intent);
1713 String intentAction = getIntent().getAction();
1714 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction)
1715 || MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) {
1716 modeIndex = videoIndex;
1717 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction)
1718 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1720 modeIndex = captureIntentIndex;
1721 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction)
1722 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction)
1723 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1724 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1725 Keys.KEY_CAMERA_MODULE_LAST_USED);
1727 // For upgraders who have not seen the aspect ratio selection screen,
1728 // we need to drop them back in the photo module and have them select
1730 // TODO: Move this to SettingsManager as an upgrade procedure.
1731 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1732 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
1733 modeIndex = photoIndex;
1736 // If the activity has not been started using an explicit intent,
1737 // read the module index from the last time the user changed modes
1738 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1739 Keys.KEY_STARTUP_MODULE_INDEX);
1740 if ((modeIndex == gcamIndex &&
1741 !GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) || modeIndex < 0) {
1742 modeIndex = photoIndex;
1749 * Call this whenever the mode drawer or filmstrip change the visibility
1752 private void updatePreviewVisibility() {
1753 if (mCurrentModule == null) {
1757 int visibility = getPreviewVisibility();
1758 mCameraAppUI.onPreviewVisiblityChanged(visibility);
1759 updatePreviewRendering(visibility);
1760 mCurrentModule.onPreviewVisibilityChanged(visibility);
1763 private void updatePreviewRendering(int visibility) {
1764 if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1765 mCameraAppUI.pausePreviewRendering();
1767 mCameraAppUI.resumePreviewRendering();
1771 private int getPreviewVisibility() {
1772 if (mFilmstripCoversPreview) {
1773 return ModuleController.VISIBILITY_HIDDEN;
1774 } else if (mModeListVisible){
1775 return ModuleController.VISIBILITY_COVERED;
1777 return ModuleController.VISIBILITY_VISIBLE;
1781 private void setRotationAnimation() {
1782 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1783 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1784 Window win = getWindow();
1785 WindowManager.LayoutParams winParams = win.getAttributes();
1786 winParams.rotationAnimation = rotationAnimation;
1787 win.setAttributes(winParams);
1791 public void onUserInteraction() {
1792 super.onUserInteraction();
1793 if (!isFinishing()) {
1794 keepScreenOnForAWhile();
1799 public boolean dispatchTouchEvent(MotionEvent ev) {
1800 boolean result = super.dispatchTouchEvent(ev);
1801 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1802 // Real deletion is postponed until the next user interaction after
1803 // the gesture that triggers deletion. Until real deletion is
1804 // performed, users can click the undo button to bring back the
1805 // image that they chose to delete.
1806 if (mPendingDeletion && !mIsUndoingDeletion) {
1814 public void onPauseTasks() {
1815 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1816 Profile profile = mProfiler.create("CameraActivity.onPause").start();
1819 * Save the last module index after all secure camera and icon launches,
1820 * not just on mode switches.
1822 * Right now we exclude capture intents from this logic, because we also
1823 * ignore the cross-Activity recovery logic in onStart for capture intents.
1825 if (!isCaptureIntent()) {
1826 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
1827 Keys.KEY_STARTUP_MODULE_INDEX,
1832 mCameraAppUI.hideCaptureIndicator();
1833 mFirstRunDialog.dismiss();
1835 // Delete photos that are pending deletion
1837 mCurrentModule.pause();
1838 mOrientationManager.pause();
1839 mPanoramaViewHelper.onPause();
1841 mLocalImagesObserver.setForegroundChangeListener(null);
1842 mLocalImagesObserver.setActivityPaused(true);
1843 mLocalVideosObserver.setActivityPaused(true);
1844 mPreloader.cancelAllLoads();
1847 mMotionManager.stop();
1849 // Always stop recording location when paused. Resume will start
1850 // location recording again if the location setting is on.
1851 mLocationManager.recordLocation(false);
1853 UsageStatistics.instance().backgrounded();
1855 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
1856 // button. Let's just kill the process.
1857 if (mCameraFatalError && !isFinishing()) {
1858 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
1861 // Close the camera and wait for the operation done.
1862 Log.v(TAG, "onPause closing camera");
1863 mCameraController.closeCamera(true);
1870 public void onResumeTasks() {
1873 // Show the dialog if necessary. The rest resume logic will be invoked
1874 // at the onFirstRunStateReady() callback.
1875 mFirstRunDialog.showIfNecessary();
1878 private void resume() {
1879 Profile profile = mProfiler.create("CameraActivity.resume").start();
1880 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1881 Log.v(TAG, "Build info: " + Build.DISPLAY);
1883 updateStorageSpaceAndHint(null);
1885 mLastLayoutOrientation = getResources().getConfiguration().orientation;
1887 // TODO: Handle this in OrientationManager.
1889 if (Settings.System.getInt(getContentResolver(),
1890 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1891 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1892 mAutoRotateScreen = false;
1894 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1895 mAutoRotateScreen = true;
1898 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and
1899 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
1900 // lockscreen onResume->onPause->onResume sequence.
1902 String action = getIntent().getAction();
1903 if (action == null) {
1904 source = ForegroundSource.UNKNOWN_SOURCE;
1907 case MediaStore.ACTION_IMAGE_CAPTURE:
1908 source = ForegroundSource.ACTION_IMAGE_CAPTURE;
1910 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
1911 // was UNKNOWN_SOURCE in Fishlake.
1912 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
1914 case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
1915 // was UNKNOWN_SOURCE in Fishlake.
1916 source = ForegroundSource.ACTION_VIDEO_CAMERA;
1918 case MediaStore.ACTION_VIDEO_CAPTURE:
1919 source = ForegroundSource.ACTION_VIDEO_CAPTURE;
1921 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
1922 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
1923 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
1925 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
1926 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
1928 case Intent.ACTION_MAIN:
1929 source = ForegroundSource.ACTION_MAIN;
1932 source = ForegroundSource.UNKNOWN_SOURCE;
1936 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode(),
1937 isKeyguardSecure(), isKeyguardLocked(),
1938 mStartupOnCreate, mExecutionStartNanoTime);
1940 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
1941 if (ApiHelper.isLOrHigher()) {
1942 // hide the up affordance for L devices, it's not very Materially
1943 mActionBar.setDisplayShowHomeEnabled(false);
1946 mOrientationManager.resume();
1948 mCurrentModule.hardResetSettings(mSettingsManager);
1951 mCurrentModule.resume();
1952 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
1953 NavigationChange.InteractionCause.BUTTON);
1954 setSwipingEnabled(true);
1955 profile.mark("mCurrentModule.resume");
1957 if (!mResetToPreviewOnResume) {
1958 FilmstripItem item = mDataAdapter.getItemAt(
1959 mFilmstripController.getCurrentAdapterIndex());
1961 mDataAdapter.refresh(item.getData().getUri());
1965 // The share button might be disabled to avoid double tapping.
1966 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1967 // Default is showing the preview, unless disabled by explicitly
1968 // starting an activity we want to return from to the filmstrip rather
1969 // than the preview.
1970 mResetToPreviewOnResume = true;
1972 if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1973 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1974 if (!mSecureCamera) {
1975 // If it's secure camera, requestLoad() should not be called
1976 // as it will load all the data.
1977 if (!mFilmstripVisible) {
1978 mDataAdapter.requestLoad(new Callback<Void>() {
1980 public void onCallback(Void result) {
1981 fillTemporarySessions();
1985 mDataAdapter.requestLoadNewPhotos();
1989 mLocalImagesObserver.setActivityPaused(false);
1990 mLocalVideosObserver.setActivityPaused(false);
1991 if (!mSecureCamera) {
1992 mLocalImagesObserver.setForegroundChangeListener(
1993 new FilmstripContentObserver.ChangeListener() {
1995 public void onChange() {
1996 mDataAdapter.requestLoadNewPhotos();
2001 keepScreenOnForAWhile();
2003 // Lights-out mode at all times.
2004 final View rootView = findViewById(R.id.activity_root_view);
2005 mLightsOutRunnable.run();
2006 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
2007 new OnSystemUiVisibilityChangeListener() {
2009 public void onSystemUiVisibilityChange(int visibility) {
2010 mMainHandler.removeCallbacks(mLightsOutRunnable);
2011 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
2016 mPanoramaViewHelper.onResume();
2017 profile.mark("mPanoramaViewHelper.onResume()");
2019 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
2020 // Enable location recording if the setting is on.
2021 final boolean locationRecordingEnabled =
2022 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
2023 mLocationManager.recordLocation(locationRecordingEnabled);
2025 final int previewVisibility = getPreviewVisibility();
2026 updatePreviewRendering(previewVisibility);
2028 mMotionManager.start();
2032 private void fillTemporarySessions() {
2033 if (mSecureCamera) {
2036 // There might be sessions still in flight (processed by our service).
2037 // Make sure they're added to the filmstrip.
2038 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
2042 public void onStartTasks() {
2043 mIsActivityRunning = true;
2044 mPanoramaViewHelper.onStart();
2047 * If we're starting after launching a different Activity (lockscreen),
2048 * we need to use the last mode used in the other Activity, and
2049 * not the old one from this Activity.
2051 * This needs to happen before CameraAppUI.resume() in order to set the
2052 * mode cover icon to the actual last mode used.
2054 * Right now we exclude capture intents from this logic.
2056 int modeIndex = getModeIndex();
2057 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
2058 onModeSelected(modeIndex);
2061 if (mResetToPreviewOnResume) {
2062 mCameraAppUI.resume();
2063 mResetToPreviewOnResume = false;
2068 protected void onStopTasks() {
2069 mIsActivityRunning = false;
2070 mPanoramaViewHelper.onStop();
2072 mLocationManager.disconnect();
2076 public void onDestroyTasks() {
2077 if (mSecureCamera) {
2078 unregisterReceiver(mShutdownReceiver);
2081 mSettingsManager.removeAllListeners();
2082 mCameraController.removeCallbackReceiver();
2083 mCameraController.setCameraExceptionHandler(null);
2084 getContentResolver().unregisterContentObserver(mLocalImagesObserver);
2085 getContentResolver().unregisterContentObserver(mLocalVideosObserver);
2086 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
2087 mCameraAppUI.onDestroy();
2088 mModeListView.setVisibilityChangedListener(null);
2089 mCameraController = null;
2090 mSettingsManager = null;
2091 mOrientationManager = null;
2092 mButtonManager = null;
2093 mSoundPlayer.release();
2094 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
2095 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
2099 public void onConfigurationChanged(Configuration config) {
2100 super.onConfigurationChanged(config);
2101 Log.v(TAG, "onConfigurationChanged");
2102 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
2106 if (mLastLayoutOrientation != config.orientation) {
2107 mLastLayoutOrientation = config.orientation;
2108 mCurrentModule.onLayoutOrientationChanged(
2109 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
2114 public boolean onKeyDown(int keyCode, KeyEvent event) {
2115 if (!mFilmstripVisible) {
2116 if (mCurrentModule.onKeyDown(keyCode, event)) {
2119 // Prevent software keyboard or voice search from showing up.
2120 if (keyCode == KeyEvent.KEYCODE_SEARCH
2121 || keyCode == KeyEvent.KEYCODE_MENU) {
2122 if (event.isLongPress()) {
2128 return super.onKeyDown(keyCode, event);
2132 public boolean onKeyUp(int keyCode, KeyEvent event) {
2133 if (!mFilmstripVisible) {
2134 // If a module is in the middle of capture, it should
2135 // consume the key event.
2136 if (mCurrentModule.onKeyUp(keyCode, event)) {
2138 } else if (keyCode == KeyEvent.KEYCODE_MENU
2139 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2140 // Let the mode list view consume the event.
2141 mCameraAppUI.openModeList();
2143 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2144 mCameraAppUI.showFilmstrip();
2148 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2149 mFilmstripController.goToNextItem();
2151 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2152 boolean wentToPrevious = mFilmstripController.goToPreviousItem();
2153 if (!wentToPrevious) {
2154 // at beginning of filmstrip, hide and go back to preview
2155 mCameraAppUI.hideFilmstrip();
2160 return super.onKeyUp(keyCode, event);
2164 public void onBackPressed() {
2165 if (!mCameraAppUI.onBackPressed()) {
2166 if (!mCurrentModule.onBackPressed()) {
2167 super.onBackPressed();
2173 public boolean isAutoRotateScreen() {
2174 // TODO: Move to OrientationManager.
2175 return mAutoRotateScreen;
2179 public boolean onCreateOptionsMenu(Menu menu) {
2180 MenuInflater inflater = getMenuInflater();
2181 inflater.inflate(R.menu.filmstrip_menu, menu);
2182 mActionBarMenu = menu;
2184 // add a button for launching the gallery
2185 if (mGalleryIntent != null) {
2186 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
2187 if (appName != null) {
2188 MenuItem menuItem = menu.add(appName);
2189 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2190 menuItem.setIntent(mGalleryIntent);
2192 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
2193 if (galleryLogo != null) {
2194 menuItem.setIcon(galleryLogo);
2199 return super.onCreateOptionsMenu(menu);
2203 public boolean onPrepareOptionsMenu(Menu menu) {
2204 if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
2205 // Compatibility pre-L: launching new activities right above
2206 // lockscreen does not reliably work, only show help if not secure
2207 menu.removeItem(R.id.action_help_and_feedback);
2210 return super.onPrepareOptionsMenu(menu);
2213 protected long getStorageSpaceBytes() {
2214 synchronized (mStorageSpaceLock) {
2215 return mStorageSpaceBytes;
2219 protected interface OnStorageUpdateDoneListener {
2220 public void onStorageUpdateDone(long bytes);
2223 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
2225 * We execute disk operations on a background thread in order to
2226 * free up the UI thread. Synchronizing on the lock below ensures
2227 * that when getStorageSpaceBytes is called, the main thread waits
2228 * until this method has completed.
2230 * However, .execute() does not ensure this execution block will be
2231 * run right away (.execute() schedules this AsyncTask for sometime
2232 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
2233 * tries to execute the task in parellel with other AsyncTasks, but
2234 * there's still no guarantee).
2235 * e.g. don't call this then immediately call getStorageSpaceBytes().
2236 * Instead, pass in an OnStorageUpdateDoneListener.
2238 (new AsyncTask<Void, Void, Long>() {
2240 protected Long doInBackground(Void ... arg) {
2241 synchronized (mStorageSpaceLock) {
2242 mStorageSpaceBytes = Storage.getAvailableSpace();
2243 return mStorageSpaceBytes;
2248 protected void onPostExecute(Long bytes) {
2249 updateStorageHint(bytes);
2250 // This callback returns after I/O to check disk, so we could be
2251 // pausing and shutting down. If so, don't bother invoking.
2252 if (callback != null && !mPaused) {
2253 callback.onStorageUpdateDone(bytes);
2255 Log.v(TAG, "ignoring storage callback after activity pause");
2258 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2261 protected void updateStorageHint(long storageSpace) {
2262 if (!mIsActivityRunning) {
2266 String message = null;
2267 if (storageSpace == Storage.UNAVAILABLE) {
2268 message = getString(R.string.no_storage);
2269 } else if (storageSpace == Storage.PREPARING) {
2270 message = getString(R.string.preparing_sd);
2271 } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2272 message = getString(R.string.access_sd_fail);
2273 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2274 message = getString(R.string.spaceIsLow_content);
2277 if (message != null) {
2278 Log.w(TAG, "Storage warning: " + message);
2279 if (mStorageHint == null) {
2280 mStorageHint = OnScreenHint.makeText(message);
2282 mStorageHint.setText(message);
2284 mStorageHint.show();
2285 UsageStatistics.instance().storageWarning(storageSpace);
2287 // Disable all user interactions,
2288 mCameraAppUI.setDisableAllUserInteractions(true);
2289 } else if (mStorageHint != null) {
2290 mStorageHint.cancel();
2291 mStorageHint = null;
2293 // Re-enable all user interactions.
2294 mCameraAppUI.setDisableAllUserInteractions(false);
2298 protected void setResultEx(int resultCode) {
2299 mResultCodeForTesting = resultCode;
2300 setResult(resultCode);
2303 protected void setResultEx(int resultCode, Intent data) {
2304 mResultCodeForTesting = resultCode;
2305 mResultDataForTesting = data;
2306 setResult(resultCode, data);
2309 public int getResultCode() {
2310 return mResultCodeForTesting;
2313 public Intent getResultData() {
2314 return mResultDataForTesting;
2317 public boolean isSecureCamera() {
2318 return mSecureCamera;
2322 public boolean isPaused() {
2327 public int getPreferredChildModeIndex(int modeIndex) {
2328 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2329 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2330 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) {
2331 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2338 public void onModeSelected(int modeIndex) {
2339 if (mCurrentModeIndex == modeIndex) {
2343 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2344 // Record last used camera mode for quick switching
2345 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2346 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2347 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2348 Keys.KEY_CAMERA_MODULE_LAST_USED,
2352 closeModule(mCurrentModule);
2354 // Select the correct module index from the mode switcher index.
2355 modeIndex = getPreferredChildModeIndex(modeIndex);
2356 setModuleFromModeIndex(modeIndex);
2358 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2359 mCameraAppUI.addShutterListener(mCurrentModule);
2360 openModule(mCurrentModule);
2361 // Store the module index so we can use it the next time the Camera
2363 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2364 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2368 * Shows the settings dialog.
2371 public void onSettingsSelected() {
2372 UsageStatistics.instance().controlUsed(
2373 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2374 Intent intent = new Intent(this, CameraSettingsActivity.class);
2375 startActivity(intent);
2379 public void freezeScreenUntilPreviewReady() {
2380 mCameraAppUI.freezeScreenUntilPreviewReady();
2384 public int getModuleId(int modeIndex) {
2385 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2386 if (agent == null) {
2389 return agent.getModuleId();
2393 * Sets the mCurrentModuleIndex, creates a new module instance for the given
2394 * index an sets it as mCurrentModule.
2396 private void setModuleFromModeIndex(int modeIndex) {
2397 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2398 if (agent == null) {
2401 if (!agent.requestAppForCamera()) {
2402 mCameraController.closeCamera(true);
2404 mCurrentModeIndex = agent.getModuleId();
2405 mCurrentModule = (CameraModule) agent.createModule(this, getIntent());
2409 public SettingsManager getSettingsManager() {
2410 return mSettingsManager;
2414 public ResolutionSetting getResolutionSetting() {
2415 return mResolutionSetting;
2419 public CameraServices getServices() {
2420 return CameraServicesImpl.instance();
2424 public FatalErrorHandler getFatalErrorHandler() {
2425 return mFatalErrorHandler;
2428 public List<String> getSupportedModeNames() {
2429 List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2430 List<String> supported = new ArrayList<String>();
2432 for (Integer modeIndex : indices) {
2433 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2434 if (name != null && !name.equals("")) {
2435 supported.add(name);
2442 public ButtonManager getButtonManager() {
2443 if (mButtonManager == null) {
2444 mButtonManager = new ButtonManager(this);
2446 return mButtonManager;
2450 public SoundPlayer getSoundPlayer() {
2451 return mSoundPlayer;
2455 * Launches an ACTION_EDIT intent for the given local data item. If
2456 * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2457 * the user start either the tiny planet editor or another photo editor.
2459 * @param data The data item to edit.
2461 public void launchEditor(FilmstripItem data) {
2462 Intent intent = new Intent(Intent.ACTION_EDIT)
2463 .setDataAndType(data.getData().getUri(), data.getData().getMimeType())
2464 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2466 launchActivityByIntent(intent);
2467 } catch (ActivityNotFoundException e) {
2468 final String msgEditWith = getResources().getString(R.string.edit_with);
2469 launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2474 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2475 super.onCreateContextMenu(menu, v, menuInfo);
2477 MenuInflater inflater = getMenuInflater();
2478 inflater.inflate(R.menu.filmstrip_context_menu, menu);
2482 public boolean onContextItemSelected(MenuItem item) {
2483 switch (item.getItemId()) {
2484 case R.id.tiny_planet_editor:
2485 mMyFilmstripBottomControlListener.onTinyPlanet();
2487 case R.id.photo_editor:
2488 mMyFilmstripBottomControlListener.onEdit();
2495 * Launch the tiny planet editor.
2497 * @param data The data must be a 360 degree stereographically mapped
2498 * panoramic image. It will not be modified, instead a new item
2499 * with the result will be added to the filmstrip.
2501 public void launchTinyPlanetEditor(FilmstripItem data) {
2502 TinyPlanetFragment fragment = new TinyPlanetFragment();
2503 Bundle bundle = new Bundle();
2504 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString());
2505 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle());
2506 fragment.setArguments(bundle);
2507 fragment.show(getFragmentManager(), "tiny_planet");
2511 * Returns what UI mode (capture mode or filmstrip) we are in.
2512 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2514 private int currentUserInterfaceMode() {
2515 int mode = NavigationChange.Mode.UNKNOWN_MODE;
2516 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2517 mode = NavigationChange.Mode.PHOTO_CAPTURE;
2519 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2520 mode = NavigationChange.Mode.VIDEO_CAPTURE;
2522 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2523 mode = NavigationChange.Mode.LENS_BLUR;
2525 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2526 mode = NavigationChange.Mode.HDR_PLUS;
2528 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2529 mode = NavigationChange.Mode.PHOTO_SPHERE;
2531 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2532 mode = NavigationChange.Mode.PANORAMA;
2534 if (mFilmstripVisible) {
2535 mode = NavigationChange.Mode.FILMSTRIP;
2540 private void openModule(CameraModule module) {
2541 module.init(this, isSecureCamera(), isCaptureIntent());
2542 module.hardResetSettings(mSettingsManager);
2543 // Hide accessibility zoom UI by default. Modules will enable it themselves if required.
2544 getCameraAppUI().hideAccessibilityZoomUI();
2547 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2548 NavigationChange.InteractionCause.BUTTON);
2549 updatePreviewVisibility();
2553 private void closeModule(CameraModule module) {
2555 mCameraAppUI.clearModuleUI();
2558 private void performDeletion() {
2559 if (!mPendingDeletion) {
2562 hideUndoDeletionBar(false);
2563 mDataAdapter.executeDeletion();
2566 public void showUndoDeletionBar() {
2567 if (mPendingDeletion) {
2570 Log.v(TAG, "showing undo bar");
2571 mPendingDeletion = true;
2572 if (mUndoDeletionBar == null) {
2573 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2574 mAboveFilmstripControlLayout, true);
2575 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2576 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2577 button.setOnClickListener(new View.OnClickListener() {
2579 public void onClick(View view) {
2580 mDataAdapter.undoDeletion();
2581 hideUndoDeletionBar(true);
2584 // Setting undo bar clickable to avoid touch events going through
2585 // the bar to the buttons (eg. edit button, etc) underneath the bar.
2586 mUndoDeletionBar.setClickable(true);
2587 // When there is user interaction going on with the undo button, we
2588 // do not want to hide the undo bar.
2589 button.setOnTouchListener(new View.OnTouchListener() {
2591 public boolean onTouch(View v, MotionEvent event) {
2592 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2593 mIsUndoingDeletion = true;
2594 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2595 mIsUndoingDeletion = false;
2601 mUndoDeletionBar.setAlpha(0f);
2602 mUndoDeletionBar.setVisibility(View.VISIBLE);
2603 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2606 private void hideUndoDeletionBar(boolean withAnimation) {
2607 Log.v(TAG, "Hiding undo deletion bar");
2608 mPendingDeletion = false;
2609 if (mUndoDeletionBar != null) {
2610 if (withAnimation) {
2611 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2612 .setListener(new Animator.AnimatorListener() {
2614 public void onAnimationStart(Animator animation) {
2619 public void onAnimationEnd(Animator animation) {
2620 mUndoDeletionBar.setVisibility(View.GONE);
2624 public void onAnimationCancel(Animator animation) {
2629 public void onAnimationRepeat(Animator animation) {
2634 mUndoDeletionBar.setVisibility(View.GONE);
2640 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2643 * @param enable {@code true} to enable swipe.
2645 public void setSwipingEnabled(boolean enable) {
2646 // TODO: Bring back the functionality.
2647 if (isCaptureIntent()) {
2648 // lockPreview(true);
2650 // lockPreview(!enable);
2654 // Accessor methods for getting latency times used in performance testing
2655 public long getFirstPreviewTime() {
2656 if (mCurrentModule instanceof PhotoModule) {
2657 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2658 if (coverHiddenTime != -1) {
2659 return coverHiddenTime - mOnCreateTime;
2665 public long getAutoFocusTime() {
2666 return (mCurrentModule instanceof PhotoModule) ?
2667 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2670 public long getShutterLag() {
2671 return (mCurrentModule instanceof PhotoModule) ?
2672 ((PhotoModule) mCurrentModule).mShutterLag : -1;
2675 public long getShutterToPictureDisplayedTime() {
2676 return (mCurrentModule instanceof PhotoModule) ?
2677 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2680 public long getPictureDisplayedToJpegCallbackTime() {
2681 return (mCurrentModule instanceof PhotoModule) ?
2682 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2685 public long getJpegCallbackFinishTime() {
2686 return (mCurrentModule instanceof PhotoModule) ?
2687 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2690 public long getCaptureStartTime() {
2691 return (mCurrentModule instanceof PhotoModule) ?
2692 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2695 public boolean isRecording() {
2696 return (mCurrentModule instanceof VideoModule) ?
2697 ((VideoModule) mCurrentModule).isRecording() : false;
2700 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2701 return mCameraController;
2704 // For debugging purposes only.
2705 public CameraModule getCurrentModule() {
2706 return mCurrentModule;
2710 public void showTutorial(AbstractTutorialOverlay tutorial) {
2711 mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2715 public void finishActivityWithIntentCompleted(Intent resultIntent) {
2716 finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent);
2720 public void finishActivityWithIntentCanceled() {
2721 finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent());
2724 private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) {
2725 mResultCodeForTesting = resultCode;
2726 mResultDataForTesting = resultIntent;
2727 setResult(resultCode, resultIntent);
2731 private void keepScreenOnForAWhile() {
2732 if (mKeepScreenOn) {
2735 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2736 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2737 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2740 private void resetScreenOn() {
2741 mKeepScreenOn = false;
2742 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2743 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2747 * @return {@code true} if the Gallery is launched successfully.
2749 private boolean startGallery() {
2750 if (mGalleryIntent == null) {
2754 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2755 NavigationChange.InteractionCause.BUTTON);
2756 Intent startGalleryIntent = new Intent(mGalleryIntent);
2757 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
2758 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex);
2759 if (currentFilmstripItem != null) {
2760 GalleryHelper.setContentUri(startGalleryIntent,
2761 currentFilmstripItem.getData().getUri());
2763 launchActivityByIntent(startGalleryIntent);
2764 } catch (ActivityNotFoundException e) {
2765 Log.w(TAG, "Failed to launch gallery activity, closing");
2770 private void setNfcBeamPushUriFromData(FilmstripItem data) {
2771 final Uri uri = data.getData().getUri();
2772 if (uri != Uri.EMPTY) {
2773 mNfcPushUris[0] = uri;
2775 mNfcPushUris[0] = null;
2780 * Updates the visibility of the filmstrip bottom controls and action bar.
2782 private void updateUiByData(final int index) {
2783 final FilmstripItem currentData = mDataAdapter.getItemAt(index);
2784 if (currentData == null) {
2785 Log.w(TAG, "Current data ID not found.");
2786 hideSessionProgress();
2789 updateActionBarMenu(currentData);
2791 /* Bottom controls. */
2792 updateBottomControlsByData(currentData);
2794 if (isSecureCamera()) {
2795 // We cannot show buttons in secure camera since go to other
2796 // activities might create a security hole.
2797 mCameraAppUI.getFilmstripBottomControls().hideControls();
2801 setNfcBeamPushUriFromData(currentData);
2803 if (!mDataAdapter.isMetadataUpdatedAt(index)) {
2804 mDataAdapter.updateMetadataAt(index);
2809 * Updates the bottom controls based on the data.
2811 private void updateBottomControlsByData(final FilmstripItem currentData) {
2813 final CameraAppUI.BottomPanel filmstripBottomPanel =
2814 mCameraAppUI.getFilmstripBottomControls();
2815 filmstripBottomPanel.showControls();
2816 filmstripBottomPanel.setEditButtonVisibility(
2817 currentData.getAttributes().canEdit());
2818 filmstripBottomPanel.setShareButtonVisibility(
2819 currentData.getAttributes().canShare());
2820 filmstripBottomPanel.setDeleteButtonVisibility(
2821 currentData.getAttributes().canDelete());
2825 Uri contentUri = currentData.getData().getUri();
2826 CaptureSessionManager sessionManager = getServices()
2827 .getCaptureSessionManager();
2829 if (sessionManager.hasErrorMessage(contentUri)) {
2830 showProcessError(sessionManager.getErrorMessageId(contentUri));
2832 filmstripBottomPanel.hideProgressError();
2833 CaptureSession session = sessionManager.getSession(contentUri);
2835 if (session != null) {
2836 int sessionProgress = session.getProgress();
2838 if (sessionProgress < 0) {
2839 hideSessionProgress();
2841 int progressMessageId = session.getProgressMessageId();
2842 showSessionProgress(progressMessageId);
2843 updateSessionProgress(sessionProgress);
2846 hideSessionProgress();
2852 // We need to add this to a separate DB.
2853 final int viewButtonVisibility;
2854 if (currentData.getMetadata().isUsePanoramaViewer()) {
2855 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2856 } else if (currentData.getMetadata().isHasRgbzData()) {
2857 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2859 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2862 filmstripBottomPanel.setTinyPlanetEnabled(
2863 currentData.getMetadata().isPanorama360());
2864 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2867 private void showDetailsDialog(int index) {
2868 final FilmstripItem data = mDataAdapter.getItemAt(index);
2872 Optional<MediaDetails> details = data.getMediaDetails();
2873 if (!details.isPresent()) {
2876 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get());
2877 detailDialog.show();
2878 UsageStatistics.instance().mediaInteraction(
2879 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS,
2880 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index));
2884 * Show or hide action bar items depending on current data type.
2886 private void updateActionBarMenu(FilmstripItem data) {
2887 if (mActionBarMenu == null) {
2891 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2892 if (detailsMenuItem == null) {
2896 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo();
2897 detailsMenuItem.setVisible(showDetails);