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