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