CameraActivity.java revision fa9c38908429b9a5db5d738fae3b2963c4cb177f
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
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
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package com.android.camera;
18
19import android.animation.Animator;
20import android.annotation.TargetApi;
21import android.app.ActionBar;
22import android.app.Activity;
23import android.app.AlertDialog;
24import android.app.Dialog;
25import android.content.ActivityNotFoundException;
26import android.content.BroadcastReceiver;
27import android.content.ContentResolver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.pm.ActivityInfo;
32import android.content.pm.PackageManager;
33import android.content.res.Configuration;
34import android.graphics.Bitmap;
35import android.graphics.BitmapFactory;
36import android.graphics.Matrix;
37import android.graphics.Point;
38import android.graphics.SurfaceTexture;
39import android.graphics.drawable.ColorDrawable;
40import android.graphics.drawable.Drawable;
41import android.net.Uri;
42import android.nfc.NfcAdapter;
43import android.nfc.NfcAdapter.CreateBeamUrisCallback;
44import android.nfc.NfcEvent;
45import android.os.AsyncTask;
46import android.os.Build;
47import android.os.Bundle;
48import android.os.Handler;
49import android.os.HandlerThread;
50import android.os.Looper;
51import android.os.Message;
52import android.provider.MediaStore;
53import android.provider.Settings;
54import android.util.CameraPerformanceTracker;
55import android.view.ContextMenu;
56import android.view.ContextMenu.ContextMenuInfo;
57import android.view.KeyEvent;
58import android.view.Menu;
59import android.view.MenuInflater;
60import android.view.MenuItem;
61import android.view.MotionEvent;
62import android.view.View;
63import android.view.ViewGroup;
64import android.view.Window;
65import android.view.WindowManager;
66import android.widget.FrameLayout;
67import android.widget.ImageView;
68import android.widget.ShareActionProvider;
69
70import com.android.camera.app.AppController;
71import com.android.camera.app.CameraAppUI;
72import com.android.camera.app.CameraController;
73import com.android.camera.app.CameraManager;
74import com.android.camera.app.CameraManagerFactory;
75import com.android.camera.app.CameraProvider;
76import com.android.camera.app.CameraServices;
77import com.android.camera.app.LocationManager;
78import com.android.camera.app.ModuleManagerImpl;
79import com.android.camera.app.OrientationManager;
80import com.android.camera.app.OrientationManagerImpl;
81import com.android.camera.data.CameraDataAdapter;
82import com.android.camera.data.FixedLastDataAdapter;
83import com.android.camera.data.LocalData;
84import com.android.camera.data.LocalDataAdapter;
85import com.android.camera.data.LocalDataUtil;
86import com.android.camera.data.LocalMediaData;
87import com.android.camera.data.LocalMediaObserver;
88import com.android.camera.data.LocalSessionData;
89import com.android.camera.data.MediaDetails;
90import com.android.camera.data.PanoramaMetadataLoader;
91import com.android.camera.data.RgbzMetadataLoader;
92import com.android.camera.data.SimpleViewData;
93import com.android.camera.debug.Log;
94import com.android.camera.filmstrip.FilmstripContentPanel;
95import com.android.camera.filmstrip.FilmstripController;
96import com.android.camera.hardware.HardwareSpec;
97import com.android.camera.hardware.HardwareSpecImpl;
98import com.android.camera.module.ModuleController;
99import com.android.camera.module.ModulesInfo;
100import com.android.camera.session.CaptureSession;
101import com.android.camera.session.CaptureSessionManager;
102import com.android.camera.session.CaptureSessionManager.SessionListener;
103import com.android.camera.settings.CameraSettingsActivity;
104import com.android.camera.settings.SettingsManager;
105import com.android.camera.settings.SettingsManager.SettingsCapabilities;
106import com.android.camera.settings.SettingsUtil;
107import com.android.camera.tinyplanet.TinyPlanetFragment;
108import com.android.camera.ui.AbstractTutorialOverlay;
109import com.android.camera.ui.DetailsDialog;
110import com.android.camera.ui.MainActivityLayout;
111import com.android.camera.ui.ModeListView;
112import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
113import com.android.camera.ui.PreviewStatusListener;
114import com.android.camera.util.ApiHelper;
115import com.android.camera.util.Callback;
116import com.android.camera.util.CameraUtil;
117import com.android.camera.util.FeedbackHelper;
118import com.android.camera.util.GalleryHelper;
119import com.android.camera.util.GcamHelper;
120import com.android.camera.util.IntentHelper;
121import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
122import com.android.camera.util.ReleaseDialogHelper;
123import com.android.camera.util.UsageStatistics;
124import com.android.camera.widget.FilmstripView;
125import com.android.camera.widget.Preloader;
126import com.android.camera2.R;
127import com.google.common.logging.eventprotos;
128import com.google.common.logging.eventprotos.CameraEvent.InteractionCause;
129import com.google.common.logging.eventprotos.NavigationChange;
130
131import java.io.File;
132import java.io.FileInputStream;
133import java.io.FileNotFoundException;
134import java.lang.ref.WeakReference;
135import java.util.ArrayList;
136import java.util.List;
137
138public class CameraActivity extends Activity
139        implements AppController, CameraManager.CameraOpenCallback,
140        ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
141        OrientationManager.OnOrientationChangeListener {
142
143    private static final Log.Tag TAG = new Log.Tag("CameraActivity");
144
145    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
146            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
147    public static final String ACTION_IMAGE_CAPTURE_SECURE =
148            "android.media.action.IMAGE_CAPTURE_SECURE";
149
150    // The intent extra for camera from secure lock screen. True if the gallery
151    // should only show newly captured pictures. sSecureAlbumId does not
152    // increment. This is used when switching between camera, camcorder, and
153    // panorama. If the extra is not set, it is in the normal camera mode.
154    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
155
156    /**
157     * Request code from an activity we started that indicated that we do not
158     * want to reset the view to the preview in onResume.
159     */
160    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
161
162    public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
163
164    private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
165    private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
166    private static final int MAX_PEEK_BITMAP_PIXELS = 1600000; // 1.6 * 4 MBs.
167    /** Load metadata for 10 items ahead of our current. */
168    private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
169
170    /** Should be used wherever a context is needed. */
171    private Context mAppContext;
172
173    /**
174     * Whether onResume should reset the view to the preview.
175     */
176    private boolean mResetToPreviewOnResume = true;
177
178    /**
179     * This data adapter is used by FilmStripView.
180     */
181    private LocalDataAdapter mDataAdapter;
182
183    private SettingsManager mSettingsManager;
184    private ModeListView mModeListView;
185    private boolean mModeListVisible = false;
186    private int mCurrentModeIndex;
187    private CameraModule mCurrentModule;
188    private ModuleManagerImpl mModuleManager;
189    private FrameLayout mAboveFilmstripControlLayout;
190    private FilmstripController mFilmstripController;
191    private boolean mFilmstripVisible;
192    /** Whether the filmstrip fully covers the preview. */
193    private boolean mFilmstripCoversPreview = false;
194    private int mResultCodeForTesting;
195    private Intent mResultDataForTesting;
196    private OnScreenHint mStorageHint;
197    private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
198    private boolean mAutoRotateScreen;
199    private boolean mSecureCamera;
200    private int mLastRawOrientation;
201    private OrientationManagerImpl mOrientationManager;
202    private LocationManager mLocationManager;
203    private ButtonManager mButtonManager;
204    private Handler mMainHandler;
205    private PanoramaViewHelper mPanoramaViewHelper;
206    private ActionBar mActionBar;
207    private ViewGroup mUndoDeletionBar;
208    private boolean mIsUndoingDeletion = false;
209
210    private final Uri[] mNfcPushUris = new Uri[1];
211
212    private LocalMediaObserver mLocalImagesObserver;
213    private LocalMediaObserver mLocalVideosObserver;
214
215    private boolean mPendingDeletion = false;
216
217    private CameraController mCameraController;
218    private boolean mPaused;
219    private CameraAppUI mCameraAppUI;
220
221    private PeekAnimationHandler mPeekAnimationHandler;
222    private HandlerThread mPeekAnimationThread;
223
224    private FeedbackHelper mFeedbackHelper;
225
226    private Intent mGalleryIntent;
227    private long mOnCreateTime;
228
229    private Menu mActionBarMenu;
230    private Preloader<Integer, AsyncTask> mPreloader;
231
232    @Override
233    public CameraAppUI getCameraAppUI() {
234        return mCameraAppUI;
235    }
236
237    // close activity when screen turns off
238    private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
239        @Override
240        public void onReceive(Context context, Intent intent) {
241            finish();
242        }
243    };
244
245    /**
246     * Whether the screen is kept turned on.
247     */
248    private boolean mKeepScreenOn;
249    private int mLastLayoutOrientation;
250    private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
251            new CameraAppUI.BottomPanel.Listener() {
252
253                /**
254                 * If the current photo is a photo sphere, this will launch the
255                 * Photo Sphere panorama viewer.
256                 */
257                @Override
258                public void onExternalViewer() {
259                    if (mPanoramaViewHelper == null) {
260                        return;
261                    }
262                    final LocalData data = getCurrentLocalData();
263                    if (data == null) {
264                        return;
265                    }
266                    final Uri contentUri = data.getUri();
267                    if (contentUri == Uri.EMPTY) {
268                        return;
269                    }
270
271                    if (PanoramaMetadataLoader.isPanoramaAndUseViewer(data)) {
272                        mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
273                    } else if (RgbzMetadataLoader.hasRGBZData(data)) {
274                        mPanoramaViewHelper.showRgbz(contentUri);
275                        if (mSettingsManager.getBoolean(
276                                SettingsManager.SETTING_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
277                            mSettingsManager.setBoolean(
278                                    SettingsManager.SETTING_SHOULD_SHOW_REFOCUS_VIEWER_CLING,
279                                    false);
280                            mCameraAppUI.clearClingForViewer(
281                                    CameraAppUI.BottomPanel.VIEWER_REFOCUS);
282                        }
283                    }
284                }
285
286                @Override
287                public void onEdit() {
288                    LocalData data = getCurrentLocalData();
289                    if (data == null) {
290                        return;
291                    }
292                    launchEditor(data);
293                }
294
295                @Override
296                public void onTinyPlanet() {
297                    LocalData data = getCurrentLocalData();
298                    if (data == null) {
299                        return;
300                    }
301                    launchTinyPlanetEditor(data);
302                }
303
304                @Override
305                public void onDelete() {
306                    final int currentDataId = getCurrentDataId();
307                    UsageStatistics.photoInteraction(
308                            UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
309                            eventprotos.CameraEvent.InteractionType.DELETE,
310                            InteractionCause.BUTTON);
311                    removeData(currentDataId);
312                }
313
314                @Override
315                public void onShare() {
316                    final LocalData data = getCurrentLocalData();
317
318                    // If applicable, show release information before this item
319                    // is shared.
320                    if (PanoramaMetadataLoader.isPanorama(data)
321                            || RgbzMetadataLoader.hasRGBZData(data)) {
322                        ReleaseDialogHelper.showReleaseInfoDialog(CameraActivity.this,
323                                new Callback<Void>() {
324                                    @Override
325                                    public void onCallback(Void result) {
326                                        share(data);
327                                    }
328                                });
329                    } else {
330                        share(data);
331                    }
332                }
333
334                private void share(LocalData data) {
335                    Intent shareIntent = getShareIntentByData(data);
336                    if (shareIntent != null) {
337                        try {
338                            launchActivityByIntent(shareIntent);
339                            mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
340                        } catch (ActivityNotFoundException ex) {
341                            // Nothing.
342                        }
343                    }
344                }
345
346                private int getCurrentDataId() {
347                    return mFilmstripController.getCurrentId();
348                }
349
350                private LocalData getCurrentLocalData() {
351                    return mDataAdapter.getLocalData(getCurrentDataId());
352                }
353
354                /**
355                 * Sets up the share intent and NFC properly according to the
356                 * data.
357                 *
358                 * @param data The data to be shared.
359                 */
360                private Intent getShareIntentByData(final LocalData data) {
361                    Intent intent = null;
362                    final Uri contentUri = data.getUri();
363                    if (PanoramaMetadataLoader.isPanorama360(data) &&
364                            data.getUri() != Uri.EMPTY) {
365                        intent = new Intent(Intent.ACTION_SEND);
366                        intent.setType("application/vnd.google.panorama360+jpg");
367                        intent.putExtra(Intent.EXTRA_STREAM, contentUri);
368                    } else if (data.isDataActionSupported(LocalData.DATA_ACTION_SHARE)) {
369                        final String mimeType = data.getMimeType();
370                        intent = getShareIntentFromType(mimeType);
371                        if (intent != null) {
372                            intent.putExtra(Intent.EXTRA_STREAM, contentUri);
373                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
374                        }
375                        intent = Intent.createChooser(intent, null);
376                    }
377                    return intent;
378                }
379
380                /**
381                 * Get the share intent according to the mimeType
382                 *
383                 * @param mimeType The mimeType of current data.
384                 * @return the video/image's ShareIntent or null if mimeType is
385                 *         invalid.
386                 */
387                private Intent getShareIntentFromType(String mimeType) {
388                    // Lazily create the intent object.
389                    Intent intent = new Intent(Intent.ACTION_SEND);
390                    if (mimeType.startsWith("video/")) {
391                        intent.setType("video/*");
392                    } else {
393                        if (mimeType.startsWith("image/")) {
394                            intent.setType("image/*");
395                        } else {
396                            Log.w(TAG, "unsupported mimeType " + mimeType);
397                        }
398                    }
399                    return intent;
400                }
401
402                @Override
403                public void onProgressErrorClicked() {
404                    LocalData data = getCurrentLocalData();
405                    getServices().getCaptureSessionManager().removeErrorMessage(
406                            data.getUri());
407                    updateBottomControlsByData(data);
408                }
409            };
410
411    private ComboPreferences mPreferences;
412
413    @Override
414    public void onCameraOpened(CameraManager.CameraProxy camera) {
415        /**
416         * The current UI requires that the flash option visibility in front-facing
417         * camera be
418         *   * disabled if back facing camera supports flash
419         *   * hidden if back facing camera does not support flash
420         * We save whether back facing camera supports flash because we cannot get
421         * this in front facing camera without a camera switch.
422         *
423         * If this preference is cleared, we also need to clear the camera facing
424         * setting so we default to opening the camera in back facing camera, and
425         * can save this flash support value again.
426         */
427        if (!mSettingsManager.isSet(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA)) {
428            HardwareSpec hardware = new HardwareSpecImpl(camera.getParameters());
429            mSettingsManager.setBoolean(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA,
430                hardware.isFlashSupported());
431        }
432
433        if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
434            // We shouldn't be here. Just close the camera and leave.
435            camera.release(false);
436            throw new IllegalStateException("Camera opened but the module shouldn't be " +
437                    "requesting");
438        }
439        if (mCurrentModule != null) {
440            SettingsCapabilities capabilities =
441                    SettingsUtil.getSettingsCapabilities(camera);
442            mSettingsManager.changeCamera(camera.getCameraId(), capabilities);
443            mCurrentModule.onCameraAvailable(camera);
444        }
445        mCameraAppUI.onChangeCamera();
446    }
447
448    @Override
449    public void onCameraDisabled(int cameraId) {
450        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.SECURITY);
451
452        CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
453    }
454
455    @Override
456    public void onDeviceOpenFailure(int cameraId) {
457        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.OPEN_FAILURE);
458
459        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
460    }
461
462    @Override
463    public void onDeviceOpenedAlready(int cameraId) {
464        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
465    }
466
467    @Override
468    public void onReconnectionFailure(CameraManager mgr) {
469        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE);
470
471        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
472    }
473
474    private static class MainHandler extends Handler {
475        final WeakReference<CameraActivity> mActivity;
476
477        public MainHandler(CameraActivity activity, Looper looper) {
478            super(looper);
479            mActivity = new WeakReference<CameraActivity>(activity);
480        }
481
482        @Override
483        public void handleMessage(Message msg) {
484            CameraActivity activity = mActivity.get();
485            if (activity == null) {
486                return;
487            }
488            switch (msg.what) {
489
490                case MSG_CLEAR_SCREEN_ON_FLAG: {
491                    if (!activity.mPaused) {
492                        activity.getWindow().clearFlags(
493                                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
494                    }
495                    break;
496                }
497            }
498        }
499    }
500
501    private String fileNameFromDataID(int dataID) {
502        final LocalData localData = mDataAdapter.getLocalData(dataID);
503
504        File localFile = new File(localData.getPath());
505        return localFile.getName();
506    }
507
508    private final FilmstripContentPanel.Listener mFilmstripListener =
509            new FilmstripContentPanel.Listener() {
510
511                @Override
512                public void onSwipeOut() {
513                    UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
514                            eventprotos.CameraEvent.InteractionCause.SWIPE_RIGHT);
515                }
516
517                @Override
518                public void onSwipeOutBegin() {
519                    mActionBar.hide();
520                    mFilmstripCoversPreview = false;
521                    updatePreviewVisibility();
522                }
523
524                @Override
525                public void onFilmstripHidden() {
526                    mFilmstripVisible = false;
527                    CameraActivity.this.setFilmstripUiVisibility(false);
528                    // When the user hide the filmstrip (either swipe out or
529                    // tap on back key) we move to the first item so next time
530                    // when the user swipe in the filmstrip, the most recent
531                    // one is shown.
532                    mFilmstripController.goToFirstItem();
533                }
534
535                @Override
536                public void onFilmstripShown() {
537                    mFilmstripVisible = true;
538                    updateUiByData(mFilmstripController.getCurrentId());
539                }
540
541                @Override
542                public void onFocusedDataLongPressed(int dataId) {
543                    // Do nothing.
544                }
545
546                @Override
547                public void onFocusedDataPromoted(int dataID) {
548                    UsageStatistics.photoInteraction(
549                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
550                            eventprotos.CameraEvent.InteractionType.DELETE,
551                            InteractionCause.SWIPE_UP);
552
553                    removeData(dataID);
554                }
555
556                @Override
557                public void onFocusedDataDemoted(int dataID) {
558                    UsageStatistics.photoInteraction(
559                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
560                            eventprotos.CameraEvent.InteractionType.DELETE,
561                            InteractionCause.SWIPE_DOWN);
562
563                    removeData(dataID);
564                }
565
566                @Override
567                public void onEnterFullScreenUiShown(int dataId) {
568                    if (mFilmstripVisible) {
569                        CameraActivity.this.setFilmstripUiVisibility(true);
570                    }
571                }
572
573                @Override
574                public void onLeaveFullScreenUiShown(int dataId) {
575                    // Do nothing.
576                }
577
578                @Override
579                public void onEnterFullScreenUiHidden(int dataId) {
580                    if (mFilmstripVisible) {
581                        CameraActivity.this.setFilmstripUiVisibility(false);
582                    }
583                }
584
585                @Override
586                public void onLeaveFullScreenUiHidden(int dataId) {
587                    // Do nothing.
588                }
589
590                @Override
591                public void onEnterFilmstrip(int dataId) {
592                    if (mFilmstripVisible) {
593                        CameraActivity.this.setFilmstripUiVisibility(true);
594                    }
595                }
596
597                @Override
598                public void onLeaveFilmstrip(int dataId) {
599                    // Do nothing.
600                }
601
602                @Override
603                public void onDataReloaded() {
604                    if (!mFilmstripVisible) {
605                        return;
606                    }
607                    updateUiByData(mFilmstripController.getCurrentId());
608                }
609
610                @Override
611                public void onDataUpdated(int dataId) {
612                    if (!mFilmstripVisible) {
613                        return;
614                    }
615                    updateUiByData(mFilmstripController.getCurrentId());
616                }
617
618                @Override
619                public void onEnterZoomView(int dataID) {
620                    if (mFilmstripVisible) {
621                        CameraActivity.this.setFilmstripUiVisibility(false);
622                    }
623                }
624
625                @Override
626                public void onDataFocusChanged(final int prevDataId, final int newDataId) {
627                    if (!mFilmstripVisible) {
628                        return;
629                    }
630                    // TODO: This callback is UI event callback, should always
631                    // happen on UI thread. Find the reason for this
632                    // runOnUiThread() and fix it.
633                    runOnUiThread(new Runnable() {
634                        @Override
635                        public void run() {
636                            updateUiByData(newDataId);
637                        }
638                    });
639                }
640
641                @Override
642                public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
643                    mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
644                }
645            };
646
647    private final LocalDataAdapter.LocalDataListener mLocalDataListener =
648            new LocalDataAdapter.LocalDataListener() {
649                @Override
650                public void onMetadataUpdated(List<Integer> updatedData) {
651                    int currentDataId = mFilmstripController.getCurrentId();
652                    for (Integer dataId : updatedData) {
653                        if (dataId == currentDataId) {
654                            updateBottomControlsByData(mDataAdapter.getLocalData(dataId));
655                        }
656                    }
657                }
658            };
659
660    public void gotoGallery() {
661        UsageStatistics.changeScreen(NavigationChange.Mode.FILMSTRIP,
662                InteractionCause.BUTTON);
663
664        mFilmstripController.goToNextItem();
665    }
666
667    /**
668     * If 'visible' is false, this hides the action bar and switches the
669     * filmstrip UI to lights-out mode.
670     *
671     * @param visible is false, this hides the action bar and switches the
672     *            filmstrip UI to lights-out mode.
673     */
674    private void setFilmstripUiVisibility(boolean visible) {
675        int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
676        int newSystemUIVisibility = (visible ? View.SYSTEM_UI_FLAG_VISIBLE
677                : View.SYSTEM_UI_FLAG_FULLSCREEN);
678        if (newSystemUIVisibility != currentSystemUIVisibility) {
679            mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
680        }
681
682        mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
683        if (visible != mActionBar.isShowing()) {
684            if (visible) {
685                mActionBar.show();
686            } else {
687                mActionBar.hide();
688            }
689        }
690        mFilmstripCoversPreview = visible;
691        updatePreviewVisibility();
692    }
693
694    private void hideSessionProgress() {
695        mCameraAppUI.getFilmstripBottomControls().hideProgress();
696    }
697
698    private void showSessionProgress(CharSequence message) {
699        CameraAppUI.BottomPanel controls =  mCameraAppUI.getFilmstripBottomControls();
700        controls.setProgressText(message);
701        controls.hideControls();
702        controls.hideProgressError();
703        controls.showProgress();
704    }
705
706    private void showProcessError(CharSequence message) {
707        mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
708    }
709
710    private void updateSessionProgress(int progress) {
711        mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
712    }
713
714    private void updateSessionProgressText(CharSequence message) {
715        mCameraAppUI.getFilmstripBottomControls().setProgressText(message);
716    }
717
718    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
719    private void setupNfcBeamPush() {
720        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
721        if (adapter == null) {
722            return;
723        }
724
725        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
726            // Disable beaming
727            adapter.setNdefPushMessage(null, CameraActivity.this);
728            return;
729        }
730
731        adapter.setBeamPushUris(null, CameraActivity.this);
732        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
733            @Override
734            public Uri[] createBeamUris(NfcEvent event) {
735                return mNfcPushUris;
736            }
737        }, CameraActivity.this);
738    }
739
740    @Override
741    public void onMenuVisibilityChanged(boolean isVisible) {
742        // TODO: Remove this or bring back the original implementation: cancel
743        // auto-hide actionbar.
744    }
745
746    @Override
747    public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
748        int currentDataId = mFilmstripController.getCurrentId();
749        if (currentDataId < 0) {
750            return false;
751        }
752        UsageStatistics.photoInteraction(
753                UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
754                eventprotos.CameraEvent.InteractionType.SHARE,
755                InteractionCause.BUTTON);
756        // TODO add intent.getComponent().getPackageName()
757        return true;
758    }
759
760    // Note: All callbacks come back on the main thread.
761    private final SessionListener mSessionListener =
762            new SessionListener() {
763                @Override
764                public void onSessionQueued(final Uri uri) {
765                    if (!Storage.isSessionUri(uri)) {
766                        return;
767                    }
768                    LocalSessionData newData = new LocalSessionData(uri);
769                    mDataAdapter.addData(newData);
770                }
771
772                @Override
773                public void onSessionDone(final Uri sessionUri) {
774                    Log.v(TAG, "onSessionDone:" + sessionUri);
775                    Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
776                    if (contentUri == null) {
777                        mDataAdapter.refresh(sessionUri);
778                        return;
779                    }
780                    LocalData newData = LocalMediaData.PhotoData.fromContentUri(
781                            getContentResolver(), contentUri);
782
783                    final int pos = mDataAdapter.findDataByContentUri(sessionUri);
784                    if (pos == -1) {
785                        // We do not have a placeholder for this image, perhaps due to the
786                        // activity crashing or being killed.
787                        mDataAdapter.addData(newData);
788                    }  else  {
789                        mDataAdapter.updateData(pos, newData);
790                    }
791                }
792
793                @Override
794                public void onSessionProgress(final Uri uri, final int progress) {
795                    if (progress < 0) {
796                        // Do nothing, there is no task for this URI.
797                        return;
798                    }
799                    int currentDataId = mFilmstripController.getCurrentId();
800                    if (currentDataId == -1) {
801                        return;
802                    }
803                    if (uri.equals(
804                            mDataAdapter.getLocalData(currentDataId).getUri())) {
805                        updateSessionProgress(progress);
806                    }
807                }
808
809                @Override
810                public void onSessionProgressText(final Uri uri, final CharSequence message) {
811                    int currentDataId = mFilmstripController.getCurrentId();
812                    if (currentDataId == -1) {
813                        return;
814                    }
815                    if (uri.equals(
816                            mDataAdapter.getLocalData(currentDataId).getUri())) {
817                        updateSessionProgressText(message);
818                    }
819                }
820
821                @Override
822                public void onSessionUpdated(Uri uri) {
823                    mDataAdapter.refresh(uri);
824                }
825
826                @Override
827                public void onSessionPreviewAvailable(Uri uri) {
828                    mDataAdapter.refresh(uri);
829                    int dataId = mDataAdapter.findDataByContentUri(uri);
830                    if (dataId != -1) {
831                        startPeekAnimation(mDataAdapter.getLocalData(dataId));
832                    }
833                }
834
835                @Override
836                public void onSessionFailed(Uri uri, CharSequence reason) {
837                    Log.v(TAG, "onSessionFailed:" + uri);
838
839                    int failedDataId = mDataAdapter.findDataByContentUri(uri);
840                    int currentDataId = mFilmstripController.getCurrentId();
841
842                    if (currentDataId == failedDataId) {
843                        updateSessionProgress(0);
844                        showProcessError(reason);
845                    }
846                    // HERE
847                    mDataAdapter.refresh(uri);
848                }
849            };
850
851    @Override
852    public Context getAndroidContext() {
853        return mAppContext;
854    }
855
856    @Override
857    public void launchActivityByIntent(Intent intent) {
858        startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
859    }
860
861    @Override
862    public int getCurrentModuleIndex() {
863        return mCurrentModeIndex;
864    }
865
866    @Override
867    public ModuleController getCurrentModuleController() {
868        return mCurrentModule;
869    }
870
871    @Override
872    public int getQuickSwitchToModuleId(int currentModuleIndex) {
873        return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
874                mAppContext);
875    }
876
877    @Override
878    public SurfaceTexture getPreviewBuffer() {
879        // TODO: implement this
880        return null;
881    }
882
883    @Override
884    public void onPreviewReadyToStart() {
885        mCameraAppUI.onPreviewReadyToStart();
886    }
887
888    @Override
889    public void onPreviewStarted() {
890        mCameraAppUI.onPreviewStarted();
891    }
892
893    @Override
894    public void addPreviewAreaSizeChangedListener(
895            PreviewStatusListener.PreviewAreaChangedListener listener) {
896        mCameraAppUI.addPreviewAreaChangedListener(listener);
897    }
898
899    @Override
900    public void removePreviewAreaSizeChangedListener(
901            PreviewStatusListener.PreviewAreaChangedListener listener) {
902        mCameraAppUI.removePreviewAreaChangedListener(listener);
903    }
904
905    @Override
906    public void setupOneShotPreviewListener() {
907        mCameraController.setOneShotPreviewCallback(mMainHandler,
908                new CameraManager.CameraPreviewDataCallback() {
909                    @Override
910                    public void onPreviewFrame(byte[] data, CameraManager.CameraProxy camera) {
911                        mCurrentModule.onPreviewInitialDataReceived();
912                        mCameraAppUI.onNewPreviewFrame();
913                    }
914                }
915        );
916    }
917
918    @Override
919    public void updatePreviewAspectRatio(float aspectRatio) {
920        mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
921    }
922
923    @Override
924    public void updatePreviewTransform(Matrix matrix) {
925        mCameraAppUI.updatePreviewTransform(matrix);
926    }
927
928    @Override
929    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
930        mCameraAppUI.setPreviewStatusListener(previewStatusListener);
931    }
932
933    @Override
934    public FrameLayout getModuleLayoutRoot() {
935        return mCameraAppUI.getModuleRootView();
936    }
937
938    @Override
939    public void setShutterEventsListener(ShutterEventsListener listener) {
940        // TODO: implement this
941    }
942
943    @Override
944    public void setShutterEnabled(boolean enabled) {
945        // TODO: implement this
946    }
947
948    @Override
949    public boolean isShutterEnabled() {
950        // TODO: implement this
951        return false;
952    }
953
954    @Override
955    public void startPreCaptureAnimation() {
956        mCameraAppUI.startPreCaptureAnimation();
957    }
958
959    @Override
960    public void cancelPreCaptureAnimation() {
961        // TODO: implement this
962    }
963
964    @Override
965    public void startPostCaptureAnimation() {
966        // TODO: implement this
967    }
968
969    @Override
970    public void startPostCaptureAnimation(Bitmap thumbnail) {
971        // TODO: implement this
972    }
973
974    @Override
975    public void cancelPostCaptureAnimation() {
976        // TODO: implement this
977    }
978
979    @Override
980    public OrientationManager getOrientationManager() {
981        return mOrientationManager;
982    }
983
984    @Override
985    public LocationManager getLocationManager() {
986        return mLocationManager;
987    }
988
989    @Override
990    public void lockOrientation() {
991        if (mOrientationManager != null) {
992            mOrientationManager.lockOrientation();
993        }
994    }
995
996    @Override
997    public void unlockOrientation() {
998        if (mOrientationManager != null) {
999            mOrientationManager.unlockOrientation();
1000        }
1001    }
1002
1003    /**
1004     * Starts the filmstrip peek animation if the filmstrip is not visible.
1005     * Only {@link LocalData#LOCAL_IMAGE}, {@link
1006     * LocalData#LOCAL_IN_PROGRESS_DATA} and {@link
1007     * LocalData#LOCAL_VIDEO} are supported.
1008     *
1009     * @param data The data to peek.
1010     */
1011    private void startPeekAnimation(final LocalData data) {
1012        if (mFilmstripVisible || mPeekAnimationHandler == null) {
1013            return;
1014        }
1015
1016        int dataType = data.getLocalDataType();
1017        if (dataType != LocalData.LOCAL_IMAGE && dataType != LocalData.LOCAL_IN_PROGRESS_DATA &&
1018                dataType != LocalData.LOCAL_VIDEO) {
1019            return;
1020        }
1021
1022        mPeekAnimationHandler.startDecodingJob(data, new Callback<Bitmap>() {
1023            @Override
1024            public void onCallback(Bitmap result) {
1025                mCameraAppUI.startPeekAnimation(result, true);
1026            }
1027        });
1028    }
1029
1030    @Override
1031    public void notifyNewMedia(Uri uri) {
1032        ContentResolver cr = getContentResolver();
1033        String mimeType = cr.getType(uri);
1034        if (LocalDataUtil.isMimeTypeVideo(mimeType)) {
1035            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1036            LocalData newData = LocalMediaData.VideoData.fromContentUri(getContentResolver(), uri);
1037            if (newData == null) {
1038                Log.e(TAG, "Can't find video data in content resolver:" + uri);
1039                return;
1040            }
1041            if (mDataAdapter.addData(newData)) {
1042                startPeekAnimation(newData);
1043            }
1044        } else if (LocalDataUtil.isMimeTypeImage(mimeType)) {
1045            CameraUtil.broadcastNewPicture(mAppContext, uri);
1046            LocalData newData = LocalMediaData.PhotoData.fromContentUri(getContentResolver(), uri);
1047            if (newData == null) {
1048                Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1049                return;
1050            }
1051            if (mDataAdapter.addData(newData)) {
1052                startPeekAnimation(newData);
1053            }
1054        } else {
1055            Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1056        }
1057    }
1058
1059    @Override
1060    public void enableKeepScreenOn(boolean enabled) {
1061        if (mPaused) {
1062            return;
1063        }
1064
1065        mKeepScreenOn = enabled;
1066        if (mKeepScreenOn) {
1067            mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1068            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1069        } else {
1070            keepScreenOnForAWhile();
1071        }
1072    }
1073
1074    @Override
1075    public CameraProvider getCameraProvider() {
1076        return mCameraController;
1077    }
1078
1079    private void removeData(int dataID) {
1080        mDataAdapter.removeData(dataID);
1081        if (mDataAdapter.getTotalNumber() > 1) {
1082            showUndoDeletionBar();
1083        } else {
1084            // If camera preview is the only view left in filmstrip,
1085            // no need to show undo bar.
1086            mPendingDeletion = true;
1087            performDeletion();
1088            if (mFilmstripVisible) {
1089                mCameraAppUI.getFilmstripContentPanel().animateHide();
1090            }
1091        }
1092    }
1093
1094    @Override
1095    public boolean onOptionsItemSelected(MenuItem item) {
1096        // Handle presses on the action bar items
1097        switch (item.getItemId()) {
1098            case android.R.id.home:
1099                if (mFilmstripVisible && startGallery()) {
1100                    return true;
1101                }
1102                onBackPressed();
1103                return true;
1104            case R.id.action_details:
1105                showDetailsDialog(mFilmstripController.getCurrentId());
1106                return true;
1107            default:
1108                return super.onOptionsItemSelected(item);
1109        }
1110    }
1111
1112    private boolean isCaptureIntent() {
1113        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1114                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1115                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1116            return true;
1117        } else {
1118            return false;
1119        }
1120    }
1121
1122    private final CameraManager.CameraExceptionCallback mCameraDefaultExceptionCallback
1123        = new CameraManager.CameraExceptionCallback() {
1124                @Override
1125                public void onCameraException(RuntimeException e) {
1126                    Log.d(TAG, "Camera Exception", e);
1127                    CameraUtil.showErrorAndFinish(CameraActivity.this,
1128                            R.string.cannot_connect_camera);
1129                }
1130            };
1131
1132    @Override
1133    public void onCreate(Bundle state) {
1134        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1135
1136        super.onCreate(state);
1137        mOnCreateTime = System.currentTimeMillis();
1138        mAppContext = getApplicationContext();
1139        mSettingsManager = getServices().getSettingsManager();
1140        mSettingsManager.setCurrentModuleProvider(this);
1141        GcamHelper.init(getContentResolver());
1142
1143        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1144        setContentView(R.layout.activity_main);
1145        mActionBar = getActionBar();
1146        mActionBar.addOnMenuVisibilityListener(this);
1147        mMainHandler = new MainHandler(this, getMainLooper());
1148        mCameraController =
1149                new CameraController(mAppContext, this, mMainHandler,
1150                        CameraManagerFactory.getAndroidCameraManager());
1151        mCameraController.setCameraDefaultExceptionCallback(mCameraDefaultExceptionCallback,
1152                mMainHandler);
1153
1154        mPreferences = new ComboPreferences(mAppContext);
1155
1156        // Remove this after we get rid of ComboPreferences.
1157        int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID));
1158        mPreferences.setLocalId(mAppContext, cameraId);
1159        CameraSettings.upgradeGlobalPreferences(mPreferences,
1160                mCameraController.getNumberOfCameras());
1161        // TODO: Try to move all the resources allocation to happen as soon as
1162        // possible so we can call module.init() at the earliest time.
1163        mModuleManager = new ModuleManagerImpl();
1164        ModulesInfo.setupModules(mAppContext, mModuleManager);
1165
1166        mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1167        mModeListView.init(mModuleManager.getSupportedModeIndexList());
1168        if (ApiHelper.HAS_ROTATION_ANIMATION) {
1169            setRotationAnimation();
1170        }
1171        mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1172            @Override
1173            public void onVisibilityChanged(boolean visible) {
1174                mModeListVisible = visible;
1175                updatePreviewVisibility();
1176            }
1177        });
1178
1179        // Check if this is in the secure camera mode.
1180        Intent intent = getIntent();
1181        String action = intent.getAction();
1182        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1183                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1184            mSecureCamera = true;
1185        } else {
1186            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1187        }
1188
1189        if (mSecureCamera) {
1190            // Foreground event caused by lock screen startup.
1191            // It is necessary to log this in onCreate, to avoid the
1192            // onResume->onPause->onResume sequence.
1193            UsageStatistics.foregrounded(
1194                    eventprotos.ForegroundEvent.ForegroundSource.LOCK_SCREEN);
1195
1196            // Change the window flags so that secure camera can show when
1197            // locked
1198            Window win = getWindow();
1199            WindowManager.LayoutParams params = win.getAttributes();
1200            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1201            win.setAttributes(params);
1202
1203            // Filter for screen off so that we can finish secure camera
1204            // activity
1205            // when screen is off.
1206            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1207            registerReceiver(mScreenOffReceiver, filter);
1208        }
1209        mCameraAppUI = new CameraAppUI(this,
1210                (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1211
1212        mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1213
1214        mAboveFilmstripControlLayout =
1215                (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1216
1217        // Add the session listener so we can track the session progress
1218        // updates.
1219        getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1220        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1221        mFilmstripController.setImageGap(
1222                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1223        mPanoramaViewHelper = new PanoramaViewHelper(this);
1224        mPanoramaViewHelper.onCreate();
1225        // Set up the camera preview first so the preview shows up ASAP.
1226        mDataAdapter = new CameraDataAdapter(mAppContext,
1227                new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
1228        mDataAdapter.setLocalDataListener(mLocalDataListener);
1229
1230        mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1231                mDataAdapter);
1232
1233        mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1234        if (mSettingsManager.getBoolean(SettingsManager.SETTING_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1235            mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1236        }
1237
1238        mLocationManager = new LocationManager(mAppContext);
1239
1240        mOrientationManager = new OrientationManagerImpl(this);
1241        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1242
1243        setModuleFromModeIndex(getModeIndex());
1244        mCameraAppUI.prepareModuleUI();
1245        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1246
1247        if (!mSecureCamera) {
1248            mFilmstripController.setDataAdapter(mDataAdapter);
1249            if (!isCaptureIntent()) {
1250                mDataAdapter.requestLoad(new Callback<Void>() {
1251                    @Override
1252                    public void onCallback(Void result) {
1253                        fillTemporarySessions();
1254                    }
1255                });
1256            }
1257        } else {
1258            // Put a lock placeholder as the last image by setting its date to
1259            // 0.
1260            ImageView v = (ImageView) getLayoutInflater().inflate(
1261                    R.layout.secure_album_placeholder, null);
1262            v.setOnClickListener(new View.OnClickListener() {
1263                @Override
1264                public void onClick(View view) {
1265                    UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY,
1266                            InteractionCause.BUTTON);
1267                    startGallery();
1268                    finish();
1269                }
1270            });
1271            v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1272            mDataAdapter = new FixedLastDataAdapter(
1273                    mAppContext,
1274                    mDataAdapter,
1275                    new SimpleViewData(
1276                            v,
1277                            v.getDrawable().getIntrinsicWidth(),
1278                            v.getDrawable().getIntrinsicHeight(),
1279                            0, 0));
1280            // Flush out all the original data.
1281            mDataAdapter.flush();
1282            mFilmstripController.setDataAdapter(mDataAdapter);
1283        }
1284
1285        setupNfcBeamPush();
1286
1287        mLocalImagesObserver = new LocalMediaObserver();
1288        mLocalVideosObserver = new LocalMediaObserver();
1289
1290        getContentResolver().registerContentObserver(
1291                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1292                mLocalImagesObserver);
1293        getContentResolver().registerContentObserver(
1294                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1295                mLocalVideosObserver);
1296        if (FeedbackHelper.feedbackAvailable()) {
1297            mFeedbackHelper = new FeedbackHelper(mAppContext);
1298        }
1299    }
1300
1301    /**
1302     * Get the current mode index from the Intent or from persistent
1303     * settings.
1304     */
1305    public int getModeIndex() {
1306        int modeIndex = -1;
1307        int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1308        int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1309        int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1310        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1311                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1312            modeIndex = videoIndex;
1313        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1314                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())) {
1315            // TODO: synchronize mode options with photo module without losing
1316            // HDR+ preferences.
1317            modeIndex = photoIndex;
1318        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1319                        .getAction())
1320                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1321            modeIndex = mSettingsManager.getInt(
1322                SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX);
1323        } else {
1324            // If the activity has not been started using an explicit intent,
1325            // read the module index from the last time the user changed modes
1326            modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1327            if ((modeIndex == gcamIndex &&
1328                    !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1329                modeIndex = photoIndex;
1330            }
1331        }
1332        return modeIndex;
1333    }
1334
1335    /**
1336     * Call this whenever the mode drawer or filmstrip change the visibility
1337     * state.
1338     */
1339    private void updatePreviewVisibility() {
1340        if (mCurrentModule == null) {
1341            return;
1342        }
1343
1344        int visibility = getPreviewVisibility();
1345        updatePreviewRendering(visibility);
1346        updateCaptureControls(visibility);
1347        mCurrentModule.onPreviewVisibilityChanged(visibility);
1348    }
1349
1350    private void updateCaptureControls(int visibility) {
1351        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1352            mCameraAppUI.setIndicatorBottomBarWrapperVisible(false);
1353        } else {
1354            mCameraAppUI.setIndicatorBottomBarWrapperVisible(true);
1355        }
1356    }
1357
1358    private void updatePreviewRendering(int visibility) {
1359        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1360            mCameraAppUI.pausePreviewRendering();
1361        } else {
1362            mCameraAppUI.resumePreviewRendering();
1363        }
1364    }
1365
1366    private int getPreviewVisibility() {
1367        if (mFilmstripCoversPreview) {
1368            return ModuleController.VISIBILITY_HIDDEN;
1369        } else if (mModeListVisible){
1370            return ModuleController.VISIBILITY_COVERED;
1371        } else {
1372            return ModuleController.VISIBILITY_VISIBLE;
1373        }
1374    }
1375
1376    private void setRotationAnimation() {
1377        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1378        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1379        Window win = getWindow();
1380        WindowManager.LayoutParams winParams = win.getAttributes();
1381        winParams.rotationAnimation = rotationAnimation;
1382        win.setAttributes(winParams);
1383    }
1384
1385    @Override
1386    public void onUserInteraction() {
1387        super.onUserInteraction();
1388        if (!isFinishing()) {
1389            keepScreenOnForAWhile();
1390        }
1391    }
1392
1393    @Override
1394    public boolean dispatchTouchEvent(MotionEvent ev) {
1395        boolean result = super.dispatchTouchEvent(ev);
1396        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1397            // Real deletion is postponed until the next user interaction after
1398            // the gesture that triggers deletion. Until real deletion is
1399            // performed, users can click the undo button to bring back the
1400            // image that they chose to delete.
1401            if (mPendingDeletion && !mIsUndoingDeletion) {
1402                performDeletion();
1403            }
1404        }
1405        return result;
1406    }
1407
1408    @Override
1409    public void onPause() {
1410        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1411
1412        /*
1413         * Save the last module index after all secure camera and icon launches,
1414         * not just on mode switches.
1415         *
1416         * Right now we exclude capture intents from this logic, because we also
1417         * ignore the cross-Activity recovery logic in onStart for capture intents.
1418         */
1419        if (!isCaptureIntent()) {
1420            mSettingsManager.setInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX,
1421                mCurrentModeIndex);
1422        }
1423
1424        mPaused = true;
1425        mPeekAnimationHandler = null;
1426        mPeekAnimationThread.quitSafely();
1427        mPeekAnimationThread = null;
1428
1429        // Delete photos that are pending deletion
1430        performDeletion();
1431        mCurrentModule.pause();
1432        mOrientationManager.pause();
1433        // Close the camera and wait for the operation done.
1434        mCameraController.closeCamera();
1435        mPanoramaViewHelper.onPause();
1436
1437        mLocalImagesObserver.setForegroundChangeListener(null);
1438        mLocalImagesObserver.setActivityPaused(true);
1439        mLocalVideosObserver.setActivityPaused(true);
1440        mPreloader.cancelAllLoads();
1441        resetScreenOn();
1442        super.onPause();
1443    }
1444
1445    @Override
1446    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1447        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1448            mResetToPreviewOnResume = false;
1449        } else {
1450            super.onActivityResult(requestCode, resultCode, data);
1451        }
1452    }
1453
1454    @Override
1455    public void onResume() {
1456        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1457
1458        mPaused = false;
1459
1460        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1461
1462        // TODO: Handle this in OrientationManager.
1463        // Auto-rotate off
1464        if (Settings.System.getInt(getContentResolver(),
1465                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1466            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1467            mAutoRotateScreen = false;
1468        } else {
1469            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1470            mAutoRotateScreen = true;
1471        }
1472
1473        if (isCaptureIntent()) {
1474            // Foreground event caused by photo or video capure intent.
1475            UsageStatistics.foregrounded(
1476                    eventprotos.ForegroundEvent.ForegroundSource.INTENT_PICKER);
1477        } else if (!mSecureCamera) {
1478            // Foreground event that is not caused by an intent.
1479            UsageStatistics.foregrounded(
1480                    eventprotos.ForegroundEvent.ForegroundSource.ICON_LAUNCHER);
1481        }
1482
1483        Drawable galleryLogo;
1484        if (mSecureCamera) {
1485            mGalleryIntent = null;
1486            galleryLogo = null;
1487        } else {
1488            mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext);
1489            galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1490        }
1491        if (galleryLogo == null) {
1492            try {
1493                galleryLogo = getPackageManager().getActivityLogo(getComponentName());
1494            } catch (PackageManager.NameNotFoundException e) {
1495                Log.e(TAG, "Can't get the activity logo");
1496            }
1497        }
1498        if (mGalleryIntent != null) {
1499            mActionBar.setDisplayUseLogoEnabled(true);
1500        }
1501        mActionBar.setLogo(galleryLogo);
1502        mOrientationManager.resume();
1503        super.onResume();
1504        mPeekAnimationThread = new HandlerThread("Peek animation");
1505        mPeekAnimationThread.start();
1506        mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
1507
1508        mCurrentModule.hardResetSettings(mSettingsManager);
1509        mCurrentModule.resume();
1510        setSwipingEnabled(true);
1511
1512        if (!mResetToPreviewOnResume) {
1513            LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1514            if (data != null) {
1515                mDataAdapter.refresh(data.getUri());
1516            }
1517        }
1518        // The share button might be disabled to avoid double tapping.
1519        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1520        // Default is showing the preview, unless disabled by explicitly
1521        // starting an activity we want to return from to the filmstrip rather
1522        // than the preview.
1523        mResetToPreviewOnResume = true;
1524
1525        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1526                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1527            if (!mSecureCamera) {
1528                // If it's secure camera, requestLoad() should not be called
1529                // as it will load all the data.
1530                if (!mFilmstripVisible) {
1531                    mDataAdapter.requestLoad(new Callback<Void>() {
1532                        @Override
1533                        public void onCallback(Void result) {
1534                            fillTemporarySessions();
1535                        }
1536                    });
1537                } else {
1538                    mDataAdapter.requestLoadNewPhotos();
1539                }
1540            }
1541        }
1542        mLocalImagesObserver.setActivityPaused(false);
1543        mLocalVideosObserver.setActivityPaused(false);
1544        if (!mSecureCamera) {
1545            mLocalImagesObserver.setForegroundChangeListener(
1546                    new LocalMediaObserver.ChangeListener() {
1547                @Override
1548                public void onChange() {
1549                    mDataAdapter.requestLoadNewPhotos();
1550                }
1551            });
1552        }
1553
1554        keepScreenOnForAWhile();
1555
1556        mPanoramaViewHelper.onResume();
1557        ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1558        syncLocationManagerSetting();
1559
1560        final int previewVisibility = getPreviewVisibility();
1561        updatePreviewRendering(previewVisibility);
1562    }
1563
1564    private void fillTemporarySessions() {
1565        if (mSecureCamera) {
1566            return;
1567        }
1568        // There might be sessions still in flight (processed by our service).
1569        // Make sure they're added to the filmstrip.
1570        getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
1571    }
1572
1573    @Override
1574    public void onStart() {
1575        super.onStart();
1576        mPanoramaViewHelper.onStart();
1577
1578        /*
1579         * If we're starting after launching a different Activity (lockscreen),
1580         * we need to use the last mode used in the other Activity, and
1581         * not the old one from this Activity.
1582         *
1583         * This needs to happen before CameraAppUI.resume() in order to set the
1584         * mode cover icon to the actual last mode used.
1585         *
1586         * Right now we exclude capture intents from this logic.
1587         */
1588        int modeIndex = getModeIndex();
1589        if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
1590            onModeSelected(modeIndex);
1591        }
1592
1593        if (mResetToPreviewOnResume) {
1594            mCameraAppUI.resume();
1595            mResetToPreviewOnResume = false;
1596        }
1597    }
1598
1599    @Override
1600    protected void onStop() {
1601        mPanoramaViewHelper.onStop();
1602        if (mFeedbackHelper != null) {
1603            mFeedbackHelper.stopFeedback();
1604        }
1605
1606        mLocationManager.disconnect();
1607        super.onStop();
1608    }
1609
1610    @Override
1611    public void onDestroy() {
1612        if (mSecureCamera) {
1613            unregisterReceiver(mScreenOffReceiver);
1614        }
1615        mActionBar.removeOnMenuVisibilityListener(this);
1616        mSettingsManager.removeAllListeners();
1617        mCameraController.removeCallbackReceiver();
1618        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1619        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1620        getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
1621        mCameraAppUI.onDestroy();
1622        mModeListView.setVisibilityChangedListener(null);
1623        mCameraController = null;
1624        mSettingsManager = null;
1625        mCameraAppUI = null;
1626        mOrientationManager = null;
1627        mButtonManager = null;
1628        CameraManagerFactory.recycle();
1629        super.onDestroy();
1630    }
1631
1632    @Override
1633    public void onConfigurationChanged(Configuration config) {
1634        super.onConfigurationChanged(config);
1635        Log.v(TAG, "onConfigurationChanged");
1636        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1637            return;
1638        }
1639
1640        if (mLastLayoutOrientation != config.orientation) {
1641            mLastLayoutOrientation = config.orientation;
1642            mCurrentModule.onLayoutOrientationChanged(
1643                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1644        }
1645    }
1646
1647    @Override
1648    public boolean onKeyDown(int keyCode, KeyEvent event) {
1649        if (!mFilmstripVisible) {
1650            if (mCurrentModule.onKeyDown(keyCode, event)) {
1651                return true;
1652            }
1653            // Prevent software keyboard or voice search from showing up.
1654            if (keyCode == KeyEvent.KEYCODE_SEARCH
1655                    || keyCode == KeyEvent.KEYCODE_MENU) {
1656                if (event.isLongPress()) {
1657                    return true;
1658                }
1659            }
1660        }
1661
1662        return super.onKeyDown(keyCode, event);
1663    }
1664
1665    @Override
1666    public boolean onKeyUp(int keyCode, KeyEvent event) {
1667        if (!mFilmstripVisible) {
1668            // If a module is in the middle of capture, it should
1669            // consume the key event.
1670            if (mCurrentModule.onKeyUp(keyCode, event)) {
1671                return true;
1672            } else if (keyCode == KeyEvent.KEYCODE_MENU
1673                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1674                // Let the mode list view consume the event.
1675                mModeListView.onMenuPressed();
1676                return true;
1677            } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1678                mCameraAppUI.showFilmstrip();
1679                return true;
1680            }
1681        } else {
1682            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1683                mFilmstripController.goToNextItem();
1684                return true;
1685            } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1686                boolean wentToPrevious = mFilmstripController.goToPreviousItem();
1687                if (!wentToPrevious) {
1688                  // at beginning of filmstrip, hide and go back to preview
1689                  mCameraAppUI.hideFilmstrip();
1690                }
1691                return true;
1692            }
1693        }
1694        return super.onKeyUp(keyCode, event);
1695    }
1696
1697    @Override
1698    public void onBackPressed() {
1699        if (!mCameraAppUI.onBackPressed()) {
1700            if (!mCurrentModule.onBackPressed()) {
1701                super.onBackPressed();
1702            }
1703        }
1704    }
1705
1706    @Override
1707    public boolean isAutoRotateScreen() {
1708        // TODO: Move to OrientationManager.
1709        return mAutoRotateScreen;
1710    }
1711
1712    @Override
1713    public boolean onCreateOptionsMenu(Menu menu) {
1714        MenuInflater inflater = getMenuInflater();
1715        inflater.inflate(R.menu.filmstrip_menu, menu);
1716        mActionBarMenu = menu;
1717        return super.onCreateOptionsMenu(menu);
1718    }
1719
1720    protected void updateStorageSpace() {
1721        mStorageSpaceBytes = Storage.getAvailableSpace();
1722    }
1723
1724    protected long getStorageSpaceBytes() {
1725        return mStorageSpaceBytes;
1726    }
1727
1728    protected void updateStorageSpaceAndHint() {
1729        updateStorageSpace();
1730        updateStorageHint(mStorageSpaceBytes);
1731    }
1732
1733    protected void updateStorageHint(long storageSpace) {
1734        String message = null;
1735        if (storageSpace == Storage.UNAVAILABLE) {
1736            message = getString(R.string.no_storage);
1737        } else if (storageSpace == Storage.PREPARING) {
1738            message = getString(R.string.preparing_sd);
1739        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1740            message = getString(R.string.access_sd_fail);
1741        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1742            message = getString(R.string.spaceIsLow_content);
1743        }
1744
1745        if (message != null) {
1746            if (mStorageHint == null) {
1747                mStorageHint = OnScreenHint.makeText(mAppContext, message);
1748            } else {
1749                mStorageHint.setText(message);
1750            }
1751            mStorageHint.show();
1752        } else if (mStorageHint != null) {
1753            mStorageHint.cancel();
1754            mStorageHint = null;
1755        }
1756    }
1757
1758    protected void setResultEx(int resultCode) {
1759        mResultCodeForTesting = resultCode;
1760        setResult(resultCode);
1761    }
1762
1763    protected void setResultEx(int resultCode, Intent data) {
1764        mResultCodeForTesting = resultCode;
1765        mResultDataForTesting = data;
1766        setResult(resultCode, data);
1767    }
1768
1769    public int getResultCode() {
1770        return mResultCodeForTesting;
1771    }
1772
1773    public Intent getResultData() {
1774        return mResultDataForTesting;
1775    }
1776
1777    public boolean isSecureCamera() {
1778        return mSecureCamera;
1779    }
1780
1781    @Override
1782    public boolean isPaused() {
1783        return mPaused;
1784    }
1785
1786    @Override
1787    public int getPreferredChildModeIndex(int modeIndex) {
1788        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
1789            boolean hdrPlusOn = mSettingsManager.isHdrPlusOn();
1790            if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1791                modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1792            }
1793        }
1794        return modeIndex;
1795    }
1796
1797    @Override
1798    public void onModeSelected(int modeIndex) {
1799        if (mCurrentModeIndex == modeIndex) {
1800            return;
1801        }
1802
1803        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
1804        // Record last used camera mode for quick switching
1805        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
1806                || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
1807            mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX,
1808                    modeIndex);
1809        }
1810
1811        closeModule(mCurrentModule);
1812
1813        // Select the correct module index from the mode switcher index.
1814        modeIndex = getPreferredChildModeIndex(modeIndex);
1815        setModuleFromModeIndex(modeIndex);
1816
1817        mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
1818        mCameraAppUI.addShutterListener(mCurrentModule);
1819        openModule(mCurrentModule);
1820        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1821        // Store the module index so we can use it the next time the Camera
1822        // starts up.
1823        mSettingsManager.setInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX, modeIndex);
1824    }
1825
1826    /**
1827     * Shows the settings dialog.
1828     */
1829    @Override
1830    public void onSettingsSelected() {
1831        Intent intent = new Intent(this, CameraSettingsActivity.class);
1832        startActivity(intent);
1833    }
1834
1835    /**
1836     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1837     * index an sets it as mCurrentModule.
1838     */
1839    private void setModuleFromModeIndex(int modeIndex) {
1840        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1841        if (agent == null) {
1842            return;
1843        }
1844        if (!agent.requestAppForCamera()) {
1845            mCameraController.closeCamera();
1846        }
1847        mCurrentModeIndex = agent.getModuleId();
1848        mCurrentModule = (CameraModule) agent.createModule(this);
1849    }
1850
1851    @Override
1852    public SettingsManager getSettingsManager() {
1853        return mSettingsManager;
1854    }
1855
1856    @Override
1857    public CameraServices getServices() {
1858        return (CameraServices) getApplication();
1859    }
1860
1861    public List<String> getSupportedModeNames() {
1862        List<Integer> indices = mModuleManager.getSupportedModeIndexList();
1863        List<String> supported = new ArrayList<String>();
1864
1865        for (Integer modeIndex : indices) {
1866            String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
1867            if (name != null && !name.equals("")) {
1868                supported.add(name);
1869            }
1870        }
1871        return supported;
1872    }
1873
1874    @Override
1875    public ButtonManager getButtonManager() {
1876        if (mButtonManager == null) {
1877            mButtonManager = new ButtonManager(this);
1878        }
1879        return mButtonManager;
1880    }
1881
1882    /**
1883     * Creates an AlertDialog appropriate for choosing whether to enable
1884     * location on the first run of the app.
1885     */
1886    public AlertDialog getFirstTimeLocationAlert() {
1887        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1888        builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
1889            @Override
1890            public void onCallback(Boolean locationOn) {
1891                mSettingsManager.setLocation(locationOn, mLocationManager);
1892            }
1893        });
1894        if (builder != null) {
1895            return builder.create();
1896        } else {
1897            return null;
1898        }
1899    }
1900
1901    /**
1902     * Launches an ACTION_EDIT intent for the given local data item. If
1903     * 'withTinyPlanet' is set, this will show a disambig dialog first to let
1904     * the user start either the tiny planet editor or another photo edior.
1905     *
1906     * @param data The data item to edit.
1907     */
1908    public void launchEditor(LocalData data) {
1909        Intent intent = new Intent(Intent.ACTION_EDIT)
1910                .setDataAndType(data.getUri(), data.getMimeType())
1911                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1912        try {
1913            launchActivityByIntent(intent);
1914        } catch (ActivityNotFoundException e) {
1915            launchActivityByIntent(Intent.createChooser(intent, null));
1916        }
1917    }
1918
1919    @Override
1920    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1921        super.onCreateContextMenu(menu, v, menuInfo);
1922
1923        MenuInflater inflater = getMenuInflater();
1924        inflater.inflate(R.menu.filmstrip_context_menu, menu);
1925    }
1926
1927    @Override
1928    public boolean onContextItemSelected(MenuItem item) {
1929        switch (item.getItemId()) {
1930            case R.id.tiny_planet_editor:
1931                mMyFilmstripBottomControlListener.onTinyPlanet();
1932                return true;
1933            case R.id.photo_editor:
1934                mMyFilmstripBottomControlListener.onEdit();
1935                return true;
1936        }
1937        return false;
1938    }
1939
1940    /**
1941     * Launch the tiny planet editor.
1942     *
1943     * @param data The data must be a 360 degree stereographically mapped
1944     *            panoramic image. It will not be modified, instead a new item
1945     *            with the result will be added to the filmstrip.
1946     */
1947    public void launchTinyPlanetEditor(LocalData data) {
1948        TinyPlanetFragment fragment = new TinyPlanetFragment();
1949        Bundle bundle = new Bundle();
1950        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString());
1951        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1952        fragment.setArguments(bundle);
1953        fragment.show(getFragmentManager(), "tiny_planet");
1954    }
1955
1956    private void openModule(CameraModule module) {
1957        module.init(this, isSecureCamera(), isCaptureIntent());
1958        module.hardResetSettings(mSettingsManager);
1959        module.resume();
1960        updatePreviewVisibility();
1961    }
1962
1963    private void closeModule(CameraModule module) {
1964        module.pause();
1965        mCameraAppUI.clearModuleUI();
1966    }
1967
1968    private void performDeletion() {
1969        if (!mPendingDeletion) {
1970            return;
1971        }
1972        hideUndoDeletionBar(false);
1973        mDataAdapter.executeDeletion();
1974    }
1975
1976    public void showUndoDeletionBar() {
1977        if (mPendingDeletion) {
1978            performDeletion();
1979        }
1980        Log.v(TAG, "showing undo bar");
1981        mPendingDeletion = true;
1982        if (mUndoDeletionBar == null) {
1983            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1984                    mAboveFilmstripControlLayout, true);
1985            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1986            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1987            button.setOnClickListener(new View.OnClickListener() {
1988                @Override
1989                public void onClick(View view) {
1990                    mDataAdapter.undoDataRemoval();
1991                    hideUndoDeletionBar(true);
1992                }
1993            });
1994            // Setting undo bar clickable to avoid touch events going through
1995            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1996            mUndoDeletionBar.setClickable(true);
1997            // When there is user interaction going on with the undo button, we
1998            // do not want to hide the undo bar.
1999            button.setOnTouchListener(new View.OnTouchListener() {
2000                @Override
2001                public boolean onTouch(View v, MotionEvent event) {
2002                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2003                        mIsUndoingDeletion = true;
2004                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2005                        mIsUndoingDeletion = false;
2006                    }
2007                    return false;
2008                }
2009            });
2010        }
2011        mUndoDeletionBar.setAlpha(0f);
2012        mUndoDeletionBar.setVisibility(View.VISIBLE);
2013        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2014    }
2015
2016    private void hideUndoDeletionBar(boolean withAnimation) {
2017        Log.v(TAG, "Hiding undo deletion bar");
2018        mPendingDeletion = false;
2019        if (mUndoDeletionBar != null) {
2020            if (withAnimation) {
2021                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2022                        .setListener(new Animator.AnimatorListener() {
2023                            @Override
2024                            public void onAnimationStart(Animator animation) {
2025                                // Do nothing.
2026                            }
2027
2028                            @Override
2029                            public void onAnimationEnd(Animator animation) {
2030                                mUndoDeletionBar.setVisibility(View.GONE);
2031                            }
2032
2033                            @Override
2034                            public void onAnimationCancel(Animator animation) {
2035                                // Do nothing.
2036                            }
2037
2038                            @Override
2039                            public void onAnimationRepeat(Animator animation) {
2040                                // Do nothing.
2041                            }
2042                        }).start();
2043            } else {
2044                mUndoDeletionBar.setVisibility(View.GONE);
2045            }
2046        }
2047    }
2048
2049    @Override
2050    public void onOrientationChanged(int orientation) {
2051        // We keep the last known orientation. So if the user first orient
2052        // the camera then point the camera to floor or sky, we still have
2053        // the correct orientation.
2054        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
2055            return;
2056        }
2057        mLastRawOrientation = orientation;
2058        if (mCurrentModule != null) {
2059            mCurrentModule.onOrientationChanged(orientation);
2060        }
2061    }
2062
2063    /**
2064     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2065     * capture intent.
2066     *
2067     * @param enable {@code true} to enable swipe.
2068     */
2069    public void setSwipingEnabled(boolean enable) {
2070        // TODO: Bring back the functionality.
2071        if (isCaptureIntent()) {
2072            // lockPreview(true);
2073        } else {
2074            // lockPreview(!enable);
2075        }
2076    }
2077
2078    // Accessor methods for getting latency times used in performance testing
2079    public long getFirstPreviewTime() {
2080        if (mCurrentModule instanceof PhotoModule) {
2081            long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2082            if (coverHiddenTime != -1) {
2083                return coverHiddenTime - mOnCreateTime;
2084            }
2085        }
2086        return -1;
2087    }
2088
2089    public long getAutoFocusTime() {
2090        return (mCurrentModule instanceof PhotoModule) ?
2091                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2092    }
2093
2094    public long getShutterLag() {
2095        return (mCurrentModule instanceof PhotoModule) ?
2096                ((PhotoModule) mCurrentModule).mShutterLag : -1;
2097    }
2098
2099    public long getShutterToPictureDisplayedTime() {
2100        return (mCurrentModule instanceof PhotoModule) ?
2101                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2102    }
2103
2104    public long getPictureDisplayedToJpegCallbackTime() {
2105        return (mCurrentModule instanceof PhotoModule) ?
2106                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2107    }
2108
2109    public long getJpegCallbackFinishTime() {
2110        return (mCurrentModule instanceof PhotoModule) ?
2111                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2112    }
2113
2114    public long getCaptureStartTime() {
2115        return (mCurrentModule instanceof PhotoModule) ?
2116                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2117    }
2118
2119    public boolean isRecording() {
2120        return (mCurrentModule instanceof VideoModule) ?
2121                ((VideoModule) mCurrentModule).isRecording() : false;
2122    }
2123
2124    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
2125        return mCameraController;
2126    }
2127
2128    // For debugging purposes only.
2129    public CameraModule getCurrentModule() {
2130        return mCurrentModule;
2131    }
2132
2133    @Override
2134    public void showTutorial(AbstractTutorialOverlay tutorial) {
2135        mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2136    }
2137
2138    /**
2139     * Reads the current location recording settings and passes it on to the
2140     * location manager.
2141     */
2142    public void syncLocationManagerSetting() {
2143        mSettingsManager.syncLocationManager(mLocationManager);
2144    }
2145
2146    private void keepScreenOnForAWhile() {
2147        if (mKeepScreenOn) {
2148            return;
2149        }
2150        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2151        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2152        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2153    }
2154
2155    private void resetScreenOn() {
2156        mKeepScreenOn = false;
2157        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2158        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2159    }
2160
2161    /**
2162     * @return {@code true} if the Gallery is launched successfully.
2163     */
2164    private boolean startGallery() {
2165        if (mGalleryIntent == null) {
2166            return false;
2167        }
2168        try {
2169            UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY, InteractionCause.BUTTON);
2170            Intent startGalleryIntent = new Intent(mGalleryIntent);
2171            int currentDataId = mFilmstripController.getCurrentId();
2172            LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
2173            if (currentLocalData != null) {
2174                GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri());
2175            }
2176            launchActivityByIntent(startGalleryIntent);
2177        } catch (ActivityNotFoundException e) {
2178            Log.w(TAG, "Failed to launch gallery activity, closing");
2179        }
2180        return false;
2181    }
2182
2183    private void setNfcBeamPushUriFromData(LocalData data) {
2184        final Uri uri = data.getUri();
2185        if (uri != Uri.EMPTY) {
2186            mNfcPushUris[0] = uri;
2187        } else {
2188            mNfcPushUris[0] = null;
2189        }
2190    }
2191
2192    /**
2193     * Updates the visibility of the filmstrip bottom controls and action bar.
2194     */
2195    private void updateUiByData(final int dataId) {
2196        final LocalData currentData = mDataAdapter.getLocalData(dataId);
2197        if (currentData == null) {
2198            Log.w(TAG, "Current data ID not found.");
2199            hideSessionProgress();
2200            return;
2201        }
2202        updateActionBarMenu(currentData);
2203
2204        /* Bottom controls. */
2205        updateBottomControlsByData(currentData);
2206
2207        if (isSecureCamera()) {
2208            // We cannot show buttons in secure camera since go to other
2209            // activities might create a security hole.
2210            mCameraAppUI.getFilmstripBottomControls().hideControls();
2211            return;
2212        }
2213
2214
2215        setNfcBeamPushUriFromData(currentData);
2216
2217        if (!mDataAdapter.isMetadataUpdated(dataId)) {
2218            mDataAdapter.updateMetadata(dataId);
2219        }
2220    }
2221
2222    /**
2223     * Updates the bottom controls based on the data.
2224     */
2225    private void updateBottomControlsByData(final LocalData currentData) {
2226
2227        final CameraAppUI.BottomPanel filmstripBottomPanel =
2228                mCameraAppUI.getFilmstripBottomControls();
2229        filmstripBottomPanel.showControls();
2230        filmstripBottomPanel.setEditButtonVisibility(
2231                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2232        filmstripBottomPanel.setShareButtonVisibility(
2233                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2234        filmstripBottomPanel.setDeleteButtonVisibility(
2235                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2236
2237        /* Progress bar */
2238
2239        Uri contentUri = currentData.getUri();
2240        CaptureSessionManager sessionManager = getServices()
2241                .getCaptureSessionManager();
2242
2243        if (sessionManager.hasErrorMessage(contentUri)) {
2244            showProcessError(sessionManager.getErrorMesage(contentUri));
2245        } else {
2246            filmstripBottomPanel.hideProgressError();
2247            CaptureSession session = sessionManager.getSession(contentUri);
2248
2249            if (session != null) {
2250                int sessionProgress = session.getProgress();
2251
2252                if (sessionProgress < 0) {
2253                    hideSessionProgress();
2254                } else {
2255                    CharSequence progressMessage = session.getProgressMessage();
2256                    showSessionProgress(progressMessage);
2257                    updateSessionProgress(sessionProgress);
2258                }
2259            } else {
2260                hideSessionProgress();
2261            }
2262        }
2263
2264        /* View button */
2265
2266        // We need to add this to a separate DB.
2267        final int viewButtonVisibility;
2268        if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2269            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2270        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2271            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2272        } else {
2273            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2274        }
2275
2276        filmstripBottomPanel.setTinyPlanetEnabled(
2277                PanoramaMetadataLoader.isPanorama360(currentData));
2278        filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2279    }
2280
2281    private class PeekAnimationHandler extends Handler {
2282        private class DataAndCallback {
2283            LocalData mData;
2284            com.android.camera.util.Callback<Bitmap> mCallback;
2285
2286            public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2287                    callback) {
2288                mData = data;
2289                mCallback = callback;
2290            }
2291        }
2292
2293        public PeekAnimationHandler(Looper looper) {
2294            super(looper);
2295        }
2296
2297        /**
2298         * Starts the animation decoding job and posts a {@code Runnable} back
2299         * when when the decoding is done.
2300         *
2301         * @param data The data item to decode the thumbnail for.
2302         * @param callback {@link com.android.camera.util.Callback} after the
2303         *                 decoding is done.
2304         */
2305        public void startDecodingJob(final LocalData data,
2306                final com.android.camera.util.Callback<Bitmap> callback) {
2307            PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2308                    new DataAndCallback(data, callback)).sendToTarget();
2309        }
2310
2311        @Override
2312        public void handleMessage(Message msg) {
2313            final LocalData data = ((DataAndCallback) msg.obj).mData;
2314            final com.android.camera.util.Callback<Bitmap> callback =
2315                    ((DataAndCallback) msg.obj).mCallback;
2316            if (data == null || callback == null) {
2317                return;
2318            }
2319
2320            final Bitmap bitmap;
2321            switch (data.getLocalDataType()) {
2322                case LocalData.LOCAL_IN_PROGRESS_DATA:
2323                    byte[] jpegData = Storage.getJpegForSession(data.getUri());
2324                    if (jpegData != null) {
2325                        bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
2326                    } else {
2327                        bitmap = null;
2328                    }
2329                    break;
2330
2331                case LocalData.LOCAL_IMAGE:
2332                    FileInputStream stream;
2333                    try {
2334                        stream = new FileInputStream(data.getPath());
2335                    } catch (FileNotFoundException e) {
2336                        Log.e(TAG, "File not found:" + data.getPath());
2337                        return;
2338                    }
2339                    Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2340                            data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2341                            mAboveFilmstripControlLayout.getMeasuredHeight());
2342                    if (data.getRotation() % 180 != 0) {
2343                        int dummy = dim.x;
2344                        dim.x = dim.y;
2345                        dim.y = dummy;
2346                    }
2347                    bitmap = LocalDataUtil
2348                            .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2349                                    (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2350                                    data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2351                    break;
2352
2353                case LocalData.LOCAL_VIDEO:
2354                    bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2355                    break;
2356
2357                default:
2358                    bitmap = null;
2359                    break;
2360            }
2361
2362            if (bitmap == null) {
2363                return;
2364            }
2365
2366            mMainHandler.post(new Runnable() {
2367                @Override
2368                public void run() {
2369                    callback.onCallback(bitmap);
2370                }
2371            });
2372        }
2373    }
2374
2375    private void showDetailsDialog(int dataId) {
2376        final LocalData data = mDataAdapter.getLocalData(dataId);
2377        if (data == null) {
2378            return;
2379        }
2380        MediaDetails details = data.getMediaDetails(getAndroidContext());
2381        if (details == null) {
2382            return;
2383        }
2384        Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details);
2385        detailDialog.show();
2386
2387        UsageStatistics.photoInteraction(
2388                UsageStatistics.hashFileName(fileNameFromDataID(dataId)),
2389                eventprotos.CameraEvent.InteractionType.DETAILS,
2390                InteractionCause.BUTTON);
2391    }
2392
2393    /**
2394     * Show or hide action bar items depending on current data type.
2395     */
2396    private void updateActionBarMenu(LocalData data) {
2397        if (mActionBarMenu == null) {
2398            return;
2399        }
2400
2401        MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2402        if (detailsMenuItem == null) {
2403            return;
2404        }
2405
2406        int type = data.getLocalDataType();
2407        boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO);
2408        detailsMenuItem.setVisible(showDetails);
2409    }
2410}
2411