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