2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 package com.android.camera;
20 import android.animation.Animator;
21 import android.app.ActionBar;
22 import android.app.Dialog;
23 import android.content.ActivityNotFoundException;
24 import android.content.BroadcastReceiver;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.ActivityInfo;
30 import android.content.res.Configuration;
31 import android.graphics.Bitmap;
32 import android.graphics.Matrix;
33 import android.graphics.RectF;
34 import android.graphics.SurfaceTexture;
35 import android.graphics.drawable.ColorDrawable;
36 import android.graphics.drawable.Drawable;
37 import android.net.Uri;
38 import android.nfc.NfcAdapter;
39 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
40 import android.nfc.NfcEvent;
41 import android.os.AsyncTask;
42 import android.os.Build;
43 import android.os.Bundle;
44 import android.os.Handler;
45 import android.os.Looper;
46 import android.os.Message;
47 import android.provider.MediaStore;
48 import android.provider.Settings;
49 import android.text.TextUtils;
50 import android.util.CameraPerformanceTracker;
51 import android.view.ContextMenu;
52 import android.view.ContextMenu.ContextMenuInfo;
53 import android.view.KeyEvent;
54 import android.view.Menu;
55 import android.view.MenuInflater;
56 import android.view.MenuItem;
57 import android.view.MotionEvent;
58 import android.view.View;
59 import android.view.View.OnSystemUiVisibilityChangeListener;
60 import android.view.ViewGroup;
61 import android.view.Window;
62 import android.view.WindowManager;
63 import android.widget.FrameLayout;
64 import android.widget.ImageView;
65 import android.widget.ShareActionProvider;
67 import com.android.camera.app.AppController;
68 import com.android.camera.app.CameraAppUI;
69 import com.android.camera.app.CameraController;
70 import com.android.camera.app.CameraProvider;
71 import com.android.camera.app.CameraServices;
72 import com.android.camera.app.CameraServicesImpl;
73 import com.android.camera.app.FirstRunDialog;
74 import com.android.camera.app.LocationManager;
75 import com.android.camera.app.MemoryManager;
76 import com.android.camera.app.MemoryQuery;
77 import com.android.camera.app.ModuleManager;
78 import com.android.camera.app.ModuleManagerImpl;
79 import com.android.camera.app.MotionManager;
80 import com.android.camera.app.OrientationManager;
81 import com.android.camera.app.OrientationManagerImpl;
82 import com.android.camera.data.CameraFilmstripDataAdapter;
83 import com.android.camera.data.FilmstripContentObserver;
84 import com.android.camera.data.FilmstripItem;
85 import com.android.camera.data.FilmstripItemData;
86 import com.android.camera.data.FilmstripItemType;
87 import com.android.camera.data.FilmstripItemUtils;
88 import com.android.camera.data.FixedLastProxyAdapter;
89 import com.android.camera.data.GlideFilmstripManager;
90 import com.android.camera.data.LocalFilmstripDataAdapter;
91 import com.android.camera.data.LocalFilmstripDataAdapter.FilmstripItemListener;
92 import com.android.camera.data.MediaDetails;
93 import com.android.camera.data.MetadataLoader;
94 import com.android.camera.data.PhotoDataFactory;
95 import com.android.camera.data.PhotoItem;
96 import com.android.camera.data.PhotoItemFactory;
97 import com.android.camera.data.PlaceholderItem;
98 import com.android.camera.data.SessionItem;
99 import com.android.camera.data.VideoDataFactory;
100 import com.android.camera.data.VideoItemFactory;
101 import com.android.camera.debug.Log;
102 import com.android.camera.filmstrip.FilmstripContentPanel;
103 import com.android.camera.filmstrip.FilmstripController;
104 import com.android.camera.module.ModuleController;
105 import com.android.camera.module.ModulesInfo;
106 import com.android.camera.one.OneCameraException;
107 import com.android.camera.one.OneCameraManager;
108 import com.android.camera.session.CaptureSession;
109 import com.android.camera.session.CaptureSessionManager;
110 import com.android.camera.session.CaptureSessionManager.SessionListener;
111 import com.android.camera.settings.AppUpgrader;
112 import com.android.camera.settings.CameraSettingsActivity;
113 import com.android.camera.settings.Keys;
114 import com.android.camera.settings.ResolutionSetting;
115 import com.android.camera.settings.ResolutionUtil;
116 import com.android.camera.settings.SettingsManager;
117 import com.android.camera.stats.UsageStatistics;
118 import com.android.camera.tinyplanet.TinyPlanetFragment;
119 import com.android.camera.ui.AbstractTutorialOverlay;
120 import com.android.camera.ui.DetailsDialog;
121 import com.android.camera.ui.MainActivityLayout;
122 import com.android.camera.ui.ModeListView;
123 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
124 import com.android.camera.ui.PreviewStatusListener;
125 import com.android.camera.util.ApiHelper;
126 import com.android.camera.util.Callback;
127 import com.android.camera.util.CameraUtil;
128 import com.android.camera.util.GalleryHelper;
129 import com.android.camera.util.GcamHelper;
130 import com.android.camera.util.GoogleHelpHelper;
131 import com.android.camera.util.IntentHelper;
132 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
133 import com.android.camera.util.QuickActivity;
134 import com.android.camera.util.ReleaseHelper;
135 import com.android.camera.widget.FilmstripView;
136 import com.android.camera.widget.Preloader;
137 import com.android.camera2.R;
138 import com.android.ex.camera2.portability.CameraAgent;
139 import com.android.ex.camera2.portability.CameraAgentFactory;
140 import com.android.ex.camera2.portability.CameraExceptionHandler;
141 import com.android.ex.camera2.portability.CameraSettings;
142 import com.bumptech.glide.Glide;
143 import com.bumptech.glide.GlideBuilder;
144 import com.bumptech.glide.MemoryCategory;
145 import com.bumptech.glide.load.DecodeFormat;
146 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
147 import com.bumptech.glide.load.engine.prefill.PreFillType;
148 import com.google.common.base.Optional;
149 import com.google.common.logging.eventprotos;
150 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
151 import com.google.common.logging.eventprotos.MediaInteraction;
152 import com.google.common.logging.eventprotos.NavigationChange;
155 import java.lang.ref.WeakReference;
156 import java.util.ArrayList;
157 import java.util.HashMap;
158 import java.util.List;
160 public class CameraActivity extends QuickActivity
161 implements AppController, CameraAgent.CameraOpenCallback,
162 ShareActionProvider.OnShareTargetSelectedListener {
164 private static final Log.Tag TAG = new Log.Tag("CameraActivity");
166 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
167 "android.media.action.STILL_IMAGE_CAMERA_SECURE";
168 public static final String ACTION_IMAGE_CAPTURE_SECURE =
169 "android.media.action.IMAGE_CAPTURE_SECURE";
171 // The intent extra for camera from secure lock screen. True if the gallery
172 // should only show newly captured pictures. sSecureAlbumId does not
173 // increment. This is used when switching between camera, camcorder, and
174 // panorama. If the extra is not set, it is in the normal camera mode.
175 public static final String SECURE_CAMERA_EXTRA = "secure_camera";
177 public static final String MODULE_SCOPE_PREFIX = "_preferences_module_";
178 public static final String CAMERA_SCOPE_PREFIX = "_preferences_camera_";
180 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
181 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
182 /** Load metadata for 10 items ahead of our current. */
183 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
185 /** Should be used wherever a context is needed. */
186 private Context mAppContext;
189 * Camera fatal error handling:
190 * 1) Present error dialog to guide users to exit the app.
191 * 2) If users hit home button, onPause should just call finish() to exit the app.
193 private boolean mCameraFatalError = false;
196 * Whether onResume should reset the view to the preview.
198 private boolean mResetToPreviewOnResume = true;
201 * This data adapter is used by FilmStripView.
203 private VideoItemFactory mVideoItemFactory;
204 private PhotoItemFactory mPhotoItemFactory;
205 private LocalFilmstripDataAdapter mDataAdapter;
207 private OneCameraManager mCameraManager;
208 private SettingsManager mSettingsManager;
209 private ResolutionSetting mResolutionSetting;
210 private ModeListView mModeListView;
211 private boolean mModeListVisible = false;
212 private int mCurrentModeIndex;
213 private CameraModule mCurrentModule;
214 private ModuleManagerImpl mModuleManager;
215 private FrameLayout mAboveFilmstripControlLayout;
216 private FilmstripController mFilmstripController;
217 private boolean mFilmstripVisible;
218 /** Whether the filmstrip fully covers the preview. */
219 private boolean mFilmstripCoversPreview = false;
220 private int mResultCodeForTesting;
221 private Intent mResultDataForTesting;
222 private OnScreenHint mStorageHint;
223 private final Object mStorageSpaceLock = new Object();
224 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
225 private boolean mAutoRotateScreen;
226 private boolean mSecureCamera;
227 private OrientationManagerImpl mOrientationManager;
228 private LocationManager mLocationManager;
229 private ButtonManager mButtonManager;
230 private Handler mMainHandler;
231 private PanoramaViewHelper mPanoramaViewHelper;
232 private ActionBar mActionBar;
233 private ViewGroup mUndoDeletionBar;
234 private boolean mIsUndoingDeletion = false;
235 private boolean mIsActivityRunning = false;
237 private final Uri[] mNfcPushUris = new Uri[1];
239 private FilmstripContentObserver mLocalImagesObserver;
240 private FilmstripContentObserver mLocalVideosObserver;
242 private boolean mPendingDeletion = false;
244 private CameraController mCameraController;
245 private boolean mPaused;
246 private CameraAppUI mCameraAppUI;
248 private Intent mGalleryIntent;
249 private long mOnCreateTime;
251 private Menu mActionBarMenu;
252 private Preloader<Integer, AsyncTask> mPreloader;
254 /** Can be used to play custom sounds. */
255 private SoundPlayer mSoundPlayer;
257 private static final int LIGHTS_OUT_DELAY_MS = 4000;
258 private final int BASE_SYS_UI_VISIBILITY =
259 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
260 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
261 private final Runnable mLightsOutRunnable = new Runnable() {
264 getWindow().getDecorView().setSystemUiVisibility(
265 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE);
268 private MemoryManager mMemoryManager;
269 private MotionManager mMotionManager;
271 /** First run dialog */
272 private FirstRunDialog mFirstRunDialog;
275 public CameraAppUI getCameraAppUI() {
280 public ModuleManager getModuleManager() {
281 return mModuleManager;
285 * Close activity when secure app passes lock screen or screen turns
288 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
290 public void onReceive(Context context, Intent intent) {
296 * Whether the screen is kept turned on.
298 private boolean mKeepScreenOn;
299 private int mLastLayoutOrientation;
300 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
301 new CameraAppUI.BottomPanel.Listener() {
304 * If the current photo is a photo sphere, this will launch the
305 * Photo Sphere panorama viewer.
308 public void onExternalViewer() {
309 if (mPanoramaViewHelper == null) {
312 final FilmstripItem data = getCurrentLocalData();
314 Log.w(TAG, "Cannot open null data.");
317 final Uri contentUri = data.getData().getUri();
318 if (contentUri == Uri.EMPTY) {
319 Log.w(TAG, "Cannot open empty URL.");
323 if (data.getMetadata().isUsePanoramaViewer()) {
324 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
325 } else if (data.getMetadata().isHasRgbzData()) {
326 mPanoramaViewHelper.showRgbz(contentUri);
327 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
328 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
329 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
330 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false);
331 mCameraAppUI.clearClingForViewer(
332 CameraAppUI.BottomPanel.VIEWER_REFOCUS);
338 public void onEdit() {
339 FilmstripItem data = getCurrentLocalData();
341 Log.w(TAG, "Cannot edit null data.");
344 final int currentDataId = getCurrentDataId();
345 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
347 MediaInteraction.InteractionType.EDIT,
348 NavigationChange.InteractionCause.BUTTON,
349 fileAgeFromAdapterAtIndex(currentDataId));
354 public void onTinyPlanet() {
355 FilmstripItem data = getCurrentLocalData();
357 Log.w(TAG, "Cannot edit tiny planet on null data.");
360 launchTinyPlanetEditor(data);
364 public void onDelete() {
365 final int currentDataId = getCurrentDataId();
366 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
368 MediaInteraction.InteractionType.DELETE,
369 NavigationChange.InteractionCause.BUTTON,
370 fileAgeFromAdapterAtIndex(currentDataId));
371 removeItemAt(currentDataId);
375 public void onShare() {
376 final FilmstripItem data = getCurrentLocalData();
378 Log.w(TAG, "Cannot share null data.");
382 final int currentDataId = getCurrentDataId();
383 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
385 MediaInteraction.InteractionType.SHARE,
386 NavigationChange.InteractionCause.BUTTON,
387 fileAgeFromAdapterAtIndex(currentDataId));
388 // If applicable, show release information before this item
390 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
391 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
392 new Callback<Void>() {
394 public void onCallback(Void result) {
403 private void share(FilmstripItem data) {
404 Intent shareIntent = getShareIntentByData(data);
405 if (shareIntent != null) {
407 launchActivityByIntent(shareIntent);
408 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
409 } catch (ActivityNotFoundException ex) {
415 private int getCurrentDataId() {
416 return mFilmstripController.getCurrentAdapterIndex();
419 private FilmstripItem getCurrentLocalData() {
420 return mDataAdapter.getItemAt(getCurrentDataId());
424 * Sets up the share intent and NFC properly according to the
427 * @param item The data to be shared.
429 private Intent getShareIntentByData(final FilmstripItem item) {
430 Intent intent = null;
431 final Uri contentUri = item.getData().getUri();
432 final String msgShareTo = getResources().getString(R.string.share_to);
434 if (item.getMetadata().isPanorama360() &&
435 item.getData().getUri() != Uri.EMPTY) {
436 intent = new Intent(Intent.ACTION_SEND);
437 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE);
438 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
439 } else if (item.getAttributes().canShare()) {
440 final String mimeType = item.getData().getMimeType();
441 intent = getShareIntentFromType(mimeType);
442 if (intent != null) {
443 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
444 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
446 intent = Intent.createChooser(intent, msgShareTo);
452 * Get the share intent according to the mimeType
454 * @param mimeType The mimeType of current data.
455 * @return the video/image's ShareIntent or null if mimeType is
458 private Intent getShareIntentFromType(String mimeType) {
459 // Lazily create the intent object.
460 Intent intent = new Intent(Intent.ACTION_SEND);
461 if (mimeType.startsWith("video/")) {
462 intent.setType("video/*");
464 if (mimeType.startsWith("image/")) {
465 intent.setType("image/*");
467 Log.w(TAG, "unsupported mimeType " + mimeType);
474 public void onProgressErrorClicked() {
475 FilmstripItem data = getCurrentLocalData();
476 getServices().getCaptureSessionManager().removeErrorMessage(
477 data.getData().getUri());
478 updateBottomControlsByData(data);
483 public void onCameraOpened(CameraAgent.CameraProxy camera) {
484 Log.v(TAG, "onCameraOpened");
486 // We've paused, but just asynchronously opened the camera. Close it
487 // because we should be releasing the camera when paused to allow
488 // other apps to access it.
489 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera");
490 mCameraController.closeCamera(false);
494 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
495 // We shouldn't be here. Just close the camera and leave.
496 mCameraController.closeCamera(false);
497 throw new IllegalStateException("Camera opened but the module shouldn't be " +
500 if (mCurrentModule != null) {
501 resetExposureCompensationToDefault(camera);
502 mCurrentModule.onCameraAvailable(camera);
504 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
506 Log.v(TAG, "invoking onChangeCamera");
507 mCameraAppUI.onChangeCamera();
510 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) {
511 // Reset the exposure compensation before handing the camera to module.
512 CameraSettings cameraSettings = camera.getSettings();
513 cameraSettings.setExposureCompensationIndex(0);
514 camera.applySettings(cameraSettings);
518 public void onCameraDisabled(int cameraId) {
519 UsageStatistics.instance().cameraFailure(
520 eventprotos.CameraFailure.FailureReason.SECURITY, null,
521 UsageStatistics.NONE, UsageStatistics.NONE);
522 Log.w(TAG, "Camera disabled: " + cameraId);
523 CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
527 public void onDeviceOpenFailure(int cameraId, String info) {
528 UsageStatistics.instance().cameraFailure(
529 eventprotos.CameraFailure.FailureReason.OPEN_FAILURE, info,
530 UsageStatistics.NONE, UsageStatistics.NONE);
531 Log.w(TAG, "Camera open failure: " + info);
532 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
536 public void onDeviceOpenedAlready(int cameraId, String info) {
537 Log.w(TAG, "Camera open already: " + cameraId + "," + info);
538 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
542 public void onReconnectionFailure(CameraAgent mgr, String info) {
543 UsageStatistics.instance().cameraFailure(
544 eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE, null,
545 UsageStatistics.NONE, UsageStatistics.NONE);
546 Log.w(TAG, "Camera reconnection failure:" + info);
547 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
550 private static class MainHandler extends Handler {
551 final WeakReference<CameraActivity> mActivity;
553 public MainHandler(CameraActivity activity, Looper looper) {
555 mActivity = new WeakReference<CameraActivity>(activity);
559 public void handleMessage(Message msg) {
560 CameraActivity activity = mActivity.get();
561 if (activity == null) {
566 case MSG_CLEAR_SCREEN_ON_FLAG: {
567 if (!activity.mPaused) {
568 activity.getWindow().clearFlags(
569 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
577 private String fileNameFromAdapterAtIndex(int index) {
578 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
579 if (filmstripItem == null) {
583 File localFile = new File(filmstripItem.getData().getFilePath());
584 return localFile.getName();
587 private float fileAgeFromAdapterAtIndex(int index) {
588 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
589 if (filmstripItem == null) {
593 File localFile = new File(filmstripItem.getData().getFilePath());
594 return 0.001f * (System.currentTimeMillis() - localFile.lastModified());
597 private final FilmstripContentPanel.Listener mFilmstripListener =
598 new FilmstripContentPanel.Listener() {
601 public void onSwipeOut() {
605 public void onSwipeOutBegin() {
607 mCameraAppUI.hideBottomControls();
608 mFilmstripCoversPreview = false;
609 updatePreviewVisibility();
613 public void onFilmstripHidden() {
614 mFilmstripVisible = false;
615 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
616 NavigationChange.InteractionCause.SWIPE_RIGHT);
617 CameraActivity.this.setFilmstripUiVisibility(false);
618 // When the user hide the filmstrip (either swipe out or
619 // tap on back key) we move to the first item so next time
620 // when the user swipe in the filmstrip, the most recent
622 mFilmstripController.goToFirstItem();
626 public void onFilmstripShown() {
627 mFilmstripVisible = true;
628 mCameraAppUI.hideCaptureIndicator();
629 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
630 NavigationChange.InteractionCause.SWIPE_LEFT);
631 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
635 public void onFocusedDataLongPressed(int adapterIndex) {
640 public void onFocusedDataPromoted(int adapterIndex) {
641 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
643 MediaInteraction.InteractionType.DELETE,
644 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex(
646 removeItemAt(adapterIndex);
650 public void onFocusedDataDemoted(int adapterIndex) {
651 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
653 MediaInteraction.InteractionType.DELETE,
654 NavigationChange.InteractionCause.SWIPE_DOWN,
655 fileAgeFromAdapterAtIndex(adapterIndex));
656 removeItemAt(adapterIndex);
660 public void onEnterFullScreenUiShown(int adapterIndex) {
661 if (mFilmstripVisible) {
662 CameraActivity.this.setFilmstripUiVisibility(true);
667 public void onLeaveFullScreenUiShown(int adapterIndex) {
672 public void onEnterFullScreenUiHidden(int adapterIndex) {
673 if (mFilmstripVisible) {
674 CameraActivity.this.setFilmstripUiVisibility(false);
679 public void onLeaveFullScreenUiHidden(int adapterIndex) {
684 public void onEnterFilmstrip(int adapterIndex) {
685 if (mFilmstripVisible) {
686 CameraActivity.this.setFilmstripUiVisibility(true);
691 public void onLeaveFilmstrip(int adapterIndex) {
696 public void onDataReloaded() {
697 if (!mFilmstripVisible) {
700 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
704 public void onDataUpdated(int adapterIndex) {
705 if (!mFilmstripVisible) {
708 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
712 public void onEnterZoomView(int adapterIndex) {
713 if (mFilmstripVisible) {
714 CameraActivity.this.setFilmstripUiVisibility(false);
719 public void onZoomAtIndexChanged(int adapterIndex, float zoom) {
720 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex);
721 long ageMillis = System.currentTimeMillis()
722 - filmstripItem.getData().getLastModifiedDate().getTime();
724 // Do not log if items is to old or does not have a path (which is
725 // being used as a key).
726 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) ||
727 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) {
730 File localFile = new File(filmstripItem.getData().getFilePath());
731 UsageStatistics.instance().mediaView(localFile.getName(),
732 filmstripItem.getData().getLastModifiedDate().getTime(), zoom);
736 public void onDataFocusChanged(final int prevIndex, final int newIndex) {
737 if (!mFilmstripVisible) {
740 // TODO: This callback is UI event callback, should always
741 // happen on UI thread. Find the reason for this
742 // runOnUiThread() and fix it.
743 runOnUiThread(new Runnable() {
746 updateUiByData(newIndex);
752 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
753 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
757 private final FilmstripItemListener mFilmstripItemListener =
758 new FilmstripItemListener() {
760 public void onMetadataUpdated(List<Integer> indexes) {
762 // Callback after the activity is paused.
765 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
766 for (Integer index : indexes) {
767 if (index == currentIndex) {
768 updateBottomControlsByData(mDataAdapter.getItemAt(index));
769 // Currently we have only 1 data can be matched.
770 // No need to look for more, break.
777 public void gotoGallery() {
778 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
779 NavigationChange.InteractionCause.BUTTON);
781 mFilmstripController.goToNextItem();
785 * If 'visible' is false, this hides the action bar. Also maintains
786 * lights-out at all times.
788 * @param visible is false, this hides the action bar and filmstrip bottom
791 private void setFilmstripUiVisibility(boolean visible) {
792 mLightsOutRunnable.run();
793 mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
794 if (visible != mActionBar.isShowing()) {
797 mCameraAppUI.showBottomControls();
800 mCameraAppUI.hideBottomControls();
803 mFilmstripCoversPreview = visible;
804 updatePreviewVisibility();
807 private void hideSessionProgress() {
808 mCameraAppUI.getFilmstripBottomControls().hideProgress();
811 private void showSessionProgress(CharSequence message) {
812 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
813 controls.setProgressText(message);
814 controls.hideControls();
815 controls.hideProgressError();
816 controls.showProgress();
819 private void showProcessError(CharSequence message) {
820 mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
823 private void updateSessionProgress(int progress) {
824 mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
827 private void updateSessionProgressText(CharSequence message) {
828 mCameraAppUI.getFilmstripBottomControls().setProgressText(message);
831 private void setupNfcBeamPush() {
832 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
833 if (adapter == null) {
837 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
839 adapter.setNdefPushMessage(null, CameraActivity.this);
843 adapter.setBeamPushUris(null, CameraActivity.this);
844 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
846 public Uri[] createBeamUris(NfcEvent event) {
849 }, CameraActivity.this);
853 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
854 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
855 if (currentIndex < 0) {
858 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex),
859 MediaInteraction.InteractionType.SHARE,
860 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex));
861 // TODO add intent.getComponent().getPackageName()
865 // Note: All callbacks come back on the main thread.
866 private final SessionListener mSessionListener =
867 new SessionListener() {
869 public void onSessionQueued(final Uri uri) {
870 Log.v(TAG, "onSessionQueued: " + uri);
871 if (!Storage.isSessionUri(uri)) {
874 SessionItem newData = new SessionItem(getApplicationContext(), uri);
875 mDataAdapter.addOrUpdate(newData);
879 public void onSessionUpdated(Uri uri) {
880 Log.v(TAG, "onSessionUpdated: " + uri);
881 mDataAdapter.refresh(uri);
885 public void onSessionDone(final Uri sessionUri) {
886 Log.v(TAG, "onSessionDone:" + sessionUri);
887 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
888 if (contentUri == null) {
889 mDataAdapter.refresh(sessionUri);
892 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri);
894 // This can be null if e.g. a session is canceled (e.g.
895 // through discard panorama). It might be worth adding
896 // onSessionCanceled or the like this interface.
897 if (newData == null) {
898 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri);
902 // Make the PhotoItem aware of the session placeholder, to
903 // allow it to make a smooth transition to its content.
904 newData.setSessionPlaceholderBitmap(
905 Storage.getPlacerHolderForSession(sessionUri));
907 final int pos = mDataAdapter.findByContentUri(sessionUri);
909 // We do not have a placeholder for this image, perhaps
910 // due to the activity crashing or being killed.
911 mDataAdapter.addOrUpdate(newData);
913 mDataAdapter.updateItemAt(pos, newData);
918 public void onSessionProgress(final Uri uri, final int progress) {
920 // Do nothing, there is no task for this URI.
923 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
924 if (currentIndex == -1) {
928 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
929 updateSessionProgress(progress);
934 public void onSessionProgressText(final Uri uri, final CharSequence message) {
935 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
936 if (currentIndex == -1) {
940 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
941 updateSessionProgressText(message);
946 public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
947 // Don't show capture indicator in Photo Sphere.
948 final int photosphereModuleId = getApplicationContext().getResources()
950 R.integer.camera_mode_photosphere);
951 if (mCurrentModeIndex == photosphereModuleId) {
954 indicateCapture(indicator, rotationDegrees);
958 public void onSessionFailed(Uri uri, CharSequence reason) {
959 Log.v(TAG, "onSessionFailed:" + uri);
961 int failedIndex = mDataAdapter.findByContentUri(uri);
962 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
964 if (currentIndex == failedIndex) {
965 updateSessionProgress(0);
966 showProcessError(reason);
969 mDataAdapter.refresh(uri);
974 public Context getAndroidContext() {
979 public Dialog createDialog() {
980 return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
984 public void launchActivityByIntent(Intent intent) {
985 // Starting from L, we prefer not to start edit activity within camera's task.
986 mResetToPreviewOnResume = false;
987 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
989 startActivity(intent);
993 public int getCurrentModuleIndex() {
994 return mCurrentModeIndex;
998 public int getCurrentCameraId() {
999 return mCameraController.getCurrentCameraId();
1003 public String getModuleScope() {
1004 return MODULE_SCOPE_PREFIX + mCurrentModule.getModuleStringIdentifier();
1008 public String getCameraScope() {
1009 int currentCameraId = getCurrentCameraId();
1010 if (currentCameraId < 0) {
1011 // if an unopen camera i.e. negative ID is returned, which we've observed in
1012 // some automated scenarios, just return it as a valid separate scope
1013 // this could cause user issues, so log a stack trace noting the call path
1014 // which resulted in this scenario.
1015 Log.w(TAG, "getting camera scope with no open camera, using id: " + currentCameraId);
1017 return CAMERA_SCOPE_PREFIX + Integer.toString(currentCameraId);
1021 public ModuleController getCurrentModuleController() {
1022 return mCurrentModule;
1026 public int getQuickSwitchToModuleId(int currentModuleIndex) {
1027 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
1032 public SurfaceTexture getPreviewBuffer() {
1033 // TODO: implement this
1038 public void onPreviewReadyToStart() {
1039 mCameraAppUI.onPreviewReadyToStart();
1043 public void onPreviewStarted() {
1044 mCameraAppUI.onPreviewStarted();
1048 public void addPreviewAreaSizeChangedListener(
1049 PreviewStatusListener.PreviewAreaChangedListener listener) {
1050 mCameraAppUI.addPreviewAreaChangedListener(listener);
1054 public void removePreviewAreaSizeChangedListener(
1055 PreviewStatusListener.PreviewAreaChangedListener listener) {
1056 mCameraAppUI.removePreviewAreaChangedListener(listener);
1060 public void setupOneShotPreviewListener() {
1061 mCameraController.setOneShotPreviewCallback(mMainHandler,
1062 new CameraAgent.CameraPreviewDataCallback() {
1064 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) {
1065 mCurrentModule.onPreviewInitialDataReceived();
1066 mCameraAppUI.onNewPreviewFrame();
1073 public void updatePreviewAspectRatio(float aspectRatio) {
1074 mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
1078 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
1079 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio);
1083 public RectF getFullscreenRect() {
1084 return mCameraAppUI.getFullscreenRect();
1088 public void updatePreviewTransform(Matrix matrix) {
1089 mCameraAppUI.updatePreviewTransform(matrix);
1093 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1094 mCameraAppUI.setPreviewStatusListener(previewStatusListener);
1098 public FrameLayout getModuleLayoutRoot() {
1099 return mCameraAppUI.getModuleRootView();
1103 public void setShutterEventsListener(ShutterEventsListener listener) {
1104 // TODO: implement this
1108 public void setShutterEnabled(boolean enabled) {
1109 mCameraAppUI.setShutterButtonEnabled(enabled);
1113 public boolean isShutterEnabled() {
1114 return mCameraAppUI.isShutterButtonEnabled();
1118 public void startFlashAnimation(boolean shortFlash) {
1119 mCameraAppUI.startFlashAnimation(shortFlash);
1123 public void startPreCaptureAnimation() {
1124 // TODO: implement this
1128 public void cancelPreCaptureAnimation() {
1129 // TODO: implement this
1133 public void startPostCaptureAnimation() {
1134 // TODO: implement this
1138 public void startPostCaptureAnimation(Bitmap thumbnail) {
1139 // TODO: implement this
1143 public void cancelPostCaptureAnimation() {
1144 // TODO: implement this
1148 public OrientationManager getOrientationManager() {
1149 return mOrientationManager;
1153 public LocationManager getLocationManager() {
1154 return mLocationManager;
1158 public void lockOrientation() {
1159 if (mOrientationManager != null) {
1160 mOrientationManager.lockOrientation();
1165 public void unlockOrientation() {
1166 if (mOrientationManager != null) {
1167 mOrientationManager.unlockOrientation();
1172 * If not in filmstrip, this shows the capture indicator.
1174 private void indicateCapture(final Bitmap indicator, final int rotationDegrees) {
1175 if (mFilmstripVisible) {
1179 // Don't show capture indicator in Photo Sphere.
1180 // TODO: Don't reach into resources to figure out the current mode.
1181 final int photosphereModuleId = getApplicationContext().getResources().getInteger(
1182 R.integer.camera_mode_photosphere);
1183 if (mCurrentModeIndex == photosphereModuleId) {
1187 mMainHandler.post(new Runnable() {
1190 mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule
1191 .getPeekAccessibilityString());
1192 mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees);
1198 public void notifyNewMedia(Uri uri) {
1199 // TODO: This method is running on the main thread. Also we should get
1200 // rid of that AsyncTask.
1202 updateStorageSpaceAndHint(null);
1203 ContentResolver cr = getContentResolver();
1204 String mimeType = cr.getType(uri);
1205 FilmstripItem newData = null;
1206 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
1207 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1208 newData = mVideoItemFactory.queryContentUri(uri);
1209 if (newData == null) {
1210 Log.e(TAG, "Can't find video data in content resolver:" + uri);
1213 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
1214 CameraUtil.broadcastNewPicture(mAppContext, uri);
1215 newData = mPhotoItemFactory.queryContentUri(uri);
1216 if (newData == null) {
1217 Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1221 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1225 // We are preloading the metadata for new video since we need the
1226 // rotation info for the thumbnail.
1227 new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
1229 protected FilmstripItem doInBackground(FilmstripItem... params) {
1230 FilmstripItem data = params[0];
1231 MetadataLoader.loadMetadata(getAndroidContext(), data);
1236 protected void onPostExecute(final FilmstripItem data) {
1237 // TODO: Figure out why sometimes the data is aleady there.
1238 mDataAdapter.addOrUpdate(data);
1240 // Legacy modules don't use CaptureSession, so we show the capture indicator when
1241 // the item was safed.
1242 if (mCurrentModule instanceof PhotoModule ||
1243 mCurrentModule instanceof VideoModule) {
1244 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1247 final Optional<Bitmap> bitmap = data.generateThumbnail(
1248 mAboveFilmstripControlLayout.getWidth(),
1249 mAboveFilmstripControlLayout.getMeasuredHeight());
1250 if (bitmap.isPresent()) {
1251 indicateCapture(bitmap.get(), 0);
1257 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
1261 public void enableKeepScreenOn(boolean enabled) {
1266 mKeepScreenOn = enabled;
1267 if (mKeepScreenOn) {
1268 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1269 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1271 keepScreenOnForAWhile();
1276 public CameraProvider getCameraProvider() {
1277 return mCameraController;
1281 public OneCameraManager getCameraManager() {
1282 return mCameraManager;
1285 private void removeItemAt(int index) {
1286 mDataAdapter.removeAt(index);
1287 if (mDataAdapter.getTotalNumber() > 1) {
1288 showUndoDeletionBar();
1290 // If camera preview is the only view left in filmstrip,
1291 // no need to show undo bar.
1292 mPendingDeletion = true;
1294 if (mFilmstripVisible) {
1295 mCameraAppUI.getFilmstripContentPanel().animateHide();
1301 public boolean onOptionsItemSelected(MenuItem item) {
1302 // Handle presses on the action bar items
1303 switch (item.getItemId()) {
1304 case android.R.id.home:
1307 case R.id.action_details:
1308 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex());
1310 case R.id.action_help_and_feedback:
1311 mResetToPreviewOnResume = false;
1312 new GoogleHelpHelper(this).launchGoogleHelp();
1315 return super.onOptionsItemSelected(item);
1319 private boolean isCaptureIntent() {
1320 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1321 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1322 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1330 * Note: Make sure this callback is unregistered properly when the activity
1331 * is destroyed since we're otherwise leaking the Activity reference.
1333 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
1334 = new CameraExceptionHandler.CameraExceptionCallback() {
1336 public void onCameraError(int errorCode) {
1337 // Not a fatal error. only do Log.e().
1338 Log.e(TAG, "Camera error callback. error=" + errorCode);
1341 public void onCameraException(
1342 RuntimeException ex, String commandHistory, int action, int state) {
1343 Log.e(TAG, "Camera Exception", ex);
1344 UsageStatistics.instance().cameraFailure(
1345 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
1346 commandHistory, action, state);
1350 public void onDispatchThreadException(RuntimeException ex) {
1351 Log.e(TAG, "DispatchThread Exception", ex);
1352 UsageStatistics.instance().cameraFailure(
1353 eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
1354 null, UsageStatistics.NONE, UsageStatistics.NONE);
1357 private void onFatalError() {
1358 if (mCameraFatalError) {
1361 mCameraFatalError = true;
1363 // If the activity receives exception during onPause, just exit the app.
1364 if (mPaused && !isFinishing()) {
1365 Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
1368 CameraUtil.showErrorAndFinish(CameraActivity.this,
1369 R.string.cannot_connect_camera);
1375 public void onNewIntentTasks(Intent intent) {
1376 onModeSelected(getModeIndex());
1380 public void onCreateTasks(Bundle state) {
1381 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1382 mAppContext = getApplicationContext();
1384 if (!Glide.isSetup()) {
1385 Context context = getAndroidContext();
1386 Glide.setup(new GlideBuilder(context)
1387 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888)
1388 .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
1390 Glide glide = Glide.get(context);
1392 // As a camera we will use a large amount of memory
1393 // for displaying images.
1394 glide.setMemoryCategory(MemoryCategory.HIGH);
1396 // Prefill glides bitmap pool to prevent excessive jank
1397 // when loading large images.
1398 glide.preFillBitmapPool(
1399 new PreFillType.Builder(GlideFilmstripManager.MAXIMUM_TEXTURE_SIZE)
1401 // It's more important for jank and GC to have
1402 // A larger weight of max texture size images than
1403 // media store sized images.
1404 new PreFillType.Builder(
1405 GlideFilmstripManager.MEDIASTORE_THUMB_WIDTH,
1406 GlideFilmstripManager.MEDIASTORE_THUMB_HEIGHT));
1409 mOnCreateTime = System.currentTimeMillis();
1410 mSoundPlayer = new SoundPlayer(mAppContext);
1413 mCameraManager = OneCameraManager.get(this, ResolutionUtil.getDisplayMetrics(this));
1414 } catch (OneCameraException e) {
1415 // Log error and continue. Modules requiring OneCamera should check
1416 // and handle if null by showing error dialog or other treatment.
1417 Log.e(TAG, "Creating camera manager failed.", e);
1418 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
1421 // TODO: Try to move all the resources allocation to happen as soon as
1422 // possible so we can call module.init() at the earliest time.
1423 mModuleManager = new ModuleManagerImpl();
1424 GcamHelper.init(getContentResolver());
1425 ModulesInfo.setupModules(mAppContext, mModuleManager);
1427 mSettingsManager = getServices().getSettingsManager();
1428 AppUpgrader appUpgrader = new AppUpgrader(this);
1429 appUpgrader.upgrade(mSettingsManager);
1430 Keys.setDefaults(mSettingsManager, mAppContext);
1431 mResolutionSetting = new ResolutionSetting(mSettingsManager, mCameraManager);
1433 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1434 // We suppress this flag via theme when drawing the system preview
1435 // background, but once we create activity here, reactivate to the
1436 // default value. The default is important for L, we don't want to
1437 // change app behavior, just starting background drawable layout.
1438 if (ApiHelper.isLOrHigher()) {
1439 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1441 setContentView(R.layout.activity_main);
1442 // A window background is set in styles.xml for the system to show a
1443 // drawable background with gray color and camera icon before the
1444 // activity is created. We set the background to null here to prevent
1445 // overdraw, all views must take care of drawing backgrounds if
1446 // necessary. This call to setBackgroundDrawable must occur after
1447 // setContentView, otherwise a background may be set again from the
1449 getWindow().setBackgroundDrawable(null);
1451 mActionBar = getActionBar();
1452 // set actionbar background to 100% or 50% transparent
1453 if (ApiHelper.isLOrHigher()) {
1454 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000));
1456 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
1459 mMainHandler = new MainHandler(this, getMainLooper());
1460 mCameraController = new CameraController(mAppContext, this, mMainHandler,
1461 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1462 CameraAgentFactory.CameraApi.API_1),
1463 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1464 CameraAgentFactory.CameraApi.AUTO));
1465 mCameraController.setCameraExceptionHandler(
1466 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
1468 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1469 mModeListView.init(mModuleManager.getSupportedModeIndexList());
1470 if (ApiHelper.HAS_ROTATION_ANIMATION) {
1471 setRotationAnimation();
1473 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1475 public void onVisibilityChanged(boolean visible) {
1476 mModeListVisible = visible;
1477 mCameraAppUI.setShutterButtonImportantToA11y(!visible);
1478 updatePreviewVisibility();
1482 // Check if this is in the secure camera mode.
1483 Intent intent = getIntent();
1484 String action = intent.getAction();
1485 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1486 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1487 mSecureCamera = true;
1489 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1492 if (mSecureCamera) {
1493 // Change the window flags so that secure camera can show when
1495 Window win = getWindow();
1496 WindowManager.LayoutParams params = win.getAttributes();
1497 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1498 win.setAttributes(params);
1500 // Filter for screen off so that we can finish secure camera
1501 // activity when screen is off.
1502 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1503 registerReceiver(mShutdownReceiver, filter_screen_off);
1505 // Filter for phone unlock so that we can finish secure camera
1506 // via this UI path:
1507 // 1. from secure lock screen, user starts secure camera
1508 // 2. user presses home button
1509 // 3. user unlocks phone
1510 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
1511 registerReceiver(mShutdownReceiver, filter_user_unlock);
1513 mCameraAppUI = new CameraAppUI(this,
1514 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1516 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1518 mAboveFilmstripControlLayout =
1519 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1521 // Add the session listener so we can track the session progress
1523 getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1524 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1525 mFilmstripController.setImageGap(
1526 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1527 mPanoramaViewHelper = new PanoramaViewHelper(this);
1528 mPanoramaViewHelper.onCreate();
1530 ContentResolver appContentResolver = mAppContext.getContentResolver();
1531 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
1532 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
1533 new PhotoDataFactory());
1534 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
1535 new VideoDataFactory());
1536 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
1537 mPhotoItemFactory, mVideoItemFactory);
1538 mDataAdapter.setLocalDataListener(mFilmstripItemListener);
1540 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1543 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1544 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1545 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1546 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1549 mLocationManager = new LocationManager(mAppContext);
1550 mOrientationManager = new OrientationManagerImpl(this, mMainHandler);
1552 setModuleFromModeIndex(getModeIndex());
1553 mCameraAppUI.prepareModuleUI();
1554 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1556 if (!mSecureCamera) {
1557 mFilmstripController.setDataAdapter(mDataAdapter);
1558 if (!isCaptureIntent()) {
1559 mDataAdapter.requestLoad(new Callback<Void>() {
1561 public void onCallback(Void result) {
1562 fillTemporarySessions();
1567 // Put a lock placeholder as the last image by setting its date to
1569 ImageView v = (ImageView) getLayoutInflater().inflate(
1570 R.layout.secure_album_placeholder, null);
1571 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
1572 v.setOnClickListener(new View.OnClickListener() {
1574 public void onClick(View view) {
1575 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1576 NavigationChange.InteractionCause.BUTTON);
1581 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1582 mDataAdapter = new FixedLastProxyAdapter(
1585 new PlaceholderItem(
1587 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
1588 v.getDrawable().getIntrinsicWidth(),
1589 v.getDrawable().getIntrinsicHeight()));
1590 // Flush out all the original data.
1591 mDataAdapter.clear();
1592 mFilmstripController.setDataAdapter(mDataAdapter);
1597 mLocalImagesObserver = new FilmstripContentObserver();
1598 mLocalVideosObserver = new FilmstripContentObserver();
1600 getContentResolver().registerContentObserver(
1601 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1602 mLocalImagesObserver);
1603 getContentResolver().registerContentObserver(
1604 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1605 mLocalVideosObserver);
1606 mMemoryManager = getServices().getMemoryManager();
1608 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1611 HashMap memoryData = mMemoryManager.queryMemory();
1612 UsageStatistics.instance().reportMemoryConsumed(memoryData,
1613 MemoryQuery.REPORT_LABEL_LAUNCH);
1616 mMotionManager = getServices().getMotionManager();
1618 mFirstRunDialog = new FirstRunDialog(this, new FirstRunDialog.FirstRunDialogListener() {
1620 public void onFirstRunStateReady() {
1621 // Run normal resume tasks.
1626 public void onCameraAccessException() {
1627 CameraUtil.showErrorAndFinish(CameraActivity.this, R.string.cannot_connect_camera);
1633 * Get the current mode index from the Intent or from persistent
1636 public int getModeIndex() {
1638 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1639 int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1640 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1641 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1642 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1643 modeIndex = videoIndex;
1644 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())) {
1646 modeIndex = photoIndex;
1647 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1648 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1650 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1651 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1652 Keys.KEY_CAMERA_MODULE_LAST_USED);
1654 // For upgraders who have not seen the aspect ratio selection screen,
1655 // we need to drop them back in the photo module and have them select
1657 // TODO: Move this to SettingsManager as an upgrade procedure.
1658 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1659 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
1660 modeIndex = photoIndex;
1663 // If the activity has not been started using an explicit intent,
1664 // read the module index from the last time the user changed modes
1665 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1666 Keys.KEY_STARTUP_MODULE_INDEX);
1667 if ((modeIndex == gcamIndex &&
1668 !GcamHelper.hasGcamAsSeparateModule()) || modeIndex < 0) {
1669 modeIndex = photoIndex;
1676 * Call this whenever the mode drawer or filmstrip change the visibility
1679 private void updatePreviewVisibility() {
1680 if (mCurrentModule == null) {
1684 int visibility = getPreviewVisibility();
1685 mCameraAppUI.onPreviewVisiblityChanged(visibility);
1686 updatePreviewRendering(visibility);
1687 mCurrentModule.onPreviewVisibilityChanged(visibility);
1690 private void updatePreviewRendering(int visibility) {
1691 if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1692 mCameraAppUI.pausePreviewRendering();
1694 mCameraAppUI.resumePreviewRendering();
1698 private int getPreviewVisibility() {
1699 if (mFilmstripCoversPreview) {
1700 return ModuleController.VISIBILITY_HIDDEN;
1701 } else if (mModeListVisible){
1702 return ModuleController.VISIBILITY_COVERED;
1704 return ModuleController.VISIBILITY_VISIBLE;
1708 private void setRotationAnimation() {
1709 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1710 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1711 Window win = getWindow();
1712 WindowManager.LayoutParams winParams = win.getAttributes();
1713 winParams.rotationAnimation = rotationAnimation;
1714 win.setAttributes(winParams);
1718 public void onUserInteraction() {
1719 super.onUserInteraction();
1720 if (!isFinishing()) {
1721 keepScreenOnForAWhile();
1726 public boolean dispatchTouchEvent(MotionEvent ev) {
1727 boolean result = super.dispatchTouchEvent(ev);
1728 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1729 // Real deletion is postponed until the next user interaction after
1730 // the gesture that triggers deletion. Until real deletion is
1731 // performed, users can click the undo button to bring back the
1732 // image that they chose to delete.
1733 if (mPendingDeletion && !mIsUndoingDeletion) {
1741 public void onPauseTasks() {
1742 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1745 * Save the last module index after all secure camera and icon launches,
1746 * not just on mode switches.
1748 * Right now we exclude capture intents from this logic, because we also
1749 * ignore the cross-Activity recovery logic in onStart for capture intents.
1751 if (!isCaptureIntent()) {
1752 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
1753 Keys.KEY_STARTUP_MODULE_INDEX,
1758 mCameraAppUI.hideCaptureIndicator();
1759 mFirstRunDialog.dismiss();
1761 // Delete photos that are pending deletion
1763 mCurrentModule.pause();
1764 mOrientationManager.pause();
1765 mPanoramaViewHelper.onPause();
1767 mLocalImagesObserver.setForegroundChangeListener(null);
1768 mLocalImagesObserver.setActivityPaused(true);
1769 mLocalVideosObserver.setActivityPaused(true);
1770 mPreloader.cancelAllLoads();
1773 mMotionManager.stop();
1775 // Always stop recording location when paused. Resume will start
1776 // location recording again if the location setting is on.
1777 mLocationManager.recordLocation(false);
1779 UsageStatistics.instance().backgrounded();
1781 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
1782 // button. Let's just kill the process.
1783 if (mCameraFatalError && !isFinishing()) {
1784 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
1787 // Close the camera and wait for the operation done.
1788 Log.v(TAG, "onPause closing camera");
1789 mCameraController.closeCamera(true);
1794 public void onResumeTasks() {
1797 // Show the dialog if necessary. The rest resume logic will be invoked
1798 // at the onFirstRunStateReady() callback.
1799 mFirstRunDialog.showIfNecessary();
1802 private void resume() {
1803 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1804 Log.v(TAG, "Build info: " + Build.DISPLAY);
1806 updateStorageSpaceAndHint(null);
1808 mLastLayoutOrientation = getResources().getConfiguration().orientation;
1810 // TODO: Handle this in OrientationManager.
1812 if (Settings.System.getInt(getContentResolver(),
1813 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1814 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1815 mAutoRotateScreen = false;
1817 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1818 mAutoRotateScreen = true;
1821 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and
1822 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
1823 // lockscreen onResume->onPause->onResume sequence.
1825 String action = getIntent().getAction();
1826 if (action == null) {
1827 source = ForegroundSource.UNKNOWN_SOURCE;
1830 case MediaStore.ACTION_IMAGE_CAPTURE:
1831 source = ForegroundSource.ACTION_IMAGE_CAPTURE;
1833 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
1834 // was UNKNOWN_SOURCE in Fishlake.
1835 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
1837 case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
1838 // was UNKNOWN_SOURCE in Fishlake.
1839 source = ForegroundSource.ACTION_VIDEO_CAMERA;
1841 case MediaStore.ACTION_VIDEO_CAPTURE:
1842 source = ForegroundSource.ACTION_VIDEO_CAPTURE;
1844 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
1845 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
1846 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
1848 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
1849 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
1851 case Intent.ACTION_MAIN:
1852 source = ForegroundSource.ACTION_MAIN;
1855 source = ForegroundSource.UNKNOWN_SOURCE;
1859 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode());
1861 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
1862 if (ApiHelper.isLOrHigher()) {
1863 // hide the up affordance for L devices, it's not very Materially
1864 mActionBar.setDisplayShowHomeEnabled(false);
1867 mOrientationManager.resume();
1869 mCurrentModule.hardResetSettings(mSettingsManager);
1870 mCurrentModule.resume();
1871 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
1872 NavigationChange.InteractionCause.BUTTON);
1873 setSwipingEnabled(true);
1875 if (!mResetToPreviewOnResume) {
1876 FilmstripItem item = mDataAdapter.getItemAt(
1877 mFilmstripController.getCurrentAdapterIndex());
1879 mDataAdapter.refresh(item.getData().getUri());
1882 // The share button might be disabled to avoid double tapping.
1883 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1884 // Default is showing the preview, unless disabled by explicitly
1885 // starting an activity we want to return from to the filmstrip rather
1886 // than the preview.
1887 mResetToPreviewOnResume = true;
1889 if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1890 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1891 if (!mSecureCamera) {
1892 // If it's secure camera, requestLoad() should not be called
1893 // as it will load all the data.
1894 if (!mFilmstripVisible) {
1895 mDataAdapter.requestLoad(new Callback<Void>() {
1897 public void onCallback(Void result) {
1898 fillTemporarySessions();
1902 mDataAdapter.requestLoadNewPhotos();
1906 mLocalImagesObserver.setActivityPaused(false);
1907 mLocalVideosObserver.setActivityPaused(false);
1908 if (!mSecureCamera) {
1909 mLocalImagesObserver.setForegroundChangeListener(
1910 new FilmstripContentObserver.ChangeListener() {
1912 public void onChange() {
1913 mDataAdapter.requestLoadNewPhotos();
1918 keepScreenOnForAWhile();
1920 // Lights-out mode at all times.
1921 final View rootView = findViewById(R.id.activity_root_view);
1922 mLightsOutRunnable.run();
1923 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
1924 new OnSystemUiVisibilityChangeListener() {
1926 public void onSystemUiVisibilityChange(int visibility) {
1927 mMainHandler.removeCallbacks(mLightsOutRunnable);
1928 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
1932 mPanoramaViewHelper.onResume();
1933 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1935 // Enable location recording if the setting is on.
1936 final boolean locationRecordingEnabled =
1937 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
1938 mLocationManager.recordLocation(locationRecordingEnabled);
1940 final int previewVisibility = getPreviewVisibility();
1941 updatePreviewRendering(previewVisibility);
1943 mMotionManager.start();
1946 private void fillTemporarySessions() {
1947 if (mSecureCamera) {
1950 // There might be sessions still in flight (processed by our service).
1951 // Make sure they're added to the filmstrip.
1952 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
1956 public void onStartTasks() {
1957 mIsActivityRunning = true;
1958 mPanoramaViewHelper.onStart();
1961 * If we're starting after launching a different Activity (lockscreen),
1962 * we need to use the last mode used in the other Activity, and
1963 * not the old one from this Activity.
1965 * This needs to happen before CameraAppUI.resume() in order to set the
1966 * mode cover icon to the actual last mode used.
1968 * Right now we exclude capture intents from this logic.
1970 int modeIndex = getModeIndex();
1971 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
1972 onModeSelected(modeIndex);
1975 if (mResetToPreviewOnResume) {
1976 mCameraAppUI.resume();
1977 mResetToPreviewOnResume = false;
1982 protected void onStopTasks() {
1983 mIsActivityRunning = false;
1984 mPanoramaViewHelper.onStop();
1986 mLocationManager.disconnect();
1990 public void onDestroyTasks() {
1991 if (mSecureCamera) {
1992 unregisterReceiver(mShutdownReceiver);
1995 mSettingsManager.removeAllListeners();
1996 mCameraController.removeCallbackReceiver();
1997 mCameraController.setCameraExceptionHandler(null);
1998 getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1999 getContentResolver().unregisterContentObserver(mLocalVideosObserver);
2000 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
2001 mCameraAppUI.onDestroy();
2002 mModeListView.setVisibilityChangedListener(null);
2003 mCameraController = null;
2004 mSettingsManager = null;
2005 mOrientationManager = null;
2006 mButtonManager = null;
2007 mSoundPlayer.release();
2008 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
2009 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
2013 public void onConfigurationChanged(Configuration config) {
2014 super.onConfigurationChanged(config);
2015 Log.v(TAG, "onConfigurationChanged");
2016 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
2020 if (mLastLayoutOrientation != config.orientation) {
2021 mLastLayoutOrientation = config.orientation;
2022 mCurrentModule.onLayoutOrientationChanged(
2023 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
2028 public boolean onKeyDown(int keyCode, KeyEvent event) {
2029 if (!mFilmstripVisible) {
2030 if (mCurrentModule.onKeyDown(keyCode, event)) {
2033 // Prevent software keyboard or voice search from showing up.
2034 if (keyCode == KeyEvent.KEYCODE_SEARCH
2035 || keyCode == KeyEvent.KEYCODE_MENU) {
2036 if (event.isLongPress()) {
2042 return super.onKeyDown(keyCode, event);
2046 public boolean onKeyUp(int keyCode, KeyEvent event) {
2047 if (!mFilmstripVisible) {
2048 // If a module is in the middle of capture, it should
2049 // consume the key event.
2050 if (mCurrentModule.onKeyUp(keyCode, event)) {
2052 } else if (keyCode == KeyEvent.KEYCODE_MENU
2053 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2054 // Let the mode list view consume the event.
2055 mCameraAppUI.openModeList();
2057 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2058 mCameraAppUI.showFilmstrip();
2062 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2063 mFilmstripController.goToNextItem();
2065 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2066 boolean wentToPrevious = mFilmstripController.goToPreviousItem();
2067 if (!wentToPrevious) {
2068 // at beginning of filmstrip, hide and go back to preview
2069 mCameraAppUI.hideFilmstrip();
2074 return super.onKeyUp(keyCode, event);
2078 public void onBackPressed() {
2079 if (!mCameraAppUI.onBackPressed()) {
2080 if (!mCurrentModule.onBackPressed()) {
2081 super.onBackPressed();
2087 public boolean isAutoRotateScreen() {
2088 // TODO: Move to OrientationManager.
2089 return mAutoRotateScreen;
2093 public boolean onCreateOptionsMenu(Menu menu) {
2094 MenuInflater inflater = getMenuInflater();
2095 inflater.inflate(R.menu.filmstrip_menu, menu);
2096 mActionBarMenu = menu;
2098 // add a button for launching the gallery
2099 if (mGalleryIntent != null) {
2100 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
2101 if (appName != null) {
2102 MenuItem menuItem = menu.add(appName);
2103 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2104 menuItem.setIntent(mGalleryIntent);
2106 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
2107 if (galleryLogo != null) {
2108 menuItem.setIcon(galleryLogo);
2113 return super.onCreateOptionsMenu(menu);
2117 public boolean onPrepareOptionsMenu(Menu menu) {
2118 if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
2119 // Compatibility pre-L: launching new activities right above
2120 // lockscreen does not reliably work, only show help if not secure
2121 menu.removeItem(R.id.action_help_and_feedback);
2124 return super.onPrepareOptionsMenu(menu);
2127 protected long getStorageSpaceBytes() {
2128 synchronized (mStorageSpaceLock) {
2129 return mStorageSpaceBytes;
2133 protected interface OnStorageUpdateDoneListener {
2134 public void onStorageUpdateDone(long bytes);
2137 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
2139 * We execute disk operations on a background thread in order to
2140 * free up the UI thread. Synchronizing on the lock below ensures
2141 * that when getStorageSpaceBytes is called, the main thread waits
2142 * until this method has completed.
2144 * However, .execute() does not ensure this execution block will be
2145 * run right away (.execute() schedules this AsyncTask for sometime
2146 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
2147 * tries to execute the task in parellel with other AsyncTasks, but
2148 * there's still no guarantee).
2149 * e.g. don't call this then immediately call getStorageSpaceBytes().
2150 * Instead, pass in an OnStorageUpdateDoneListener.
2152 (new AsyncTask<Void, Void, Long>() {
2154 protected Long doInBackground(Void ... arg) {
2155 synchronized (mStorageSpaceLock) {
2156 mStorageSpaceBytes = Storage.getAvailableSpace();
2157 return mStorageSpaceBytes;
2162 protected void onPostExecute(Long bytes) {
2163 updateStorageHint(bytes);
2164 // This callback returns after I/O to check disk, so we could be
2165 // pausing and shutting down. If so, don't bother invoking.
2166 if (callback != null && !mPaused) {
2167 callback.onStorageUpdateDone(bytes);
2169 Log.v(TAG, "ignoring storage callback after activity pause");
2172 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2175 protected void updateStorageHint(long storageSpace) {
2176 if (!mIsActivityRunning) {
2180 String message = null;
2181 if (storageSpace == Storage.UNAVAILABLE) {
2182 message = getString(R.string.no_storage);
2183 } else if (storageSpace == Storage.PREPARING) {
2184 message = getString(R.string.preparing_sd);
2185 } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2186 message = getString(R.string.access_sd_fail);
2187 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2188 message = getString(R.string.spaceIsLow_content);
2191 if (message != null) {
2192 Log.w(TAG, "Storage warning: " + message);
2193 if (mStorageHint == null) {
2194 mStorageHint = OnScreenHint.makeText(message);
2196 mStorageHint.setText(message);
2198 mStorageHint.show();
2199 UsageStatistics.instance().storageWarning(storageSpace);
2201 // Disable all user interactions,
2202 mCameraAppUI.setDisableAllUserInteractions(true);
2203 } else if (mStorageHint != null) {
2204 mStorageHint.cancel();
2205 mStorageHint = null;
2207 // Re-enable all user interactions.
2208 mCameraAppUI.setDisableAllUserInteractions(false);
2212 protected void setResultEx(int resultCode) {
2213 mResultCodeForTesting = resultCode;
2214 setResult(resultCode);
2217 protected void setResultEx(int resultCode, Intent data) {
2218 mResultCodeForTesting = resultCode;
2219 mResultDataForTesting = data;
2220 setResult(resultCode, data);
2223 public int getResultCode() {
2224 return mResultCodeForTesting;
2227 public Intent getResultData() {
2228 return mResultDataForTesting;
2231 public boolean isSecureCamera() {
2232 return mSecureCamera;
2236 public boolean isPaused() {
2241 public int getPreferredChildModeIndex(int modeIndex) {
2242 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2243 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2244 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule()) {
2245 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2252 public void onModeSelected(int modeIndex) {
2253 if (mCurrentModeIndex == modeIndex) {
2257 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2258 // Record last used camera mode for quick switching
2259 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2260 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2261 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2262 Keys.KEY_CAMERA_MODULE_LAST_USED,
2266 closeModule(mCurrentModule);
2268 // Select the correct module index from the mode switcher index.
2269 modeIndex = getPreferredChildModeIndex(modeIndex);
2270 setModuleFromModeIndex(modeIndex);
2272 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2273 mCameraAppUI.addShutterListener(mCurrentModule);
2274 openModule(mCurrentModule);
2275 // Store the module index so we can use it the next time the Camera
2277 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2278 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2282 * Shows the settings dialog.
2285 public void onSettingsSelected() {
2286 UsageStatistics.instance().controlUsed(
2287 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2288 Intent intent = new Intent(this, CameraSettingsActivity.class);
2289 startActivity(intent);
2293 public void freezeScreenUntilPreviewReady() {
2294 mCameraAppUI.freezeScreenUntilPreviewReady();
2298 public int getModuleId(int modeIndex) {
2299 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2300 if (agent == null) {
2303 return agent.getModuleId();
2307 * Sets the mCurrentModuleIndex, creates a new module instance for the given
2308 * index an sets it as mCurrentModule.
2310 private void setModuleFromModeIndex(int modeIndex) {
2311 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2312 if (agent == null) {
2315 if (!agent.requestAppForCamera()) {
2316 mCameraController.closeCamera(true);
2318 mCurrentModeIndex = agent.getModuleId();
2319 mCurrentModule = (CameraModule) agent.createModule(this);
2323 public SettingsManager getSettingsManager() {
2324 return mSettingsManager;
2328 public ResolutionSetting getResolutionSetting() {
2329 return mResolutionSetting;
2333 public CameraServices getServices() {
2334 return CameraServicesImpl.instance();
2337 public List<String> getSupportedModeNames() {
2338 List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2339 List<String> supported = new ArrayList<String>();
2341 for (Integer modeIndex : indices) {
2342 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2343 if (name != null && !name.equals("")) {
2344 supported.add(name);
2351 public ButtonManager getButtonManager() {
2352 if (mButtonManager == null) {
2353 mButtonManager = new ButtonManager(this);
2355 return mButtonManager;
2359 public SoundPlayer getSoundPlayer() {
2360 return mSoundPlayer;
2364 * Launches an ACTION_EDIT intent for the given local data item. If
2365 * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2366 * the user start either the tiny planet editor or another photo editor.
2368 * @param data The data item to edit.
2370 public void launchEditor(FilmstripItem data) {
2371 Intent intent = new Intent(Intent.ACTION_EDIT)
2372 .setDataAndType(data.getData().getUri(), data.getData().getMimeType())
2373 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2375 launchActivityByIntent(intent);
2376 } catch (ActivityNotFoundException e) {
2377 final String msgEditWith = getResources().getString(R.string.edit_with);
2378 launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2383 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2384 super.onCreateContextMenu(menu, v, menuInfo);
2386 MenuInflater inflater = getMenuInflater();
2387 inflater.inflate(R.menu.filmstrip_context_menu, menu);
2391 public boolean onContextItemSelected(MenuItem item) {
2392 switch (item.getItemId()) {
2393 case R.id.tiny_planet_editor:
2394 mMyFilmstripBottomControlListener.onTinyPlanet();
2396 case R.id.photo_editor:
2397 mMyFilmstripBottomControlListener.onEdit();
2404 * Launch the tiny planet editor.
2406 * @param data The data must be a 360 degree stereographically mapped
2407 * panoramic image. It will not be modified, instead a new item
2408 * with the result will be added to the filmstrip.
2410 public void launchTinyPlanetEditor(FilmstripItem data) {
2411 TinyPlanetFragment fragment = new TinyPlanetFragment();
2412 Bundle bundle = new Bundle();
2413 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString());
2414 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle());
2415 fragment.setArguments(bundle);
2416 fragment.show(getFragmentManager(), "tiny_planet");
2420 * Returns what UI mode (capture mode or filmstrip) we are in.
2421 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2423 private int currentUserInterfaceMode() {
2424 int mode = NavigationChange.Mode.UNKNOWN_MODE;
2425 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2426 mode = NavigationChange.Mode.PHOTO_CAPTURE;
2428 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2429 mode = NavigationChange.Mode.VIDEO_CAPTURE;
2431 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2432 mode = NavigationChange.Mode.LENS_BLUR;
2434 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2435 mode = NavigationChange.Mode.HDR_PLUS;
2437 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2438 mode = NavigationChange.Mode.PHOTO_SPHERE;
2440 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2441 mode = NavigationChange.Mode.PANORAMA;
2443 if (mFilmstripVisible) {
2444 mode = NavigationChange.Mode.FILMSTRIP;
2449 private void openModule(CameraModule module) {
2450 module.init(this, isSecureCamera(), isCaptureIntent());
2451 module.hardResetSettings(mSettingsManager);
2454 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2455 NavigationChange.InteractionCause.BUTTON);
2456 updatePreviewVisibility();
2460 private void closeModule(CameraModule module) {
2462 mCameraAppUI.clearModuleUI();
2465 private void performDeletion() {
2466 if (!mPendingDeletion) {
2469 hideUndoDeletionBar(false);
2470 mDataAdapter.executeDeletion();
2473 public void showUndoDeletionBar() {
2474 if (mPendingDeletion) {
2477 Log.v(TAG, "showing undo bar");
2478 mPendingDeletion = true;
2479 if (mUndoDeletionBar == null) {
2480 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2481 mAboveFilmstripControlLayout, true);
2482 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2483 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2484 button.setOnClickListener(new View.OnClickListener() {
2486 public void onClick(View view) {
2487 mDataAdapter.undoDeletion();
2488 hideUndoDeletionBar(true);
2491 // Setting undo bar clickable to avoid touch events going through
2492 // the bar to the buttons (eg. edit button, etc) underneath the bar.
2493 mUndoDeletionBar.setClickable(true);
2494 // When there is user interaction going on with the undo button, we
2495 // do not want to hide the undo bar.
2496 button.setOnTouchListener(new View.OnTouchListener() {
2498 public boolean onTouch(View v, MotionEvent event) {
2499 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2500 mIsUndoingDeletion = true;
2501 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2502 mIsUndoingDeletion = false;
2508 mUndoDeletionBar.setAlpha(0f);
2509 mUndoDeletionBar.setVisibility(View.VISIBLE);
2510 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2513 private void hideUndoDeletionBar(boolean withAnimation) {
2514 Log.v(TAG, "Hiding undo deletion bar");
2515 mPendingDeletion = false;
2516 if (mUndoDeletionBar != null) {
2517 if (withAnimation) {
2518 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2519 .setListener(new Animator.AnimatorListener() {
2521 public void onAnimationStart(Animator animation) {
2526 public void onAnimationEnd(Animator animation) {
2527 mUndoDeletionBar.setVisibility(View.GONE);
2531 public void onAnimationCancel(Animator animation) {
2536 public void onAnimationRepeat(Animator animation) {
2541 mUndoDeletionBar.setVisibility(View.GONE);
2547 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2550 * @param enable {@code true} to enable swipe.
2552 public void setSwipingEnabled(boolean enable) {
2553 // TODO: Bring back the functionality.
2554 if (isCaptureIntent()) {
2555 // lockPreview(true);
2557 // lockPreview(!enable);
2561 // Accessor methods for getting latency times used in performance testing
2562 public long getFirstPreviewTime() {
2563 if (mCurrentModule instanceof PhotoModule) {
2564 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2565 if (coverHiddenTime != -1) {
2566 return coverHiddenTime - mOnCreateTime;
2572 public long getAutoFocusTime() {
2573 return (mCurrentModule instanceof PhotoModule) ?
2574 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2577 public long getShutterLag() {
2578 return (mCurrentModule instanceof PhotoModule) ?
2579 ((PhotoModule) mCurrentModule).mShutterLag : -1;
2582 public long getShutterToPictureDisplayedTime() {
2583 return (mCurrentModule instanceof PhotoModule) ?
2584 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2587 public long getPictureDisplayedToJpegCallbackTime() {
2588 return (mCurrentModule instanceof PhotoModule) ?
2589 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2592 public long getJpegCallbackFinishTime() {
2593 return (mCurrentModule instanceof PhotoModule) ?
2594 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2597 public long getCaptureStartTime() {
2598 return (mCurrentModule instanceof PhotoModule) ?
2599 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2602 public boolean isRecording() {
2603 return (mCurrentModule instanceof VideoModule) ?
2604 ((VideoModule) mCurrentModule).isRecording() : false;
2607 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2608 return mCameraController;
2611 // For debugging purposes only.
2612 public CameraModule getCurrentModule() {
2613 return mCurrentModule;
2617 public void showTutorial(AbstractTutorialOverlay tutorial) {
2618 mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2622 public void showErrorAndFinish(int messageId) {
2623 CameraUtil.showErrorAndFinish(this, messageId);
2627 private void keepScreenOnForAWhile() {
2628 if (mKeepScreenOn) {
2631 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2632 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2633 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2636 private void resetScreenOn() {
2637 mKeepScreenOn = false;
2638 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2639 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2643 * @return {@code true} if the Gallery is launched successfully.
2645 private boolean startGallery() {
2646 if (mGalleryIntent == null) {
2650 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2651 NavigationChange.InteractionCause.BUTTON);
2652 Intent startGalleryIntent = new Intent(mGalleryIntent);
2653 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
2654 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex);
2655 if (currentFilmstripItem != null) {
2656 GalleryHelper.setContentUri(startGalleryIntent,
2657 currentFilmstripItem.getData().getUri());
2659 launchActivityByIntent(startGalleryIntent);
2660 } catch (ActivityNotFoundException e) {
2661 Log.w(TAG, "Failed to launch gallery activity, closing");
2666 private void setNfcBeamPushUriFromData(FilmstripItem data) {
2667 final Uri uri = data.getData().getUri();
2668 if (uri != Uri.EMPTY) {
2669 mNfcPushUris[0] = uri;
2671 mNfcPushUris[0] = null;
2676 * Updates the visibility of the filmstrip bottom controls and action bar.
2678 private void updateUiByData(final int index) {
2679 final FilmstripItem currentData = mDataAdapter.getItemAt(index);
2680 if (currentData == null) {
2681 Log.w(TAG, "Current data ID not found.");
2682 hideSessionProgress();
2685 updateActionBarMenu(currentData);
2687 /* Bottom controls. */
2688 updateBottomControlsByData(currentData);
2690 if (isSecureCamera()) {
2691 // We cannot show buttons in secure camera since go to other
2692 // activities might create a security hole.
2693 mCameraAppUI.getFilmstripBottomControls().hideControls();
2697 setNfcBeamPushUriFromData(currentData);
2699 if (!mDataAdapter.isMetadataUpdatedAt(index)) {
2700 mDataAdapter.updateMetadataAt(index);
2705 * Updates the bottom controls based on the data.
2707 private void updateBottomControlsByData(final FilmstripItem currentData) {
2709 final CameraAppUI.BottomPanel filmstripBottomPanel =
2710 mCameraAppUI.getFilmstripBottomControls();
2711 filmstripBottomPanel.showControls();
2712 filmstripBottomPanel.setEditButtonVisibility(
2713 currentData.getAttributes().canEdit());
2714 filmstripBottomPanel.setShareButtonVisibility(
2715 currentData.getAttributes().canShare());
2716 filmstripBottomPanel.setDeleteButtonVisibility(
2717 currentData.getAttributes().canDelete());
2721 Uri contentUri = currentData.getData().getUri();
2722 CaptureSessionManager sessionManager = getServices()
2723 .getCaptureSessionManager();
2725 if (sessionManager.hasErrorMessage(contentUri)) {
2726 showProcessError(sessionManager.getErrorMessage(contentUri));
2728 filmstripBottomPanel.hideProgressError();
2729 CaptureSession session = sessionManager.getSession(contentUri);
2731 if (session != null) {
2732 int sessionProgress = session.getProgress();
2734 if (sessionProgress < 0) {
2735 hideSessionProgress();
2737 CharSequence progressMessage = session.getProgressMessage();
2738 showSessionProgress(progressMessage);
2739 updateSessionProgress(sessionProgress);
2742 hideSessionProgress();
2748 // We need to add this to a separate DB.
2749 final int viewButtonVisibility;
2750 if (currentData.getMetadata().isUsePanoramaViewer()) {
2751 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2752 } else if (currentData.getMetadata().isHasRgbzData()) {
2753 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2755 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2758 filmstripBottomPanel.setTinyPlanetEnabled(
2759 currentData.getMetadata().isPanorama360());
2760 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2763 private void showDetailsDialog(int index) {
2764 final FilmstripItem data = mDataAdapter.getItemAt(index);
2768 Optional<MediaDetails> details = data.getMediaDetails();
2769 if (!details.isPresent()) {
2772 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get());
2773 detailDialog.show();
2774 UsageStatistics.instance().mediaInteraction(
2775 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS,
2776 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index));
2780 * Show or hide action bar items depending on current data type.
2782 private void updateActionBarMenu(FilmstripItem data) {
2783 if (mActionBarMenu == null) {
2787 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2788 if (detailsMenuItem == null) {
2792 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo();
2793 detailsMenuItem.setVisible(showDetails);