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