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.
17 package com.android.camera;
19 import android.animation.Animator;
20 import android.annotation.TargetApi;
21 import android.app.ActionBar;
22 import android.app.Activity;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.content.ActivityNotFoundException;
26 import android.content.BroadcastReceiver;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.SharedPreferences;
32 import android.content.pm.ActivityInfo;
33 import android.content.pm.PackageManager;
34 import android.content.res.Configuration;
35 import android.graphics.Bitmap;
36 import android.graphics.Matrix;
37 import android.graphics.Point;
38 import android.graphics.SurfaceTexture;
39 import android.graphics.drawable.ColorDrawable;
40 import android.graphics.drawable.Drawable;
41 import android.net.Uri;
42 import android.nfc.NfcAdapter;
43 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
44 import android.nfc.NfcEvent;
45 import android.os.Build;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.HandlerThread;
49 import android.os.Looper;
50 import android.os.Message;
51 import android.preference.PreferenceManager;
52 import android.provider.MediaStore;
53 import android.provider.Settings;
54 import android.util.CameraPerformanceTracker;
55 import android.util.Log;
56 import android.view.ContextMenu;
57 import android.view.ContextMenu.ContextMenuInfo;
58 import android.view.KeyEvent;
59 import android.view.Menu;
60 import android.view.MenuInflater;
61 import android.view.MenuItem;
62 import android.view.MotionEvent;
63 import android.view.View;
64 import android.view.ViewGroup;
65 import android.view.Window;
66 import android.view.WindowManager;
67 import android.widget.FrameLayout;
68 import android.widget.ImageView;
69 import android.widget.ShareActionProvider;
71 import com.android.camera.app.AppController;
72 import com.android.camera.app.CameraAppUI;
73 import com.android.camera.app.CameraController;
74 import com.android.camera.app.CameraManager;
75 import com.android.camera.app.CameraManagerFactory;
76 import com.android.camera.app.CameraProvider;
77 import com.android.camera.app.CameraServices;
78 import com.android.camera.app.LocationManager;
79 import com.android.camera.app.ModuleManagerImpl;
80 import com.android.camera.app.OrientationManager;
81 import com.android.camera.app.OrientationManagerImpl;
82 import com.android.camera.data.CameraDataAdapter;
83 import com.android.camera.data.FixedLastDataAdapter;
84 import com.android.camera.data.InProgressDataWrapper;
85 import com.android.camera.data.LocalData;
86 import com.android.camera.data.LocalDataAdapter;
87 import com.android.camera.data.LocalDataUtil;
88 import com.android.camera.data.LocalMediaObserver;
89 import com.android.camera.data.MediaDetails;
90 import com.android.camera.data.PanoramaMetadataLoader;
91 import com.android.camera.data.RgbzMetadataLoader;
92 import com.android.camera.data.SimpleViewData;
93 import com.android.camera.filmstrip.FilmstripContentPanel;
94 import com.android.camera.filmstrip.FilmstripController;
95 import com.android.camera.hardware.HardwareSpec;
96 import com.android.camera.hardware.HardwareSpecImpl;
97 import com.android.camera.module.ModuleController;
98 import com.android.camera.module.ModulesInfo;
99 import com.android.camera.session.CaptureSessionManager;
100 import com.android.camera.session.CaptureSessionManager.SessionListener;
101 import com.android.camera.settings.CameraSettingsActivity;
102 import com.android.camera.settings.SettingsManager;
103 import com.android.camera.settings.SettingsManager.SettingsCapabilities;
104 import com.android.camera.settings.SettingsUtil;
105 import com.android.camera.tinyplanet.TinyPlanetFragment;
106 import com.android.camera.ui.DetailsDialog;
107 import com.android.camera.ui.MainActivityLayout;
108 import com.android.camera.ui.ModeListView;
109 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
110 import com.android.camera.ui.PreviewStatusListener;
111 import com.android.camera.util.ApiHelper;
112 import com.android.camera.util.Callback;
113 import com.android.camera.util.CameraUtil;
114 import com.android.camera.util.FeedbackHelper;
115 import com.android.camera.util.GalleryHelper;
116 import com.android.camera.util.GcamHelper;
117 import com.android.camera.util.IntentHelper;
118 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
119 import com.android.camera.util.ReleaseDialogHelper;
120 import com.android.camera.util.UsageStatistics;
121 import com.android.camera.widget.FilmstripView;
122 import com.android.camera2.R;
123 import com.google.common.logging.eventprotos;
124 import com.google.common.logging.eventprotos.CameraEvent.InteractionCause;
125 import com.google.common.logging.eventprotos.NavigationChange;
128 import java.io.FileInputStream;
129 import java.io.FileNotFoundException;
130 import java.lang.ref.WeakReference;
131 import java.util.ArrayList;
132 import java.util.List;
134 public class CameraActivity extends Activity
135 implements AppController, CameraManager.CameraOpenCallback,
136 ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
137 OrientationManager.OnOrientationChangeListener {
139 private static final String TAG = "CameraActivity";
141 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
142 "android.media.action.STILL_IMAGE_CAMERA_SECURE";
143 public static final String ACTION_IMAGE_CAPTURE_SECURE =
144 "android.media.action.IMAGE_CAPTURE_SECURE";
146 // The intent extra for camera from secure lock screen. True if the gallery
147 // should only show newly captured pictures. sSecureAlbumId does not
148 // increment. This is used when switching between camera, camcorder, and
149 // panorama. If the extra is not set, it is in the normal camera mode.
150 public static final String SECURE_CAMERA_EXTRA = "secure_camera";
153 * Request code from an activity we started that indicated that we do not
154 * want to reset the view to the preview in onResume.
156 public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
158 public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
160 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
161 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
162 private static final int MAX_PEEK_BITMAP_PIXELS = 1600000; // 1.6 * 4 MBs.
164 /** Should be used wherever a context is needed. */
165 private Context mAppContext;
168 * Whether onResume should reset the view to the preview.
170 private boolean mResetToPreviewOnResume = true;
173 * This data adapter is used by FilmStripView.
175 private LocalDataAdapter mDataAdapter;
178 * TODO: This should be moved to the app level.
180 private SettingsManager mSettingsManager;
182 private ModeListView mModeListView;
183 private int mCurrentModeIndex;
184 private CameraModule mCurrentModule;
185 private ModuleManagerImpl mModuleManager;
186 private FrameLayout mAboveFilmstripControlLayout;
187 private FilmstripController mFilmstripController;
188 private boolean mFilmstripVisible;
189 private int mResultCodeForTesting;
190 private Intent mResultDataForTesting;
191 private OnScreenHint mStorageHint;
192 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
193 private boolean mAutoRotateScreen;
194 private boolean mSecureCamera;
195 private int mLastRawOrientation;
196 private OrientationManagerImpl mOrientationManager;
197 private LocationManager mLocationManager;
198 private ButtonManager mButtonManager;
199 private Handler mMainHandler;
200 private PanoramaViewHelper mPanoramaViewHelper;
201 private ActionBar mActionBar;
202 private ViewGroup mUndoDeletionBar;
203 private boolean mIsUndoingDeletion = false;
205 private final Uri[] mNfcPushUris = new Uri[1];
207 private LocalMediaObserver mLocalImagesObserver;
208 private LocalMediaObserver mLocalVideosObserver;
210 private boolean mPendingDeletion = false;
212 private CameraController mCameraController;
213 private boolean mPaused;
214 private CameraAppUI mCameraAppUI;
216 private PeekAnimationHandler mPeekAnimationHandler;
217 private HandlerThread mPeekAnimationThread;
219 private FeedbackHelper mFeedbackHelper;
221 private Intent mGalleryIntent;
222 private long mOnCreateTime;
224 private Menu mActionBarMenu;
227 public CameraAppUI getCameraAppUI() {
231 // close activity when screen turns off
232 private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
234 public void onReceive(Context context, Intent intent) {
240 * Whether the screen is kept turned on.
242 private boolean mKeepScreenOn;
243 private int mLastLayoutOrientation;
244 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
245 new CameraAppUI.BottomPanel.Listener() {
248 * If the current photo is a photo sphere, this will launch the
249 * Photo Sphere panorama viewer.
252 public void onExternalViewer() {
253 if (mPanoramaViewHelper == null) {
256 final LocalData data = getCurrentLocalData();
260 final Uri contentUri = data.getContentUri();
261 if (contentUri == Uri.EMPTY) {
265 if (PanoramaMetadataLoader.isPanoramaAndUseViewer(data)) {
266 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
267 } else if (RgbzMetadataLoader.hasRGBZData(data)) {
268 mPanoramaViewHelper.showRgbz(contentUri);
273 public void onEdit() {
274 LocalData data = getCurrentLocalData();
282 public void onTinyPlanet() {
283 LocalData data = getCurrentLocalData();
287 launchTinyPlanetEditor(data);
291 public void onDelete() {
292 final int currentDataId = getCurrentDataId();
293 UsageStatistics.photoInteraction(
294 UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
295 eventprotos.CameraEvent.InteractionType.DELETE,
296 InteractionCause.BUTTON);
297 removeData(currentDataId);
301 public void onShare() {
302 final LocalData data = getCurrentLocalData();
304 // If applicable, show release information before this item
306 if (PanoramaMetadataLoader.isPanorama(data)
307 || RgbzMetadataLoader.hasRGBZData(data)) {
308 ReleaseDialogHelper.showReleaseInfoDialog(CameraActivity.this,
309 new Callback<Void>() {
311 public void onCallback(Void result) {
320 private void share(LocalData data) {
321 Intent shareIntent = getShareIntentByData(data);
322 if (shareIntent != null) {
324 launchActivityByIntent(shareIntent);
325 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
326 } catch (ActivityNotFoundException ex) {
332 private int getCurrentDataId() {
333 return mFilmstripController.getCurrentId();
336 private LocalData getCurrentLocalData() {
337 return mDataAdapter.getLocalData(getCurrentDataId());
341 * Sets up the share intent and NFC properly according to the
344 * @param data The data to be shared.
346 private Intent getShareIntentByData(final LocalData data) {
347 Intent intent = null;
348 final Uri contentUri = data.getContentUri();
349 if (PanoramaMetadataLoader.isPanorama360(data) &&
350 data.getContentUri() != Uri.EMPTY) {
351 intent = new Intent(Intent.ACTION_SEND);
352 intent.setType("application/vnd.google.panorama360+jpg");
353 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
354 } else if (data.isDataActionSupported(LocalData.DATA_ACTION_SHARE)) {
355 final String mimeType = data.getMimeType();
356 intent = getShareIntentFromType(mimeType);
357 if (intent != null) {
358 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
359 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
361 intent = Intent.createChooser(intent, null);
367 * Get the share intent according to the mimeType
369 * @param mimeType The mimeType of current data.
370 * @return the video/image's ShareIntent or null if mimeType is
373 private Intent getShareIntentFromType(String mimeType) {
374 // Lazily create the intent object.
375 Intent intent = new Intent(Intent.ACTION_SEND);
376 if (mimeType.startsWith("video/")) {
377 intent.setType("video/*");
379 if (mimeType.startsWith("image/")) {
380 intent.setType("image/*");
382 Log.w(TAG, "unsupported mimeType " + mimeType);
389 private ComboPreferences mPreferences;
392 public void onCameraOpened(CameraManager.CameraProxy camera) {
394 * The current UI requires that the flash option visibility in front-facing
396 * * disabled if back facing camera supports flash
397 * * hidden if back facing camera does not support flash
398 * We save whether back facing camera supports flash because we cannot get
399 * this in front facing camera without a camera switch.
401 * If this preference is cleared, we also need to clear the camera facing
402 * setting so we default to opening the camera in back facing camera, and
403 * can save this flash support value again.
405 if (!mSettingsManager.isSet(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA)) {
406 HardwareSpec hardware = new HardwareSpecImpl(camera.getParameters());
407 mSettingsManager.setBoolean(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA,
408 hardware.isFlashSupported());
411 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
412 // We shouldn't be here. Just close the camera and leave.
413 camera.release(false);
414 throw new IllegalStateException("Camera opened but the module shouldn't be " +
417 if (mCurrentModule != null) {
418 SettingsCapabilities capabilities =
419 SettingsUtil.getSettingsCapabilities(camera);
420 mSettingsManager.changeCamera(camera.getCameraId(), capabilities);
421 mCurrentModule.onCameraAvailable(camera);
423 mCameraAppUI.onChangeCamera();
427 public void onCameraDisabled(int cameraId) {
428 UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.SECURITY);
430 CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
434 public void onDeviceOpenFailure(int cameraId) {
435 UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.OPEN_FAILURE);
437 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
441 public void onDeviceOpenedAlready(int cameraId) {
442 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
446 public void onReconnectionFailure(CameraManager mgr) {
447 UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE);
449 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
452 private static class MainHandler extends Handler {
453 final WeakReference<CameraActivity> mActivity;
455 public MainHandler(CameraActivity activity, Looper looper) {
457 mActivity = new WeakReference<CameraActivity>(activity);
461 public void handleMessage(Message msg) {
462 CameraActivity activity = mActivity.get();
463 if (activity == null) {
468 case MSG_CLEAR_SCREEN_ON_FLAG: {
469 if (!activity.mPaused) {
470 activity.getWindow().clearFlags(
471 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
479 private String fileNameFromDataID(int dataID) {
480 final LocalData localData = mDataAdapter.getLocalData(dataID);
482 File localFile = new File(localData.getPath());
483 return localFile.getName();
486 private final FilmstripContentPanel.Listener mFilmstripListener =
487 new FilmstripContentPanel.Listener() {
490 public void onSwipeOut() {
491 UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
492 eventprotos.CameraEvent.InteractionCause.SWIPE_RIGHT);
496 public void onSwipeOutBegin() {
501 public void onFilmstripHidden() {
502 mFilmstripVisible = false;
503 CameraActivity.this.setFilmstripUiVisibility(false);
504 // When the user hide the filmstrip (either swipe out or
505 // tap on back key) we move to the first item so next time
506 // when the user swipe in the filmstrip, the most recent
508 mFilmstripController.goToFirstItem();
509 if (mCurrentModule != null) {
511 .onPreviewVisibilityChanged(ModuleController.VISIBILITY_VISIBLE);
516 public void onFilmstripShown() {
517 mFilmstripVisible = true;
518 decrementPeekAnimPlayTimes();
519 updateUiByData(mFilmstripController.getCurrentId());
520 if (mCurrentModule != null) {
522 .onPreviewVisibilityChanged(ModuleController.VISIBILITY_HIDDEN);
527 public void onFocusedDataLongPressed(int dataId) {
532 public void onFocusedDataPromoted(int dataID) {
533 UsageStatistics.photoInteraction(
534 UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
535 eventprotos.CameraEvent.InteractionType.DELETE,
536 InteractionCause.SWIPE_UP);
542 public void onFocusedDataDemoted(int dataID) {
543 UsageStatistics.photoInteraction(
544 UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
545 eventprotos.CameraEvent.InteractionType.DELETE,
546 InteractionCause.SWIPE_DOWN);
552 public void onEnterFullScreenUiShown(int dataId) {
553 if (mFilmstripVisible) {
554 CameraActivity.this.setFilmstripUiVisibility(true);
559 public void onLeaveFullScreenUiShown(int dataId) {
564 public void onEnterFullScreenUiHidden(int dataId) {
565 if (mFilmstripVisible) {
566 CameraActivity.this.setFilmstripUiVisibility(false);
571 public void onLeaveFullScreenUiHidden(int dataId) {
576 public void onEnterFilmstrip(int dataId) {
577 if (mFilmstripVisible) {
578 CameraActivity.this.setFilmstripUiVisibility(true);
583 public void onLeaveFilmstrip(int dataId) {
588 public void onDataReloaded() {
589 if (!mFilmstripVisible) {
592 updateUiByData(mFilmstripController.getCurrentId());
596 public void onDataUpdated(int dataId) {
597 if (!mFilmstripVisible) {
600 updateUiByData(mFilmstripController.getCurrentId());
604 public void onEnterZoomView(int dataID) {
605 if (mFilmstripVisible) {
606 CameraActivity.this.setFilmstripUiVisibility(false);
611 public void onDataFocusChanged(final int prevDataId, final int newDataId) {
612 if (!mFilmstripVisible) {
615 // TODO: This callback is UI event callback, should always
616 // happen on UI thread. Find the reason for this
617 // runOnUiThread() and fix it.
618 runOnUiThread(new Runnable() {
621 updateUiByData(newDataId);
627 private final LocalDataAdapter.LocalDataListener mLocalDataListener =
628 new LocalDataAdapter.LocalDataListener() {
630 public void onMetadataUpdated(List<Integer> updatedData) {
631 int currentDataId = mFilmstripController.getCurrentId();
632 for (Integer dataId : updatedData) {
633 if (dataId == currentDataId) {
634 updateBottomControlsByData(mDataAdapter.getLocalData(dataId));
640 public void onNewDataAdded(LocalData data) {
641 startPeekAnimation(data);
645 public void gotoGallery() {
646 UsageStatistics.changeScreen(NavigationChange.Mode.FILMSTRIP,
647 InteractionCause.BUTTON);
649 mFilmstripController.goToNextItem();
653 * If 'visible' is false, this hides the action bar and switches the
654 * filmstrip UI to lights-out mode.
656 * @param visible is false, this hides the action bar and switches the
657 * filmstrip UI to lights-out mode.
659 private void setFilmstripUiVisibility(boolean visible) {
660 int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
661 int newSystemUIVisibility = (visible ? View.SYSTEM_UI_FLAG_VISIBLE
662 : View.SYSTEM_UI_FLAG_FULLSCREEN);
663 if (newSystemUIVisibility != currentSystemUIVisibility) {
664 mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
667 mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
668 if (visible != mActionBar.isShowing()) {
677 private void hideSessionProgress() {
678 mCameraAppUI.getFilmstripBottomControls().hideProgress();
681 private void showSessionProgress(CharSequence message) {
682 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
683 controls.setProgressText(message);
684 controls.hideControls();
685 controls.showProgress();
688 private void updateSessionProgress(int progress) {
689 mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
692 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
693 private void setupNfcBeamPush() {
694 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
695 if (adapter == null) {
699 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
701 adapter.setNdefPushMessage(null, CameraActivity.this);
705 adapter.setBeamPushUris(null, CameraActivity.this);
706 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
708 public Uri[] createBeamUris(NfcEvent event) {
711 }, CameraActivity.this);
715 public void onMenuVisibilityChanged(boolean isVisible) {
716 // TODO: Remove this or bring back the original implementation: cancel
717 // auto-hide actionbar.
721 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
722 int currentDataId = mFilmstripController.getCurrentId();
723 if (currentDataId < 0) {
726 UsageStatistics.photoInteraction(
727 UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
728 eventprotos.CameraEvent.InteractionType.SHARE,
729 InteractionCause.BUTTON);
730 // TODO add intent.getComponent().getPackageName()
734 // Note: All callbacks come back on the main thread.
735 private final SessionListener mSessionListener =
736 new SessionListener() {
738 public void onSessionQueued(final Uri uri) {
740 int dataID = mDataAdapter.findDataByContentUri(uri);
742 // Don't allow special UI actions (swipe to
743 // delete, for example) on in-progress data.
744 LocalData d = mDataAdapter.getLocalData(dataID);
745 InProgressDataWrapper newData = new InProgressDataWrapper(d);
746 mDataAdapter.updateData(dataID, newData);
751 public void onSessionDone(final Uri uri) {
752 Log.v(TAG, "onSessionDone:" + uri);
753 int doneID = mDataAdapter.findDataByContentUri(uri);
754 int currentDataId = mFilmstripController.getCurrentId();
756 if (currentDataId == doneID) {
757 hideSessionProgress();
758 updateSessionProgress(0);
760 mDataAdapter.refresh(uri, /* isInProgress */false);
764 public void onSessionProgress(final Uri uri, final int progress) {
766 // Do nothing, there is no task for this URI.
769 int currentDataId = mFilmstripController.getCurrentId();
770 if (currentDataId == -1) {
774 mDataAdapter.getLocalData(currentDataId).getContentUri())) {
775 updateSessionProgress(progress);
780 public void onSessionUpdated(Uri uri) {
781 mDataAdapter.refresh(uri, /* isInProgress */true);
786 public Context getAndroidContext() {
791 public void launchActivityByIntent(Intent intent) {
792 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
796 public int getCurrentModuleIndex() {
797 return mCurrentModeIndex;
801 public ModuleController getCurrentModuleController() {
802 return mCurrentModule;
806 public int getQuickSwitchToModuleId(int currentModuleIndex) {
807 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
812 public SurfaceTexture getPreviewBuffer() {
813 // TODO: implement this
818 public void onPreviewReadyToStart() {
819 mCameraAppUI.onPreviewReadyToStart();
823 public void onPreviewStarted() {
824 mCameraAppUI.onPreviewStarted();
828 public void addPreviewAreaSizeChangedListener(
829 PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
830 mCameraAppUI.addPreviewAreaSizeChangedListener(listener);
834 public void removePreviewAreaSizeChangedListener(
835 PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
836 mCameraAppUI.removePreviewAreaSizeChangedListener(listener);
840 public void setupOneShotPreviewListener() {
841 mCameraController.setOneShotPreviewCallback(mMainHandler,
842 new CameraManager.CameraPreviewDataCallback() {
844 public void onPreviewFrame(byte[] data, CameraManager.CameraProxy camera) {
845 mCurrentModule.onPreviewInitialDataReceived();
846 mCameraAppUI.onNewPreviewFrame();
852 public void updatePreviewAspectRatio(float aspectRatio) {
853 mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
857 public boolean shouldShowShimmy() {
858 int remainingTimes = mSettingsManager.getInt(
859 SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX);
860 return remainingTimes > 0;
864 public void decrementShimmyPlayTimes() {
865 int remainingTimes = mSettingsManager.getInt(
866 SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX) - 1;
867 if (remainingTimes >= 0) {
868 mSettingsManager.setInt(SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX,
874 public void updatePreviewTransform(Matrix matrix) {
875 mCameraAppUI.updatePreviewTransform(matrix);
879 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
880 mCameraAppUI.setPreviewStatusListener(previewStatusListener);
884 public FrameLayout getModuleLayoutRoot() {
885 return mCameraAppUI.getModuleRootView();
889 public void setShutterEventsListener(ShutterEventsListener listener) {
890 // TODO: implement this
894 public void setShutterEnabled(boolean enabled) {
895 // TODO: implement this
899 public boolean isShutterEnabled() {
900 // TODO: implement this
905 public void startPreCaptureAnimation() {
906 mCameraAppUI.startPreCaptureAnimation();
910 public void cancelPreCaptureAnimation() {
911 // TODO: implement this
915 public void startPostCaptureAnimation() {
916 // TODO: implement this
920 public void startPostCaptureAnimation(Bitmap thumbnail) {
921 // TODO: implement this
925 public void cancelPostCaptureAnimation() {
926 // TODO: implement this
930 public OrientationManager getOrientationManager() {
931 return mOrientationManager;
935 public LocationManager getLocationManager() {
936 return mLocationManager;
940 public void lockOrientation() {
941 if (mOrientationManager != null) {
942 mOrientationManager.lockOrientation();
947 public void unlockOrientation() {
948 if (mOrientationManager != null) {
949 mOrientationManager.unlockOrientation();
954 * Decrement the remaining play times for peek animation.
956 private void decrementPeekAnimPlayTimes() {
957 int remainingTimes = mSettingsManager.getInt(
958 SettingsManager.SETTING_FILMSTRIP_PEEK_ANIM_REMAINING_PLAY_TIMES_INDEX) - 1;
959 if (remainingTimes < 0) {
963 .setInt(SettingsManager.SETTING_FILMSTRIP_PEEK_ANIM_REMAINING_PLAY_TIMES_INDEX,
968 * Starts the filmstrip peek animation if the filmstrip is not visible.
969 * Only {@link LocalData#LOCAL_IMAGE}, {@link
970 * LocalData#LOCAL_IN_PROGRESS_DATA} and {@link
971 * LocalData#LOCAL_VIDEO} are supported.
973 * @param data The data to peek.
975 private void startPeekAnimation(final LocalData data) {
976 if (mFilmstripVisible || mPeekAnimationHandler == null) {
980 int dataType = data.getLocalDataType();
981 if (dataType != LocalData.LOCAL_IMAGE && dataType != LocalData.LOCAL_IN_PROGRESS_DATA &&
982 dataType != LocalData.LOCAL_VIDEO) {
986 int remainingTimes = mSettingsManager.getInt(
987 SettingsManager.SETTING_FILMSTRIP_PEEK_ANIM_REMAINING_PLAY_TIMES_INDEX);
988 if (remainingTimes <= 0) {
991 mPeekAnimationHandler.startDecodingJob(data, new Callback<Bitmap>() {
993 public void onCallback(Bitmap result) {
994 mCameraAppUI.startPeekAnimation(result, true);
1000 public void notifyNewMedia(Uri uri) {
1001 ContentResolver cr = getContentResolver();
1002 String mimeType = cr.getType(uri);
1003 if (LocalDataUtil.isMimeTypeVideo(mimeType)) {
1004 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1005 mDataAdapter.addNewVideo(uri);
1006 } else if (LocalDataUtil.isMimeTypeImage(mimeType)) {
1007 CameraUtil.broadcastNewPicture(mAppContext, uri);
1008 mDataAdapter.addNewPhoto(uri);
1009 } else if (LocalDataUtil.isMimeTypePlaceHolder(mimeType)) {
1010 mDataAdapter.addNewPhoto(uri);
1012 android.util.Log.w(TAG, "Unknown new media with MIME type:"
1013 + mimeType + ", uri:" + uri);
1018 public void enableKeepScreenOn(boolean enabled) {
1023 mKeepScreenOn = enabled;
1024 if (mKeepScreenOn) {
1025 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1026 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1028 keepScreenOnForAWhile();
1033 public CameraProvider getCameraProvider() {
1034 return mCameraController;
1037 private void removeData(int dataID) {
1038 mDataAdapter.removeData(dataID);
1039 if (mDataAdapter.getTotalNumber() > 1) {
1040 showUndoDeletionBar();
1042 // If camera preview is the only view left in filmstrip,
1043 // no need to show undo bar.
1044 mPendingDeletion = true;
1046 if (mFilmstripVisible) {
1047 mCameraAppUI.getFilmstripContentPanel().animateHide();
1053 public boolean onOptionsItemSelected(MenuItem item) {
1054 // Handle presses on the action bar items
1055 switch (item.getItemId()) {
1056 case android.R.id.home:
1057 if (mFilmstripVisible && startGallery()) {
1062 case R.id.action_details:
1063 showDetailsDialog(mFilmstripController.getCurrentId());
1066 return super.onOptionsItemSelected(item);
1070 private boolean isCaptureIntent() {
1071 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1072 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1073 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1080 private final SettingsManager.StrictUpgradeCallback mStrictUpgradeCallback
1081 = new SettingsManager.StrictUpgradeCallback() {
1083 public void upgrade(SettingsManager settingsManager, int version) {
1084 // Show the location dialog on upgrade if
1085 // (a) the user has never set this option (status quo).
1086 // (b) the user opt'ed out previously.
1087 if (settingsManager.isSet(SettingsManager.SETTING_RECORD_LOCATION) &&
1088 !settingsManager.getBoolean(SettingsManager.SETTING_RECORD_LOCATION)) {
1089 settingsManager.remove(SettingsManager.SETTING_RECORD_LOCATION);
1096 public void onCreate(Bundle state) {
1097 super.onCreate(state);
1098 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1099 mOnCreateTime = System.currentTimeMillis();
1100 mAppContext = getApplicationContext();
1101 GcamHelper.init(getContentResolver());
1103 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1104 setContentView(R.layout.activity_main);
1105 mActionBar = getActionBar();
1106 mActionBar.addOnMenuVisibilityListener(this);
1107 mMainHandler = new MainHandler(this, getMainLooper());
1109 new CameraController(mAppContext, this, mMainHandler,
1110 CameraManagerFactory.getAndroidCameraManager());
1111 mPreferences = new ComboPreferences(mAppContext);
1113 mSettingsManager = new SettingsManager(mAppContext, this,
1114 mCameraController.getNumberOfCameras(), mStrictUpgradeCallback);
1116 // Remove this after we get rid of ComboPreferences.
1117 int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID));
1118 mPreferences.setLocalId(mAppContext, cameraId);
1119 CameraSettings.upgradeGlobalPreferences(mPreferences,
1120 mCameraController.getNumberOfCameras());
1121 // TODO: Try to move all the resources allocation to happen as soon as
1122 // possible so we can call module.init() at the earliest time.
1123 mModuleManager = new ModuleManagerImpl();
1124 ModulesInfo.setupModules(mAppContext, mModuleManager);
1126 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1127 mModeListView.init(mModuleManager.getSupportedModeIndexList());
1128 if (ApiHelper.HAS_ROTATION_ANIMATION) {
1129 setRotationAnimation();
1131 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1133 public void onVisibilityChanged(boolean visible) {
1134 if (mCurrentModule != null) {
1135 int visibility = visible ? ModuleController.VISIBILITY_COVERED
1136 : ModuleController.VISIBILITY_VISIBLE;
1137 mCurrentModule.onPreviewVisibilityChanged(visibility);
1142 // Check if this is in the secure camera mode.
1143 Intent intent = getIntent();
1144 String action = intent.getAction();
1145 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1146 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1147 mSecureCamera = true;
1149 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1152 if (mSecureCamera) {
1153 // Foreground event caused by lock screen startup.
1154 // It is necessary to log this in onCreate, to avoid the
1155 // onResume->onPause->onResume sequence.
1156 UsageStatistics.foregrounded(
1157 eventprotos.ForegroundEvent.ForegroundSource.LOCK_SCREEN);
1159 // Change the window flags so that secure camera can show when
1161 Window win = getWindow();
1162 WindowManager.LayoutParams params = win.getAttributes();
1163 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1164 win.setAttributes(params);
1166 // Filter for screen off so that we can finish secure camera
1168 // when screen is off.
1169 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1170 registerReceiver(mScreenOffReceiver, filter);
1172 mCameraAppUI = new CameraAppUI(this,
1173 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1175 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1177 mAboveFilmstripControlLayout =
1178 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1180 // Add the session listener so we can track the session progress
1182 getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1183 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1184 mFilmstripController.setImageGap(
1185 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1186 mPanoramaViewHelper = new PanoramaViewHelper(this);
1187 mPanoramaViewHelper.onCreate();
1188 // Set up the camera preview first so the preview shows up ASAP.
1189 mDataAdapter = new CameraDataAdapter(mAppContext,
1190 new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
1191 mDataAdapter.setLocalDataListener(mLocalDataListener);
1193 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1195 mLocationManager = new LocationManager(mAppContext);
1198 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1199 int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1200 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1201 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1202 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1203 modeIndex = videoIndex;
1204 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1205 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1207 modeIndex = photoIndex;
1208 if (mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX)
1209 == gcamIndex && GcamHelper.hasGcamCapture()) {
1210 modeIndex = gcamIndex;
1212 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1213 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1214 modeIndex = photoIndex;
1216 // If the activity has not been started using an explicit intent,
1217 // read the module index from the last time the user changed modes
1218 modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1219 if ((modeIndex == gcamIndex &&
1220 !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1221 modeIndex = photoIndex;
1225 mOrientationManager = new OrientationManagerImpl(this);
1226 mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1228 setModuleFromModeIndex(modeIndex);
1229 mCameraAppUI.prepareModuleUI();
1230 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1232 if (!mSecureCamera) {
1233 mFilmstripController.setDataAdapter(mDataAdapter);
1234 if (!isCaptureIntent()) {
1235 mDataAdapter.requestLoad();
1238 // Put a lock placeholder as the last image by setting its date to
1240 ImageView v = (ImageView) getLayoutInflater().inflate(
1241 R.layout.secure_album_placeholder, null);
1242 v.setOnClickListener(new View.OnClickListener() {
1244 public void onClick(View view) {
1245 UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY,
1246 InteractionCause.BUTTON);
1251 mDataAdapter = new FixedLastDataAdapter(
1256 v.getDrawable().getIntrinsicWidth(),
1257 v.getDrawable().getIntrinsicHeight(),
1259 // Flush out all the original data.
1260 mDataAdapter.flush();
1261 mFilmstripController.setDataAdapter(mDataAdapter);
1266 mLocalImagesObserver = new LocalMediaObserver();
1267 mLocalVideosObserver = new LocalMediaObserver();
1269 getContentResolver().registerContentObserver(
1270 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1271 mLocalImagesObserver);
1272 getContentResolver().registerContentObserver(
1273 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1274 mLocalVideosObserver);
1275 if (FeedbackHelper.feedbackAvailable()) {
1276 mFeedbackHelper = new FeedbackHelper(mAppContext);
1280 private void setRotationAnimation() {
1281 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1282 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1283 Window win = getWindow();
1284 WindowManager.LayoutParams winParams = win.getAttributes();
1285 winParams.rotationAnimation = rotationAnimation;
1286 win.setAttributes(winParams);
1290 public void onUserInteraction() {
1291 super.onUserInteraction();
1292 if (!isFinishing()) {
1293 keepScreenOnForAWhile();
1298 public boolean dispatchTouchEvent(MotionEvent ev) {
1299 boolean result = super.dispatchTouchEvent(ev);
1300 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1301 // Real deletion is postponed until the next user interaction after
1302 // the gesture that triggers deletion. Until real deletion is
1303 // performed, users can click the undo button to bring back the
1304 // image that they chose to delete.
1305 if (mPendingDeletion && !mIsUndoingDeletion) {
1313 public void onPause() {
1315 mPeekAnimationHandler = null;
1316 mPeekAnimationThread.quitSafely();
1317 mPeekAnimationThread = null;
1318 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1320 // Delete photos that are pending deletion
1322 mCurrentModule.pause();
1323 mOrientationManager.pause();
1324 // Close the camera and wait for the operation done.
1325 mCameraController.closeCamera();
1326 mPanoramaViewHelper.onPause();
1328 mLocalImagesObserver.setActivityPaused(true);
1329 mLocalVideosObserver.setActivityPaused(true);
1335 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1336 if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1337 mResetToPreviewOnResume = false;
1339 super.onActivityResult(requestCode, resultCode, data);
1344 public void onResume() {
1346 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1348 mLastLayoutOrientation = getResources().getConfiguration().orientation;
1350 // TODO: Handle this in OrientationManager.
1352 if (Settings.System.getInt(getContentResolver(),
1353 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1354 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1355 mAutoRotateScreen = false;
1357 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1358 mAutoRotateScreen = true;
1361 if (isCaptureIntent()) {
1362 // Foreground event caused by photo or video capure intent.
1363 UsageStatistics.foregrounded(
1364 eventprotos.ForegroundEvent.ForegroundSource.INTENT_PICKER);
1365 } else if (!mSecureCamera) {
1366 // Foreground event that is not caused by an intent.
1367 UsageStatistics.foregrounded(
1368 eventprotos.ForegroundEvent.ForegroundSource.ICON_LAUNCHER);
1371 Drawable galleryLogo;
1372 if (mSecureCamera) {
1373 mGalleryIntent = null;
1376 mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext);
1377 galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1379 if (galleryLogo == null) {
1381 galleryLogo = getPackageManager().getActivityLogo(getComponentName());
1382 } catch (PackageManager.NameNotFoundException e) {
1383 Log.e(TAG, "Can't get the activity logo");
1386 if (mGalleryIntent != null) {
1387 mActionBar.setDisplayUseLogoEnabled(true);
1389 mActionBar.setLogo(galleryLogo);
1390 mOrientationManager.resume();
1392 mPeekAnimationThread = new HandlerThread("Peek animation");
1393 mPeekAnimationThread.start();
1394 mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
1395 mCurrentModule.resume();
1396 setSwipingEnabled(true);
1398 if (mResetToPreviewOnResume) {
1399 mCameraAppUI.resume();
1401 LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1403 mDataAdapter.refresh(data.getContentUri(), false);
1406 // The share button might be disabled to avoid double tapping.
1407 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1408 // Default is showing the preview, unless disabled by explicitly
1409 // starting an activity we want to return from to the filmstrip rather
1410 // than the preview.
1411 mResetToPreviewOnResume = true;
1413 if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1414 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1415 if (!mSecureCamera) {
1416 // If it's secure camera, requestLoad() should not be called
1417 // as it will load all the data.
1418 if (!mFilmstripVisible) {
1419 mDataAdapter.requestLoad();
1423 mLocalImagesObserver.setActivityPaused(false);
1424 mLocalVideosObserver.setActivityPaused(false);
1426 keepScreenOnForAWhile();
1428 // Lights-out mode at all times.
1429 findViewById(R.id.activity_root_view)
1430 .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
1431 mPanoramaViewHelper.onResume();
1432 ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1433 syncLocationManagerSetting();
1437 public void onStart() {
1439 mPanoramaViewHelper.onStart();
1443 protected void onStop() {
1444 mPanoramaViewHelper.onStop();
1445 if (mFeedbackHelper != null) {
1446 mFeedbackHelper.stopFeedback();
1449 mLocationManager.disconnect();
1454 public void onDestroy() {
1455 if (mSecureCamera) {
1456 unregisterReceiver(mScreenOffReceiver);
1458 mActionBar.removeOnMenuVisibilityListener(this);
1459 mSettingsManager.removeAllListeners();
1460 mCameraController.removeCallbackReceiver();
1461 getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1462 getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1463 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
1464 mCameraAppUI.onDestroy();
1465 mCameraController = null;
1466 mSettingsManager = null;
1467 mCameraAppUI = null;
1468 mOrientationManager = null;
1469 mButtonManager = null;
1470 CameraManagerFactory.recycle();
1475 public void onConfigurationChanged(Configuration config) {
1476 super.onConfigurationChanged(config);
1477 Log.v(TAG, "onConfigurationChanged");
1478 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1482 if (mLastLayoutOrientation != config.orientation) {
1483 mLastLayoutOrientation = config.orientation;
1484 mCurrentModule.onLayoutOrientationChanged(
1485 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1490 public boolean onKeyDown(int keyCode, KeyEvent event) {
1491 if (!mFilmstripVisible) {
1492 if (mCurrentModule.onKeyDown(keyCode, event)) {
1495 // Prevent software keyboard or voice search from showing up.
1496 if (keyCode == KeyEvent.KEYCODE_SEARCH
1497 || keyCode == KeyEvent.KEYCODE_MENU) {
1498 if (event.isLongPress()) {
1504 return super.onKeyDown(keyCode, event);
1508 public boolean onKeyUp(int keyCode, KeyEvent event) {
1509 if (!mFilmstripVisible && mCurrentModule.onKeyUp(keyCode, event)) {
1512 return super.onKeyUp(keyCode, event);
1516 public void onBackPressed() {
1517 if (!mCameraAppUI.onBackPressed()) {
1518 if (!mCurrentModule.onBackPressed()) {
1519 super.onBackPressed();
1525 public boolean isAutoRotateScreen() {
1526 // TODO: Move to OrientationManager.
1527 return mAutoRotateScreen;
1531 public boolean onCreateOptionsMenu(Menu menu) {
1532 MenuInflater inflater = getMenuInflater();
1533 inflater.inflate(R.menu.filmstrip_menu, menu);
1534 mActionBarMenu = menu;
1535 return super.onCreateOptionsMenu(menu);
1538 protected void updateStorageSpace() {
1539 mStorageSpaceBytes = Storage.getAvailableSpace();
1542 protected long getStorageSpaceBytes() {
1543 return mStorageSpaceBytes;
1546 protected void updateStorageSpaceAndHint() {
1547 updateStorageSpace();
1548 updateStorageHint(mStorageSpaceBytes);
1551 protected void updateStorageHint(long storageSpace) {
1552 String message = null;
1553 if (storageSpace == Storage.UNAVAILABLE) {
1554 message = getString(R.string.no_storage);
1555 } else if (storageSpace == Storage.PREPARING) {
1556 message = getString(R.string.preparing_sd);
1557 } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1558 message = getString(R.string.access_sd_fail);
1559 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1560 message = getString(R.string.spaceIsLow_content);
1563 if (message != null) {
1564 if (mStorageHint == null) {
1565 mStorageHint = OnScreenHint.makeText(mAppContext, message);
1567 mStorageHint.setText(message);
1569 mStorageHint.show();
1570 } else if (mStorageHint != null) {
1571 mStorageHint.cancel();
1572 mStorageHint = null;
1576 protected void setResultEx(int resultCode) {
1577 mResultCodeForTesting = resultCode;
1578 setResult(resultCode);
1581 protected void setResultEx(int resultCode, Intent data) {
1582 mResultCodeForTesting = resultCode;
1583 mResultDataForTesting = data;
1584 setResult(resultCode, data);
1587 public int getResultCode() {
1588 return mResultCodeForTesting;
1591 public Intent getResultData() {
1592 return mResultDataForTesting;
1595 public boolean isSecureCamera() {
1596 return mSecureCamera;
1600 public boolean isPaused() {
1605 public void onModeSelected(int modeIndex) {
1606 if (mCurrentModeIndex == modeIndex) {
1610 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
1611 // Record last used camera mode for quick switching
1612 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
1613 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
1614 mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX,
1618 closeModule(mCurrentModule);
1619 int oldModuleIndex = mCurrentModeIndex;
1621 // Refocus and Gcam are modes that cannot be selected
1622 // from the mode list view, because they are not list items.
1623 // Check whether we should interpret MODULE_CRAFT as either.
1624 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
1625 boolean hdrPlusOn = mSettingsManager.isHdrPlusOn();
1626 if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1627 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1631 setModuleFromModeIndex(modeIndex);
1633 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
1634 mCameraAppUI.addShutterListener(mCurrentModule);
1635 openModule(mCurrentModule);
1636 mCurrentModule.onOrientationChanged(mLastRawOrientation);
1637 // Store the module index so we can use it the next time the Camera
1639 SharedPreferences prefs = PreferenceManager
1640 .getDefaultSharedPreferences(mAppContext);
1641 prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply();
1645 * Shows the settings dialog.
1648 public void onSettingsSelected() {
1649 Intent intent = new Intent(this, CameraSettingsActivity.class);
1650 startActivity(intent);
1654 * Sets the mCurrentModuleIndex, creates a new module instance for the given
1655 * index an sets it as mCurrentModule.
1657 private void setModuleFromModeIndex(int modeIndex) {
1658 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1659 if (agent == null) {
1662 if (!agent.requestAppForCamera()) {
1663 mCameraController.closeCamera();
1665 mCurrentModeIndex = agent.getModuleId();
1666 mCurrentModule = (CameraModule) agent.createModule(this);
1670 public SettingsManager getSettingsManager() {
1671 return mSettingsManager;
1675 public CameraServices getServices() {
1676 return (CameraServices) getApplication();
1679 public List<String> getSupportedModeNames() {
1680 List<Integer> indices = mModuleManager.getSupportedModeIndexList();
1681 List<String> supported = new ArrayList<String>();
1683 for (Integer modeIndex : indices) {
1684 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
1685 if (name != null && !name.equals("")) {
1686 supported.add(name);
1693 public ButtonManager getButtonManager() {
1694 if (mButtonManager == null) {
1695 mButtonManager = new ButtonManager(this);
1697 return mButtonManager;
1701 * Creates an AlertDialog appropriate for choosing whether to enable
1702 * location on the first run of the app.
1704 public AlertDialog getFirstTimeLocationAlert() {
1705 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1706 builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
1708 public void onCallback(Boolean locationOn) {
1709 mSettingsManager.setLocation(locationOn, mLocationManager);
1712 if (builder != null) {
1713 return builder.create();
1720 * Launches an ACTION_EDIT intent for the given local data item. If
1721 * 'withTinyPlanet' is set, this will show a disambig dialog first to let
1722 * the user start either the tiny planet editor or another photo edior.
1724 * @param data The data item to edit.
1726 public void launchEditor(LocalData data) {
1727 Intent intent = new Intent(Intent.ACTION_EDIT)
1728 .setDataAndType(data.getContentUri(), data.getMimeType())
1729 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1731 launchActivityByIntent(intent);
1732 } catch (ActivityNotFoundException e) {
1733 launchActivityByIntent(Intent.createChooser(intent, null));
1738 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1739 super.onCreateContextMenu(menu, v, menuInfo);
1741 MenuInflater inflater = getMenuInflater();
1742 inflater.inflate(R.menu.filmstrip_context_menu, menu);
1746 public boolean onContextItemSelected(MenuItem item) {
1747 switch (item.getItemId()) {
1748 case R.id.tiny_planet_editor:
1749 mMyFilmstripBottomControlListener.onTinyPlanet();
1751 case R.id.photo_editor:
1752 mMyFilmstripBottomControlListener.onEdit();
1759 * Launch the tiny planet editor.
1761 * @param data The data must be a 360 degree stereographically mapped
1762 * panoramic image. It will not be modified, instead a new item
1763 * with the result will be added to the filmstrip.
1765 public void launchTinyPlanetEditor(LocalData data) {
1766 TinyPlanetFragment fragment = new TinyPlanetFragment();
1767 Bundle bundle = new Bundle();
1768 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1769 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1770 fragment.setArguments(bundle);
1771 fragment.show(getFragmentManager(), "tiny_planet");
1774 private void openModule(CameraModule module) {
1775 module.init(this, isSecureCamera(), isCaptureIntent());
1777 int visibility = mFilmstripVisible ? ModuleController.VISIBILITY_HIDDEN
1778 : ModuleController.VISIBILITY_VISIBLE;
1779 module.onPreviewVisibilityChanged(visibility);
1782 private void closeModule(CameraModule module) {
1784 mCameraAppUI.clearModuleUI();
1787 private void performDeletion() {
1788 if (!mPendingDeletion) {
1791 hideUndoDeletionBar(false);
1792 mDataAdapter.executeDeletion();
1795 public void showUndoDeletionBar() {
1796 if (mPendingDeletion) {
1799 Log.v(TAG, "showing undo bar");
1800 mPendingDeletion = true;
1801 if (mUndoDeletionBar == null) {
1802 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1803 mAboveFilmstripControlLayout, true);
1804 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1805 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1806 button.setOnClickListener(new View.OnClickListener() {
1808 public void onClick(View view) {
1809 mDataAdapter.undoDataRemoval();
1810 hideUndoDeletionBar(true);
1813 // Setting undo bar clickable to avoid touch events going through
1814 // the bar to the buttons (eg. edit button, etc) underneath the bar.
1815 mUndoDeletionBar.setClickable(true);
1816 // When there is user interaction going on with the undo button, we
1817 // do not want to hide the undo bar.
1818 button.setOnTouchListener(new View.OnTouchListener() {
1820 public boolean onTouch(View v, MotionEvent event) {
1821 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1822 mIsUndoingDeletion = true;
1823 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1824 mIsUndoingDeletion = false;
1830 mUndoDeletionBar.setAlpha(0f);
1831 mUndoDeletionBar.setVisibility(View.VISIBLE);
1832 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1835 private void hideUndoDeletionBar(boolean withAnimation) {
1836 Log.v(TAG, "Hiding undo deletion bar");
1837 mPendingDeletion = false;
1838 if (mUndoDeletionBar != null) {
1839 if (withAnimation) {
1840 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1841 .setListener(new Animator.AnimatorListener() {
1843 public void onAnimationStart(Animator animation) {
1848 public void onAnimationEnd(Animator animation) {
1849 mUndoDeletionBar.setVisibility(View.GONE);
1853 public void onAnimationCancel(Animator animation) {
1858 public void onAnimationRepeat(Animator animation) {
1863 mUndoDeletionBar.setVisibility(View.GONE);
1869 public void onOrientationChanged(int orientation) {
1870 // We keep the last known orientation. So if the user first orient
1871 // the camera then point the camera to floor or sky, we still have
1872 // the correct orientation.
1873 if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
1876 mLastRawOrientation = orientation;
1877 if (mCurrentModule != null) {
1878 mCurrentModule.onOrientationChanged(orientation);
1883 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1886 * @param enable {@code true} to enable swipe.
1888 public void setSwipingEnabled(boolean enable) {
1889 // TODO: Bring back the functionality.
1890 if (isCaptureIntent()) {
1891 // lockPreview(true);
1893 // lockPreview(!enable);
1897 // Accessor methods for getting latency times used in performance testing
1898 public long getFirstPreviewTime() {
1899 if (mCurrentModule instanceof PhotoModule) {
1900 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
1901 if (coverHiddenTime != -1) {
1902 return coverHiddenTime - mOnCreateTime;
1908 public long getAutoFocusTime() {
1909 return (mCurrentModule instanceof PhotoModule) ?
1910 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1913 public long getShutterLag() {
1914 return (mCurrentModule instanceof PhotoModule) ?
1915 ((PhotoModule) mCurrentModule).mShutterLag : -1;
1918 public long getShutterToPictureDisplayedTime() {
1919 return (mCurrentModule instanceof PhotoModule) ?
1920 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1923 public long getPictureDisplayedToJpegCallbackTime() {
1924 return (mCurrentModule instanceof PhotoModule) ?
1925 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1928 public long getJpegCallbackFinishTime() {
1929 return (mCurrentModule instanceof PhotoModule) ?
1930 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1933 public long getCaptureStartTime() {
1934 return (mCurrentModule instanceof PhotoModule) ?
1935 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1938 public boolean isRecording() {
1939 return (mCurrentModule instanceof VideoModule) ?
1940 ((VideoModule) mCurrentModule).isRecording() : false;
1943 public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
1944 return mCameraController;
1947 // For debugging purposes only.
1948 public CameraModule getCurrentModule() {
1949 return mCurrentModule;
1953 * Reads the current location recording settings and passes it on to the
1956 public void syncLocationManagerSetting() {
1957 mSettingsManager.syncLocationManager(mLocationManager);
1960 private void keepScreenOnForAWhile() {
1961 if (mKeepScreenOn) {
1964 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1965 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1966 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
1969 private void resetScreenOn() {
1970 mKeepScreenOn = false;
1971 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1972 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1976 * @return {@code true} if the Gallery is launched successfully.
1978 private boolean startGallery() {
1979 if (mGalleryIntent == null) {
1983 UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY, InteractionCause.BUTTON);
1984 Intent startGalleryIntent = new Intent(mGalleryIntent);
1985 int currentDataId = mFilmstripController.getCurrentId();
1986 LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
1987 if (currentLocalData != null) {
1988 GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getContentUri());
1990 launchActivityByIntent(startGalleryIntent);
1991 } catch (ActivityNotFoundException e) {
1992 Log.w(TAG, "Failed to launch gallery activity, closing");
1997 private void setNfcBeamPushUriFromData(LocalData data) {
1998 final Uri uri = data.getContentUri();
1999 if (uri != Uri.EMPTY) {
2000 mNfcPushUris[0] = uri;
2002 mNfcPushUris[0] = null;
2007 * Updates the visibility of the filmstrip bottom controls and action bar.
2009 private void updateUiByData(final int dataId) {
2010 final LocalData currentData = mDataAdapter.getLocalData(dataId);
2011 if (currentData == null) {
2012 Log.w(TAG, "Current data ID not found.");
2013 hideSessionProgress();
2016 updateActionBarMenu(currentData);
2018 /* Bottom controls. */
2019 updateBottomControlsByData(currentData);
2021 if (isSecureCamera()) {
2022 // We cannot show buttons in secure camera since go to other
2023 // activities might create a security hole.
2024 mCameraAppUI.getFilmstripBottomControls().hideControls();
2029 setNfcBeamPushUriFromData(currentData);
2031 if (!mDataAdapter.isMetadataUpdated(dataId)) {
2032 mDataAdapter.updateMetadata(dataId);
2037 * Updates the bottom controls based on the data.
2039 private void updateBottomControlsByData(final LocalData currentData) {
2041 final CameraAppUI.BottomPanel filmstripBottomPanel =
2042 mCameraAppUI.getFilmstripBottomControls();
2043 filmstripBottomPanel.showControls();
2044 filmstripBottomPanel.setEditButtonVisibility(
2045 currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2046 filmstripBottomPanel.setShareButtonVisibility(
2047 currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2048 filmstripBottomPanel.setDeleteButtonVisibility(
2049 currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2053 Uri contentUri = currentData.getContentUri();
2054 CaptureSessionManager sessionManager = getServices()
2055 .getCaptureSessionManager();
2056 int sessionProgress = sessionManager.getSessionProgress(contentUri);
2058 if (sessionProgress < 0) {
2059 hideSessionProgress();
2061 CharSequence progressMessage = sessionManager
2062 .getSessionProgressMessage(contentUri);
2063 showSessionProgress(progressMessage);
2064 updateSessionProgress(sessionProgress);
2069 // We need to add this to a separate DB.
2070 final int viewButtonVisibility;
2071 if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2072 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2073 } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2074 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2076 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2079 filmstripBottomPanel.setTinyPlanetEnabled(
2080 PanoramaMetadataLoader.isPanorama360(currentData));
2081 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2084 private class PeekAnimationHandler extends Handler {
2085 private class DataAndCallback {
2087 com.android.camera.util.Callback<Bitmap> mCallback;
2089 public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2092 mCallback = callback;
2096 public PeekAnimationHandler(Looper looper) {
2101 * Starts the animation decoding job and posts a {@code Runnable} back
2102 * when when the decoding is done.
2104 * @param data The data item to decode the thumbnail for.
2105 * @param callback {@link com.android.camera.util.Callback} after the
2108 public void startDecodingJob(final LocalData data,
2109 final com.android.camera.util.Callback<Bitmap> callback) {
2110 PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2111 new DataAndCallback(data, callback)).sendToTarget();
2115 public void handleMessage(Message msg) {
2116 final LocalData data = ((DataAndCallback) msg.obj).mData;
2117 final com.android.camera.util.Callback<Bitmap> callback =
2118 ((DataAndCallback) msg.obj).mCallback;
2119 if (data == null || callback == null) {
2123 final Bitmap bitmap;
2124 switch (data.getLocalDataType()) {
2125 case LocalData.LOCAL_IMAGE:
2126 case LocalData.LOCAL_IN_PROGRESS_DATA:
2127 FileInputStream stream;
2129 stream = new FileInputStream(data.getPath());
2130 } catch (FileNotFoundException e) {
2131 Log.e(TAG, "File not found:" + data.getPath());
2134 Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2135 data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2136 mAboveFilmstripControlLayout.getMeasuredHeight());
2137 if (data.getRotation() % 180 != 0) {
2142 bitmap = LocalDataUtil
2143 .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2144 (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2145 data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2148 case LocalData.LOCAL_VIDEO:
2149 bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2157 if (bitmap == null) {
2161 mMainHandler.post(new Runnable() {
2164 callback.onCallback(bitmap);
2165 mCameraAppUI.startPeekAnimation(bitmap, true);
2171 private void showDetailsDialog(int dataId) {
2172 final LocalData data = mDataAdapter.getLocalData(dataId);
2176 MediaDetails details = data.getMediaDetails(getAndroidContext());
2177 if (details == null) {
2180 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details);
2181 detailDialog.show();
2186 * Show or hide action bar items depending on current data type.
2188 private void updateActionBarMenu(LocalData data) {
2189 if (mActionBarMenu == null) {
2193 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2194 if (detailsMenuItem == null) {
2198 int type = data.getLocalDataType();
2199 boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO);
2200 detailsMenuItem.setVisible(showDetails);