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