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