CameraActivity.java revision 62753ae94be2ae7c08bf24ebcf1c462975af230f
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.ModeListView.ModeListVisibilityChangedListener;
111import com.android.camera.ui.PreviewStatusListener;
112import com.android.camera.util.ApiHelper;
113import com.android.camera.util.Callback;
114import com.android.camera.util.CameraUtil;
115import com.android.camera.util.FeedbackHelper;
116import com.android.camera.util.GalleryHelper;
117import com.android.camera.util.GcamHelper;
118import com.android.camera.util.IntentHelper;
119import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
120import com.android.camera.util.ReleaseDialogHelper;
121import com.android.camera.util.UsageStatistics;
122import com.android.camera.widget.FilmstripView;
123import com.android.camera2.R;
124import com.google.common.logging.eventprotos;
125import com.google.common.logging.eventprotos.CameraEvent.InteractionCause;
126import com.google.common.logging.eventprotos.NavigationChange;
127
128import java.io.File;
129import java.io.FileInputStream;
130import java.io.FileNotFoundException;
131import java.lang.ref.WeakReference;
132import java.util.ArrayList;
133import java.util.List;
134
135public class CameraActivity extends Activity
136        implements AppController, CameraManager.CameraOpenCallback,
137        ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
138        OrientationManager.OnOrientationChangeListener {
139
140    private static final String TAG = "CameraActivity";
141
142    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
143            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
144    public static final String ACTION_IMAGE_CAPTURE_SECURE =
145            "android.media.action.IMAGE_CAPTURE_SECURE";
146
147    // The intent extra for camera from secure lock screen. True if the gallery
148    // should only show newly captured pictures. sSecureAlbumId does not
149    // increment. This is used when switching between camera, camcorder, and
150    // panorama. If the extra is not set, it is in the normal camera mode.
151    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
152
153    /**
154     * Request code from an activity we started that indicated that we do not
155     * want to reset the view to the preview in onResume.
156     */
157    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
158
159    public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
160
161    private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
162    private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
163    private static final int MAX_PEEK_BITMAP_PIXELS = 1600000; // 1.6 * 4 MBs.
164
165    /** Should be used wherever a context is needed. */
166    private Context mAppContext;
167
168    /**
169     * Whether onResume should reset the view to the preview.
170     */
171    private boolean mResetToPreviewOnResume = true;
172
173    /**
174     * This data adapter is used by FilmStripView.
175     */
176    private LocalDataAdapter mDataAdapter;
177
178    /**
179     * TODO: This should be moved to the app level.
180     */
181    private SettingsManager mSettingsManager;
182
183    private ModeListView mModeListView;
184    private int mCurrentModeIndex;
185    private CameraModule mCurrentModule;
186    private ModuleManagerImpl mModuleManager;
187    private FrameLayout mAboveFilmstripControlLayout;
188    private FilmstripController mFilmstripController;
189    private boolean mFilmstripVisible;
190    private TextView mBottomProgressText;
191    private ProgressBar mBottomProgressBar;
192    private View mSessionProgressPanel;
193    private int mResultCodeForTesting;
194    private Intent mResultDataForTesting;
195    private OnScreenHint mStorageHint;
196    private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
197    private boolean mAutoRotateScreen;
198    private boolean mSecureCamera;
199    private int mLastRawOrientation;
200    private OrientationManagerImpl mOrientationManager;
201    private LocationManager mLocationManager;
202    private ButtonManager mButtonManager;
203    private Handler mMainHandler;
204    private PanoramaViewHelper mPanoramaViewHelper;
205    private ActionBar mActionBar;
206    private ViewGroup mUndoDeletionBar;
207    private boolean mIsUndoingDeletion = false;
208
209    private final Uri[] mNfcPushUris = new Uri[1];
210
211    private LocalMediaObserver mLocalImagesObserver;
212    private LocalMediaObserver mLocalVideosObserver;
213
214    private boolean mPendingDeletion = false;
215
216    private CameraController mCameraController;
217    private boolean mPaused;
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.isPanoramaAndUseViewer(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
306                    // If applicable, show release information before this item
307                    // is shared.
308                    if (PanoramaMetadataLoader.isPanorama(data)
309                            || RgbzMetadataLoader.hasRGBZData(data)) {
310                        ReleaseDialogHelper.showReleaseInfoDialog(CameraActivity.this,
311                                new Callback<Void>() {
312                                    @Override
313                                    public void onCallback(Void result) {
314                                        share(data);
315                                    }
316                                });
317                    } else {
318                        share(data);
319                    }
320                }
321
322                private void share(LocalData data) {
323                    Intent shareIntent = getShareIntentByData(data);
324                    if (shareIntent != null) {
325                        try {
326                            launchActivityByIntent(shareIntent);
327                            mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
328                        } catch (ActivityNotFoundException ex) {
329                            // Nothing.
330                        }
331                    }
332                }
333
334                private int getCurrentDataId() {
335                    return mFilmstripController.getCurrentId();
336                }
337
338                private LocalData getCurrentLocalData() {
339                    return mDataAdapter.getLocalData(getCurrentDataId());
340                }
341
342                /**
343                 * Sets up the share intent and NFC properly according to the
344                 * data.
345                 *
346                 * @param data The data to be shared.
347                 */
348                private Intent getShareIntentByData(final LocalData data) {
349                    Intent intent = null;
350                    final Uri contentUri = data.getContentUri();
351                    if (PanoramaMetadataLoader.isPanorama360(data) &&
352                            data.getContentUri() != Uri.EMPTY) {
353                        intent = new Intent(Intent.ACTION_SEND);
354                        intent.setType("application/vnd.google.panorama360+jpg");
355                        intent.putExtra(Intent.EXTRA_STREAM, contentUri);
356                    } else if (data.isDataActionSupported(LocalData.DATA_ACTION_SHARE)) {
357                        final String mimeType = data.getMimeType();
358                        intent = getShareIntentFromType(mimeType);
359                        if (intent != null) {
360                            intent.putExtra(Intent.EXTRA_STREAM, contentUri);
361                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
362                        }
363                    }
364                    return intent;
365                }
366
367                /**
368                 * Get the share intent according to the mimeType
369                 *
370                 * @param mimeType The mimeType of current data.
371                 * @return the video/image's ShareIntent or null if mimeType is
372                 *         invalid.
373                 */
374                private Intent getShareIntentFromType(String mimeType) {
375                    // Lazily create the intent object.
376                    Intent intent = new Intent(Intent.ACTION_SEND);
377                    if (mimeType.startsWith("video/")) {
378                        intent.setType("video/*");
379                    } else {
380                        if (mimeType.startsWith("image/")) {
381                            intent.setType("image/*");
382                        } else {
383                            Log.w(TAG, "unsupported mimeType " + mimeType);
384                        }
385                    }
386                    return intent;
387                }
388            };
389
390    private ComboPreferences mPreferences;
391    private ContentResolver mContentResolver;
392
393    @Override
394    public void onCameraOpened(CameraManager.CameraProxy camera) {
395        /**
396         * The current UI requires that the flash option visibility in front-facing
397         * camera be
398         *   * disabled if back facing camera supports flash
399         *   * hidden if back facing camera does not support flash
400         * We save whether back facing camera supports flash because we cannot get
401         * this in front facing camera without a camera switch.
402         *
403         * If this preference is cleared, we also need to clear the camera facing
404         * setting so we default to opening the camera in back facing camera, and
405         * can save this flash support value again.
406         */
407        if (!mSettingsManager.isSet(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA)) {
408            HardwareSpec hardware = new HardwareSpecImpl(camera.getParameters());
409            mSettingsManager.setBoolean(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA,
410                hardware.isFlashSupported());
411        }
412
413        if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
414            // We shouldn't be here. Just close the camera and leave.
415            camera.release(false);
416            throw new IllegalStateException("Camera opened but the module shouldn't be " +
417                    "requesting");
418        }
419        if (mCurrentModule != null) {
420            SettingsCapabilities capabilities =
421                    SettingsUtil.getSettingsCapabilities(camera);
422            mSettingsManager.changeCamera(camera.getCameraId(), capabilities);
423            mCurrentModule.onCameraAvailable(camera);
424        }
425        mCameraAppUI.onChangeCamera();
426    }
427
428    @Override
429    public void onCameraDisabled(int cameraId) {
430        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.SECURITY);
431
432        CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
433    }
434
435    @Override
436    public void onDeviceOpenFailure(int cameraId) {
437        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.OPEN_FAILURE);
438
439        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
440    }
441
442    @Override
443    public void onDeviceOpenedAlready(int cameraId) {
444        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
445    }
446
447    @Override
448    public void onReconnectionFailure(CameraManager mgr) {
449        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE);
450
451        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
452    }
453
454    private static class MainHandler extends Handler {
455        final WeakReference<CameraActivity> mActivity;
456
457        public MainHandler(CameraActivity activity, Looper looper) {
458            super(looper);
459            mActivity = new WeakReference<CameraActivity>(activity);
460        }
461
462        @Override
463        public void handleMessage(Message msg) {
464            CameraActivity activity = mActivity.get();
465            if (activity == null) {
466                return;
467            }
468            switch (msg.what) {
469
470                case MSG_CLEAR_SCREEN_ON_FLAG: {
471                    if (!activity.mPaused) {
472                        activity.getWindow().clearFlags(
473                                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
474                    }
475                    break;
476                }
477            }
478        }
479    }
480
481    private String fileNameFromDataID(int dataID) {
482        final LocalData localData = mDataAdapter.getLocalData(dataID);
483
484        File localFile = new File(localData.getPath());
485        return localFile.getName();
486    }
487
488    private final FilmstripContentPanel.Listener mFilmstripListener =
489            new FilmstripContentPanel.Listener() {
490
491                @Override
492                public void onSwipeOut() {
493                    UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
494                            eventprotos.CameraEvent.InteractionCause.SWIPE_RIGHT);
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        mSessionProgressPanel.setVisibility(View.GONE);
685    }
686
687    private void showSessionProgress(CharSequence message) {
688        mBottomProgressText.setText(message);
689        mSessionProgressPanel.setVisibility(View.VISIBLE);
690    }
691
692    private void updateSessionProgress(int progress) {
693        mBottomProgressBar.setProgress(progress);
694    }
695
696    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
697    private void setupNfcBeamPush() {
698        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
699        if (adapter == null) {
700            return;
701        }
702
703        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
704            // Disable beaming
705            adapter.setNdefPushMessage(null, CameraActivity.this);
706            return;
707        }
708
709        adapter.setBeamPushUris(null, CameraActivity.this);
710        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
711            @Override
712            public Uri[] createBeamUris(NfcEvent event) {
713                return mNfcPushUris;
714            }
715        }, CameraActivity.this);
716    }
717
718    @Override
719    public void onMenuVisibilityChanged(boolean isVisible) {
720        // TODO: Remove this or bring back the original implementation: cancel
721        // auto-hide actionbar.
722    }
723
724    @Override
725    public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
726        int currentDataId = mFilmstripController.getCurrentId();
727        if (currentDataId < 0) {
728            return false;
729        }
730        UsageStatistics.photoInteraction(
731                UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
732                eventprotos.CameraEvent.InteractionType.SHARE,
733                InteractionCause.BUTTON);
734        // TODO add intent.getComponent().getPackageName()
735        return true;
736    }
737
738    // Note: All callbacks come back on the main thread.
739    private final SessionListener mSessionListener =
740            new SessionListener() {
741                @Override
742                public void onSessionQueued(final Uri uri) {
743                    notifyNewMedia(uri);
744                    int dataID = mDataAdapter.findDataByContentUri(uri);
745                    if (dataID != -1) {
746                        // Don't allow special UI actions (swipe to
747                        // delete, for example) on in-progress data.
748                        LocalData d = mDataAdapter.getLocalData(dataID);
749                        InProgressDataWrapper newData = new InProgressDataWrapper(d);
750                        mDataAdapter.updateData(dataID, newData);
751                    }
752                }
753
754                @Override
755                public void onSessionDone(final Uri uri) {
756                    Log.v(TAG, "onSessionDone:" + uri);
757                    int doneID = mDataAdapter.findDataByContentUri(uri);
758                    int currentDataId = mFilmstripController.getCurrentId();
759
760                    if (currentDataId == doneID) {
761                        hideSessionProgress();
762                        updateSessionProgress(0);
763                    }
764                    mDataAdapter.refresh(uri, /* isInProgress */false);
765                }
766
767                @Override
768                public void onSessionProgress(final Uri uri, final int progress) {
769                    if (progress < 0) {
770                        // Do nothing, there is no task for this URI.
771                        return;
772                    }
773                    int currentDataId = mFilmstripController.getCurrentId();
774                    if (currentDataId == -1) {
775                        return;
776                    }
777                    if (uri.equals(
778                            mDataAdapter.getLocalData(currentDataId).getContentUri())) {
779                        updateSessionProgress(progress);
780                    }
781                }
782
783                @Override
784                public void onSessionUpdated(Uri uri) {
785                    mDataAdapter.refresh(uri, /* isInProgress */true);
786                }
787            };
788
789    @Override
790    public Context getAndroidContext() {
791        return mAppContext;
792    }
793
794    @Override
795    public void launchActivityByIntent(Intent intent) {
796        startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
797    }
798
799    @Override
800    public int getCurrentModuleIndex() {
801        return mCurrentModeIndex;
802    }
803
804    @Override
805    public ModuleController getCurrentModuleController() {
806        return mCurrentModule;
807    }
808
809    @Override
810    public int getQuickSwitchToModuleId(int currentModuleIndex) {
811        return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
812                mAppContext);
813    }
814
815    @Override
816    public SurfaceTexture getPreviewBuffer() {
817        // TODO: implement this
818        return null;
819    }
820
821    @Override
822    public void onPreviewReadyToStart() {
823        mCameraAppUI.onPreviewReadyToStart();
824    }
825
826    @Override
827    public void onPreviewStarted() {
828        mCameraAppUI.onPreviewStarted();
829    }
830
831    @Override
832    public void addPreviewAreaSizeChangedListener(
833            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
834        mCameraAppUI.addPreviewAreaSizeChangedListener(listener);
835    }
836
837    @Override
838    public void removePreviewAreaSizeChangedListener(
839            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
840        mCameraAppUI.removePreviewAreaSizeChangedListener(listener);
841    }
842
843    @Override
844    public void setupOneShotPreviewListener() {
845        mCameraController.setOneShotPreviewCallback(mMainHandler,
846                new CameraManager.CameraPreviewDataCallback() {
847                    @Override
848                    public void onPreviewFrame(byte[] data, CameraManager.CameraProxy camera) {
849                        mCurrentModule.onPreviewInitialDataReceived();
850                        mCameraAppUI.onNewPreviewFrame();
851                    }
852                });
853    }
854
855    @Override
856    public void updatePreviewAspectRatio(float aspectRatio) {
857        mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
858    }
859
860    @Override
861    public boolean shouldShowShimmy() {
862        int remainingTimes = mSettingsManager.getInt(
863                SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX);
864        return remainingTimes > 0;
865    }
866
867    @Override
868    public void decrementShimmyPlayTimes() {
869        int remainingTimes = mSettingsManager.getInt(
870                SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX) - 1;
871        if (remainingTimes >= 0) {
872            mSettingsManager.setInt(SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX,
873                    remainingTimes);
874        }
875    }
876
877    @Override
878    public void updatePreviewTransform(Matrix matrix) {
879        mCameraAppUI.updatePreviewTransform(matrix);
880    }
881
882    @Override
883    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
884        mCameraAppUI.setPreviewStatusListener(previewStatusListener);
885    }
886
887    @Override
888    public FrameLayout getModuleLayoutRoot() {
889        return mCameraAppUI.getModuleRootView();
890    }
891
892    @Override
893    public void setShutterEventsListener(ShutterEventsListener listener) {
894        // TODO: implement this
895    }
896
897    @Override
898    public void setShutterEnabled(boolean enabled) {
899        // TODO: implement this
900    }
901
902    @Override
903    public boolean isShutterEnabled() {
904        // TODO: implement this
905        return false;
906    }
907
908    @Override
909    public void startPreCaptureAnimation() {
910        mCameraAppUI.startPreCaptureAnimation();
911    }
912
913    @Override
914    public void cancelPreCaptureAnimation() {
915        // TODO: implement this
916    }
917
918    @Override
919    public void startPostCaptureAnimation() {
920        // TODO: implement this
921    }
922
923    @Override
924    public void startPostCaptureAnimation(Bitmap thumbnail) {
925        // TODO: implement this
926    }
927
928    @Override
929    public void cancelPostCaptureAnimation() {
930        // TODO: implement this
931    }
932
933    @Override
934    public OrientationManager getOrientationManager() {
935        return mOrientationManager;
936    }
937
938    @Override
939    public LocationManager getLocationManager() {
940        return mLocationManager;
941    }
942
943    @Override
944    public void lockOrientation() {
945        mOrientationManager.lockOrientation();
946    }
947
948    @Override
949    public void unlockOrientation() {
950        mOrientationManager.unlockOrientation();
951    }
952
953    /**
954     * Starts the filmstrip peek animation if the filmstrip is not visible.
955     * Only {@link LocalData#LOCAL_IMAGE}, {@link
956     * LocalData#LOCAL_IN_PROGRESS_DATA} and {@link
957     * LocalData#LOCAL_VIDEO} are supported.
958     *
959     * @param data The data to peek.
960     */
961    private void startPeekAnimation(final LocalData data) {
962        if (mFilmstripVisible || mPeekAnimationHandler == null) {
963            return;
964        }
965
966        int dataType = data.getLocalDataType();
967        if (dataType != LocalData.LOCAL_IMAGE && dataType != LocalData.LOCAL_IN_PROGRESS_DATA &&
968                dataType != LocalData.LOCAL_VIDEO) {
969            return;
970        }
971
972        mPeekAnimationHandler.startDecodingJob(data, new Callback<Bitmap>() {
973            @Override
974            public void onCallback(Bitmap result) {
975                mCameraAppUI.startPeekAnimation(result, true);
976            }
977        });
978    }
979
980    @Override
981    public void notifyNewMedia(Uri uri) {
982        ContentResolver cr = getContentResolver();
983        String mimeType = cr.getType(uri);
984        if (LocalDataUtil.isMimeTypeVideo(mimeType)) {
985            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
986            mDataAdapter.addNewVideo(uri);
987        } else if (LocalDataUtil.isMimeTypeImage(mimeType)) {
988            CameraUtil.broadcastNewPicture(mAppContext, uri);
989            mDataAdapter.addNewPhoto(uri);
990        } else if (LocalDataUtil.isMimeTypePlaceHolder(mimeType)) {
991            mDataAdapter.addNewPhoto(uri);
992        } else {
993            android.util.Log.w(TAG, "Unknown new media with MIME type:"
994                    + mimeType + ", uri:" + uri);
995        }
996    }
997
998    @Override
999    public void enableKeepScreenOn(boolean enabled) {
1000        if (mPaused) {
1001            return;
1002        }
1003
1004        mKeepScreenOn = enabled;
1005        if (mKeepScreenOn) {
1006            mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1007            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1008        } else {
1009            keepScreenOnForAWhile();
1010        }
1011    }
1012
1013    @Override
1014    public CameraProvider getCameraProvider() {
1015        return mCameraController;
1016    }
1017
1018    private void removeData(int dataID) {
1019        mDataAdapter.removeData(dataID);
1020        if (mDataAdapter.getTotalNumber() > 1) {
1021            showUndoDeletionBar();
1022        } else {
1023            // If camera preview is the only view left in filmstrip,
1024            // no need to show undo bar.
1025            mPendingDeletion = true;
1026            performDeletion();
1027            if (mFilmstripVisible) {
1028                mCameraAppUI.getFilmstripContentPanel().animateHide();
1029            }
1030        }
1031    }
1032
1033    @Override
1034    public boolean onOptionsItemSelected(MenuItem item) {
1035        // Handle presses on the action bar items
1036        switch (item.getItemId()) {
1037            case android.R.id.home:
1038                if (mFilmstripVisible && startGallery()) {
1039                    return true;
1040                }
1041                onBackPressed();
1042                return true;
1043            default:
1044                return super.onOptionsItemSelected(item);
1045        }
1046    }
1047
1048    private boolean isCaptureIntent() {
1049        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1050                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1051                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1052            return true;
1053        } else {
1054            return false;
1055        }
1056    }
1057
1058    private SettingsManager.StrictUpgradeCallback mStrictUpgradeCallback
1059        = new SettingsManager.StrictUpgradeCallback() {
1060                @Override
1061                public void upgrade(SettingsManager settingsManager, int version) {
1062                    // Show the location dialog on upgrade if
1063                    //  (a) the user has never set this option (status quo).
1064                    //  (b) the user opt'ed out previously.
1065                    if (settingsManager.isSet(SettingsManager.SETTING_RECORD_LOCATION) &&
1066                            !settingsManager.getBoolean(SettingsManager.SETTING_RECORD_LOCATION)) {
1067                        settingsManager.remove(SettingsManager.SETTING_RECORD_LOCATION);
1068                    }
1069                }
1070            };
1071
1072    @Override
1073    public void onCreate(Bundle state) {
1074        super.onCreate(state);
1075        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1076        mOnCreateTime = System.currentTimeMillis();
1077        mAppContext = getApplicationContext();
1078        GcamHelper.init(getContentResolver());
1079
1080        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1081        setContentView(R.layout.activity_main);
1082        mActionBar = getActionBar();
1083        mActionBar.addOnMenuVisibilityListener(this);
1084        mMainHandler = new MainHandler(this, getMainLooper());
1085        mCameraController =
1086                new CameraController(mAppContext, this, mMainHandler,
1087                        CameraManagerFactory.getAndroidCameraManager());
1088        mPreferences = new ComboPreferences(mAppContext);
1089        mContentResolver = this.getContentResolver();
1090
1091        mSettingsManager = new SettingsManager(mAppContext, this,
1092                mCameraController.getNumberOfCameras(), mStrictUpgradeCallback);
1093
1094        // Remove this after we get rid of ComboPreferences.
1095        int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID));
1096        mPreferences.setLocalId(mAppContext, cameraId);
1097        CameraSettings.upgradeGlobalPreferences(mPreferences,
1098                mCameraController.getNumberOfCameras());
1099        // TODO: Try to move all the resources allocation to happen as soon as
1100        // possible so we can call module.init() at the earliest time.
1101        mModuleManager = new ModuleManagerImpl();
1102        ModulesInfo.setupModules(mAppContext, mModuleManager);
1103
1104        mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1105        mModeListView.init(mModuleManager.getSupportedModeIndexList());
1106        if (ApiHelper.HAS_ROTATION_ANIMATION) {
1107            setRotationAnimation();
1108        }
1109        mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1110            @Override
1111            public void onVisibilityChanged(boolean visible) {
1112                if (mCurrentModule != null) {
1113                    int visibility = visible ? ModuleController.VISIBILITY_COVERED
1114                            : ModuleController.VISIBILITY_VISIBLE;
1115                    mCurrentModule.onPreviewVisibilityChanged(visibility);
1116                }
1117            }
1118        });
1119
1120        // Check if this is in the secure camera mode.
1121        Intent intent = getIntent();
1122        String action = intent.getAction();
1123        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1124                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1125            mSecureCamera = true;
1126        } else {
1127            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1128        }
1129
1130        if (mSecureCamera) {
1131            // Foreground event caused by lock screen startup.
1132            // It is necessary to log this in onCreate, to avoid the
1133            // onResume->onPause->onResume sequence.
1134            UsageStatistics.foregrounded(
1135                    eventprotos.ForegroundEvent.ForegroundSource.LOCK_SCREEN);
1136
1137            // Change the window flags so that secure camera can show when
1138            // locked
1139            Window win = getWindow();
1140            WindowManager.LayoutParams params = win.getAttributes();
1141            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1142            win.setAttributes(params);
1143
1144            // Filter for screen off so that we can finish secure camera
1145            // activity
1146            // when screen is off.
1147            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1148            registerReceiver(mScreenOffReceiver, filter);
1149        }
1150        mCameraAppUI = new CameraAppUI(this,
1151                (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1152
1153        mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1154
1155        mAboveFilmstripControlLayout =
1156                (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1157
1158        // Add the session listener so we can track the session progress
1159        // updates.
1160        getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1161        mSessionProgressPanel = findViewById(R.id.pano_session_progress_panel);
1162        mBottomProgressBar = (ProgressBar) findViewById(R.id.pano_session_progress_bar);
1163        mBottomProgressText = (TextView) findViewById(R.id.pano_session_progress_text);
1164        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1165        mFilmstripController.setImageGap(
1166                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1167        mPanoramaViewHelper = new PanoramaViewHelper(this);
1168        mPanoramaViewHelper.onCreate();
1169        // Set up the camera preview first so the preview shows up ASAP.
1170        mDataAdapter = new CameraDataAdapter(mAppContext,
1171                new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
1172        mDataAdapter.setLocalDataListener(mLocalDataListener);
1173
1174        mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1175
1176        mLocationManager = new LocationManager(mAppContext);
1177
1178        int modeIndex = -1;
1179        int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1180        int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1181        int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1182        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1183                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1184            modeIndex = videoIndex;
1185        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1186                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1187                        .getAction())) {
1188            modeIndex = photoIndex;
1189            if (mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX)
1190                        == gcamIndex && GcamHelper.hasGcamCapture()) {
1191                modeIndex = gcamIndex;
1192            }
1193        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1194                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1195            modeIndex = photoIndex;
1196        } else {
1197            // If the activity has not been started using an explicit intent,
1198            // read the module index from the last time the user changed modes
1199            modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1200            if ((modeIndex == gcamIndex &&
1201                    !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1202                modeIndex = photoIndex;
1203            }
1204        }
1205
1206        mOrientationManager = new OrientationManagerImpl(this);
1207        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1208
1209        setModuleFromModeIndex(modeIndex);
1210        mCameraAppUI.prepareModuleUI();
1211        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1212
1213        if (!mSecureCamera) {
1214            mFilmstripController.setDataAdapter(mDataAdapter);
1215            if (!isCaptureIntent()) {
1216                mDataAdapter.requestLoad();
1217            }
1218        } else {
1219            // Put a lock placeholder as the last image by setting its date to
1220            // 0.
1221            ImageView v = (ImageView) getLayoutInflater().inflate(
1222                    R.layout.secure_album_placeholder, null);
1223            v.setOnClickListener(new View.OnClickListener() {
1224                @Override
1225                public void onClick(View view) {
1226                    UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY,
1227                            InteractionCause.BUTTON);
1228                    startGallery();
1229                    finish();
1230                }
1231            });
1232            mDataAdapter = new FixedLastDataAdapter(
1233                    mAppContext,
1234                    mDataAdapter,
1235                    new SimpleViewData(
1236                            v,
1237                            v.getDrawable().getIntrinsicWidth(),
1238                            v.getDrawable().getIntrinsicHeight(),
1239                            0, 0));
1240            // Flush out all the original data.
1241            mDataAdapter.flush();
1242            mFilmstripController.setDataAdapter(mDataAdapter);
1243        }
1244
1245        setupNfcBeamPush();
1246
1247        mLocalImagesObserver = new LocalMediaObserver();
1248        mLocalVideosObserver = new LocalMediaObserver();
1249
1250        getContentResolver().registerContentObserver(
1251                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1252                mLocalImagesObserver);
1253        getContentResolver().registerContentObserver(
1254                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1255                mLocalVideosObserver);
1256        if (FeedbackHelper.feedbackAvailable()) {
1257            mFeedbackHelper = new FeedbackHelper(mAppContext);
1258        }
1259    }
1260
1261    private void setRotationAnimation() {
1262        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1263        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1264        Window win = getWindow();
1265        WindowManager.LayoutParams winParams = win.getAttributes();
1266        winParams.rotationAnimation = rotationAnimation;
1267        win.setAttributes(winParams);
1268    }
1269
1270    @Override
1271    public void onUserInteraction() {
1272        super.onUserInteraction();
1273        if (!isFinishing()) {
1274            keepScreenOnForAWhile();
1275        }
1276    }
1277
1278    @Override
1279    public boolean dispatchTouchEvent(MotionEvent ev) {
1280        boolean result = super.dispatchTouchEvent(ev);
1281        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1282            // Real deletion is postponed until the next user interaction after
1283            // the gesture that triggers deletion. Until real deletion is
1284            // performed, users can click the undo button to bring back the
1285            // image that they chose to delete.
1286            if (mPendingDeletion && !mIsUndoingDeletion) {
1287                performDeletion();
1288            }
1289        }
1290        return result;
1291    }
1292
1293    @Override
1294    public void onPause() {
1295        mPaused = true;
1296        mPeekAnimationHandler = null;
1297        mPeekAnimationThread.quitSafely();
1298        mPeekAnimationThread = null;
1299        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1300
1301        // Delete photos that are pending deletion
1302        performDeletion();
1303        mCurrentModule.pause();
1304        mOrientationManager.pause();
1305        // Close the camera and wait for the operation done.
1306        mCameraController.closeCamera();
1307        mPanoramaViewHelper.onPause();
1308
1309        mLocalImagesObserver.setActivityPaused(true);
1310        mLocalVideosObserver.setActivityPaused(true);
1311        resetScreenOn();
1312        super.onPause();
1313    }
1314
1315    @Override
1316    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1317        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1318            mResetToPreviewOnResume = false;
1319        } else {
1320            super.onActivityResult(requestCode, resultCode, data);
1321        }
1322    }
1323
1324    @Override
1325    public void onResume() {
1326        mPaused = false;
1327        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1328
1329        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1330
1331        // TODO: Handle this in OrientationManager.
1332        // Auto-rotate off
1333        if (Settings.System.getInt(getContentResolver(),
1334                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1335            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1336            mAutoRotateScreen = false;
1337        } else {
1338            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1339            mAutoRotateScreen = true;
1340        }
1341
1342        if (isCaptureIntent()) {
1343            // Foreground event caused by photo or video capure intent.
1344            UsageStatistics.foregrounded(
1345                    eventprotos.ForegroundEvent.ForegroundSource.INTENT_PICKER);
1346        } else if (!mSecureCamera) {
1347            // Foreground event that is not caused by an intent.
1348            UsageStatistics.foregrounded(
1349                    eventprotos.ForegroundEvent.ForegroundSource.ICON_LAUNCHER);
1350        }
1351
1352        Drawable galleryLogo;
1353        if (mSecureCamera) {
1354            mGalleryIntent = null;
1355            galleryLogo = null;
1356        } else {
1357            mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext);
1358            galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1359        }
1360        if (galleryLogo == null) {
1361            try {
1362                galleryLogo = getPackageManager().getActivityLogo(getComponentName());
1363            } catch (PackageManager.NameNotFoundException e) {
1364                Log.e(TAG, "Can't get the activity logo");
1365            }
1366        }
1367        if (mGalleryIntent != null) {
1368            mActionBar.setDisplayUseLogoEnabled(true);
1369        }
1370        mActionBar.setLogo(galleryLogo);
1371        mOrientationManager.resume();
1372        super.onResume();
1373        mPeekAnimationThread = new HandlerThread("Peek animation");
1374        mPeekAnimationThread.start();
1375        mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
1376        mCurrentModule.resume();
1377        setSwipingEnabled(true);
1378
1379        if (mResetToPreviewOnResume) {
1380            mCameraAppUI.resume();
1381        } else {
1382            LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1383            if (data != null) {
1384                mDataAdapter.refresh(data.getContentUri(), false);
1385            }
1386        }
1387        // The share button might be disabled to avoid double tapping.
1388        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1389        // Default is showing the preview, unless disabled by explicitly
1390        // starting an activity we want to return from to the filmstrip rather
1391        // than the preview.
1392        mResetToPreviewOnResume = true;
1393
1394        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1395                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1396            if (!mSecureCamera) {
1397                // If it's secure camera, requestLoad() should not be called
1398                // as it will load all the data.
1399                if (!mFilmstripVisible) {
1400                    mDataAdapter.requestLoad();
1401                }
1402            }
1403        }
1404        mLocalImagesObserver.setActivityPaused(false);
1405        mLocalVideosObserver.setActivityPaused(false);
1406
1407        keepScreenOnForAWhile();
1408
1409        // Lights-out mode at all times.
1410        findViewById(R.id.activity_root_view)
1411                .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
1412        mPanoramaViewHelper.onResume();
1413        ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1414    }
1415
1416    @Override
1417    public void onStart() {
1418        super.onStart();
1419        mPanoramaViewHelper.onStart();
1420        boolean recordLocation = RecordLocationPreference.get(
1421                mPreferences, mContentResolver);
1422        mLocationManager.recordLocation(recordLocation);
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        mCameraAppUI.clearModuleUI();
1608
1609        mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
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    }
1760
1761    private void performDeletion() {
1762        if (!mPendingDeletion) {
1763            return;
1764        }
1765        hideUndoDeletionBar(false);
1766        mDataAdapter.executeDeletion();
1767    }
1768
1769    public void showUndoDeletionBar() {
1770        if (mPendingDeletion) {
1771            performDeletion();
1772        }
1773        Log.v(TAG, "showing undo bar");
1774        mPendingDeletion = true;
1775        if (mUndoDeletionBar == null) {
1776            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1777                    mAboveFilmstripControlLayout, true);
1778            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1779            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1780            button.setOnClickListener(new View.OnClickListener() {
1781                @Override
1782                public void onClick(View view) {
1783                    mDataAdapter.undoDataRemoval();
1784                    hideUndoDeletionBar(true);
1785                }
1786            });
1787            // Setting undo bar clickable to avoid touch events going through
1788            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1789            mUndoDeletionBar.setClickable(true);
1790            // When there is user interaction going on with the undo button, we
1791            // do not want to hide the undo bar.
1792            button.setOnTouchListener(new View.OnTouchListener() {
1793                @Override
1794                public boolean onTouch(View v, MotionEvent event) {
1795                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1796                        mIsUndoingDeletion = true;
1797                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1798                        mIsUndoingDeletion = false;
1799                    }
1800                    return false;
1801                }
1802            });
1803        }
1804        mUndoDeletionBar.setAlpha(0f);
1805        mUndoDeletionBar.setVisibility(View.VISIBLE);
1806        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1807    }
1808
1809    private void hideUndoDeletionBar(boolean withAnimation) {
1810        Log.v(TAG, "Hiding undo deletion bar");
1811        mPendingDeletion = false;
1812        if (mUndoDeletionBar != null) {
1813            if (withAnimation) {
1814                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1815                        .setListener(new Animator.AnimatorListener() {
1816                            @Override
1817                            public void onAnimationStart(Animator animation) {
1818                                // Do nothing.
1819                            }
1820
1821                            @Override
1822                            public void onAnimationEnd(Animator animation) {
1823                                mUndoDeletionBar.setVisibility(View.GONE);
1824                            }
1825
1826                            @Override
1827                            public void onAnimationCancel(Animator animation) {
1828                                // Do nothing.
1829                            }
1830
1831                            @Override
1832                            public void onAnimationRepeat(Animator animation) {
1833                                // Do nothing.
1834                            }
1835                        }).start();
1836            } else {
1837                mUndoDeletionBar.setVisibility(View.GONE);
1838            }
1839        }
1840    }
1841
1842    @Override
1843    public void onOrientationChanged(int orientation) {
1844        // We keep the last known orientation. So if the user first orient
1845        // the camera then point the camera to floor or sky, we still have
1846        // the correct orientation.
1847        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
1848            return;
1849        }
1850        mLastRawOrientation = orientation;
1851        if (mCurrentModule != null) {
1852            mCurrentModule.onOrientationChanged(orientation);
1853        }
1854    }
1855
1856    /**
1857     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1858     * capture intent.
1859     *
1860     * @param enable {@code true} to enable swipe.
1861     */
1862    public void setSwipingEnabled(boolean enable) {
1863        // TODO: Bring back the functionality.
1864        if (isCaptureIntent()) {
1865            // lockPreview(true);
1866        } else {
1867            // lockPreview(!enable);
1868        }
1869    }
1870
1871    // Accessor methods for getting latency times used in performance testing
1872    public long getFirstPreviewTime() {
1873        if (mCurrentModule instanceof PhotoModule) {
1874            long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
1875            if (coverHiddenTime != -1) {
1876                return coverHiddenTime - mOnCreateTime;
1877            }
1878        }
1879        return -1;
1880    }
1881
1882    public long getAutoFocusTime() {
1883        return (mCurrentModule instanceof PhotoModule) ?
1884                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1885    }
1886
1887    public long getShutterLag() {
1888        return (mCurrentModule instanceof PhotoModule) ?
1889                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1890    }
1891
1892    public long getShutterToPictureDisplayedTime() {
1893        return (mCurrentModule instanceof PhotoModule) ?
1894                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1895    }
1896
1897    public long getPictureDisplayedToJpegCallbackTime() {
1898        return (mCurrentModule instanceof PhotoModule) ?
1899                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1900    }
1901
1902    public long getJpegCallbackFinishTime() {
1903        return (mCurrentModule instanceof PhotoModule) ?
1904                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1905    }
1906
1907    public long getCaptureStartTime() {
1908        return (mCurrentModule instanceof PhotoModule) ?
1909                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1910    }
1911
1912    public boolean isRecording() {
1913        return (mCurrentModule instanceof VideoModule) ?
1914                ((VideoModule) mCurrentModule).isRecording() : false;
1915    }
1916
1917    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
1918        return mCameraController;
1919    }
1920
1921    // For debugging purposes only.
1922    public CameraModule getCurrentModule() {
1923        return mCurrentModule;
1924    }
1925
1926    /**
1927     * Reads the current location recording settings and passes it on to the
1928     * location manager.
1929     */
1930    public void syncLocationManagerSetting() {
1931        mSettingsManager.syncLocationManager(mLocationManager);
1932    }
1933
1934    private void keepScreenOnForAWhile() {
1935        if (mKeepScreenOn) {
1936            return;
1937        }
1938        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1939        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1940        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
1941    }
1942
1943    private void resetScreenOn() {
1944        mKeepScreenOn = false;
1945        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1946        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1947    }
1948
1949    /**
1950     * @return {@code true} if the Gallery is launched successfully.
1951     */
1952    private boolean startGallery() {
1953        if (mGalleryIntent == null) {
1954            return false;
1955        }
1956        try {
1957            UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY, InteractionCause.BUTTON);
1958            Intent startGalleryIntent = new Intent(mGalleryIntent);
1959            int currentDataId = mFilmstripController.getCurrentId();
1960            LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
1961            if (currentLocalData != null) {
1962                GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getContentUri());
1963            }
1964            launchActivityByIntent(startGalleryIntent);
1965        } catch (ActivityNotFoundException e) {
1966            Log.w(TAG, "Failed to launch gallery activity, closing");
1967        }
1968        return false;
1969    }
1970
1971    private void setNfcBeamPushUriFromData(LocalData data) {
1972        final Uri uri = data.getContentUri();
1973        if (uri != Uri.EMPTY) {
1974            mNfcPushUris[0] = uri;
1975        } else {
1976            mNfcPushUris[0] = null;
1977        }
1978    }
1979
1980    /**
1981     * Updates the visibility of the filmstrip bottom controls.
1982     */
1983    private void updateUiByData(final int dataId) {
1984        if (isSecureCamera()) {
1985            // We cannot show buttons in secure camera since go to other
1986            // activities might create a security hole.
1987            return;
1988        }
1989
1990        final LocalData currentData = mDataAdapter.getLocalData(dataId);
1991        if (currentData == null) {
1992            Log.w(TAG, "Current data ID not found.");
1993            hideSessionProgress();
1994            return;
1995        }
1996
1997        setNfcBeamPushUriFromData(currentData);
1998
1999        /* Bottom controls. */
2000
2001        updateBottomControlsByData(currentData);
2002        if (!mDataAdapter.isMetadataUpdated(dataId)) {
2003            mDataAdapter.updateMetadata(dataId);
2004        }
2005    }
2006
2007    /**
2008     * Updates the bottom controls based on the data.
2009     */
2010    private void updateBottomControlsByData(final LocalData currentData) {
2011
2012        final CameraAppUI.BottomControls filmstripBottomControls =
2013                mCameraAppUI.getFilmstripBottomControls();
2014        filmstripBottomControls.setEditButtonVisibility(
2015                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2016        filmstripBottomControls.setShareButtonVisibility(
2017                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2018        filmstripBottomControls.setDeleteButtonVisibility(
2019                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2020
2021        /* Progress bar */
2022
2023        Uri contentUri = currentData.getContentUri();
2024        CaptureSessionManager sessionManager = getServices()
2025                .getCaptureSessionManager();
2026        int sessionProgress = sessionManager.getSessionProgress(contentUri);
2027
2028        if (sessionProgress < 0) {
2029            hideSessionProgress();
2030        } else {
2031            CharSequence progressMessage = sessionManager
2032                    .getSessionProgressMessage(contentUri);
2033            showSessionProgress(progressMessage);
2034            updateSessionProgress(sessionProgress);
2035        }
2036
2037        /* View button */
2038
2039        // We need to add this to a separate DB.
2040        final int viewButtonVisibility;
2041        if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2042            viewButtonVisibility = CameraAppUI.BottomControls.VIEWER_PHOTO_SPHERE;
2043        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2044            viewButtonVisibility = CameraAppUI.BottomControls.VIEWER_REFOCUS;
2045        } else {
2046            viewButtonVisibility = CameraAppUI.BottomControls.VIEWER_NONE;
2047        }
2048
2049        filmstripBottomControls.setTinyPlanetEnabled(
2050                PanoramaMetadataLoader.isPanorama360(currentData));
2051        filmstripBottomControls.setViewerButtonVisibility(viewButtonVisibility);
2052    }
2053
2054    private class PeekAnimationHandler extends Handler {
2055        private class DataAndCallback {
2056            LocalData mData;
2057            com.android.camera.util.Callback<Bitmap> mCallback;
2058
2059            public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2060                    callback) {
2061                mData = data;
2062                mCallback = callback;
2063            }
2064        }
2065
2066        public PeekAnimationHandler(Looper looper) {
2067            super(looper);
2068        }
2069
2070        /**
2071         * Starts the animation decoding job and posts a {@code Runnable} back
2072         * when when the decoding is done.
2073         *
2074         * @param data The data item to decode the thumbnail for.
2075         * @param callback {@link com.android.camera.util.Callback} after the
2076         *                 decoding is done.
2077         */
2078        public void startDecodingJob(final LocalData data,
2079                final com.android.camera.util.Callback<Bitmap> callback) {
2080            PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2081                    new DataAndCallback(data, callback)).sendToTarget();
2082        }
2083
2084        @Override
2085        public void handleMessage(Message msg) {
2086            final LocalData data = ((DataAndCallback) msg.obj).mData;
2087            final com.android.camera.util.Callback<Bitmap> callback =
2088                    ((DataAndCallback) msg.obj).mCallback;
2089            if (data == null || callback == null) {
2090                return;
2091            }
2092
2093            final Bitmap bitmap;
2094            switch (data.getLocalDataType()) {
2095                case LocalData.LOCAL_IMAGE:
2096                case LocalData.LOCAL_IN_PROGRESS_DATA:
2097                    FileInputStream stream;
2098                    try {
2099                        stream = new FileInputStream(data.getPath());
2100                    } catch (FileNotFoundException e) {
2101                        Log.e(TAG, "File not found:" + data.getPath());
2102                        return;
2103                    }
2104                    Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2105                            data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2106                            mAboveFilmstripControlLayout.getMeasuredHeight());
2107                    if (data.getRotation() % 180 != 0) {
2108                        int dummy = dim.x;
2109                        dim.x = dim.y;
2110                        dim.y = dummy;
2111                    }
2112                    bitmap = LocalDataUtil
2113                            .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2114                                    (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2115                                    data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2116                    break;
2117
2118                case LocalData.LOCAL_VIDEO:
2119                    bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2120                    break;
2121
2122                default:
2123                    bitmap = null;
2124                    break;
2125            }
2126
2127            if (bitmap == null) {
2128                return;
2129            }
2130
2131            mMainHandler.post(new Runnable() {
2132                @Override
2133                public void run() {
2134                    callback.onCallback(bitmap);
2135                    mCameraAppUI.startPeekAnimation(bitmap, true);
2136                }
2137            });
2138        }
2139    }
2140}
2141