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