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