CameraActivity.java revision 44442f6f673a37e5d0fa9c7676f5147fffd97218
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                }
499
500                @Override
501                public void onFilmstripHidden() {
502                    mFilmstripVisible = false;
503                    CameraActivity.this.setFilmstripUiVisibility(false);
504                    // When the user hide the filmstrip (either swipe out or
505                    // tap on back key) we move to the first item so next time
506                    // when the user swipe in the filmstrip, the most recent
507                    // one is shown.
508                    mFilmstripController.goToFirstItem();
509                    if (mCurrentModule != null) {
510                        mCurrentModule
511                                .onPreviewVisibilityChanged(ModuleController.VISIBILITY_VISIBLE);
512                    }
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.PreviewAreaSizeChangedListener listener) {
830        mCameraAppUI.addPreviewAreaSizeChangedListener(listener);
831    }
832
833    @Override
834    public void removePreviewAreaSizeChangedListener(
835            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
836        mCameraAppUI.removePreviewAreaSizeChangedListener(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            if (mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX)
1209                        == gcamIndex && GcamHelper.hasGcamCapture()) {
1210                modeIndex = gcamIndex;
1211            }
1212        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1213                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1214            modeIndex = photoIndex;
1215        } else {
1216            // If the activity has not been started using an explicit intent,
1217            // read the module index from the last time the user changed modes
1218            modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1219            if ((modeIndex == gcamIndex &&
1220                    !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1221                modeIndex = photoIndex;
1222            }
1223        }
1224
1225        mOrientationManager = new OrientationManagerImpl(this);
1226        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1227
1228        setModuleFromModeIndex(modeIndex);
1229        mCameraAppUI.prepareModuleUI();
1230        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1231
1232        if (!mSecureCamera) {
1233            mFilmstripController.setDataAdapter(mDataAdapter);
1234            if (!isCaptureIntent()) {
1235                mDataAdapter.requestLoad();
1236            }
1237        } else {
1238            // Put a lock placeholder as the last image by setting its date to
1239            // 0.
1240            ImageView v = (ImageView) getLayoutInflater().inflate(
1241                    R.layout.secure_album_placeholder, null);
1242            v.setOnClickListener(new View.OnClickListener() {
1243                @Override
1244                public void onClick(View view) {
1245                    UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY,
1246                            InteractionCause.BUTTON);
1247                    startGallery();
1248                    finish();
1249                }
1250            });
1251            mDataAdapter = new FixedLastDataAdapter(
1252                    mAppContext,
1253                    mDataAdapter,
1254                    new SimpleViewData(
1255                            v,
1256                            v.getDrawable().getIntrinsicWidth(),
1257                            v.getDrawable().getIntrinsicHeight(),
1258                            0, 0));
1259            // Flush out all the original data.
1260            mDataAdapter.flush();
1261            mFilmstripController.setDataAdapter(mDataAdapter);
1262        }
1263
1264        setupNfcBeamPush();
1265
1266        mLocalImagesObserver = new LocalMediaObserver();
1267        mLocalVideosObserver = new LocalMediaObserver();
1268
1269        getContentResolver().registerContentObserver(
1270                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1271                mLocalImagesObserver);
1272        getContentResolver().registerContentObserver(
1273                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1274                mLocalVideosObserver);
1275        if (FeedbackHelper.feedbackAvailable()) {
1276            mFeedbackHelper = new FeedbackHelper(mAppContext);
1277        }
1278    }
1279
1280    private void setRotationAnimation() {
1281        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1282        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1283        Window win = getWindow();
1284        WindowManager.LayoutParams winParams = win.getAttributes();
1285        winParams.rotationAnimation = rotationAnimation;
1286        win.setAttributes(winParams);
1287    }
1288
1289    @Override
1290    public void onUserInteraction() {
1291        super.onUserInteraction();
1292        if (!isFinishing()) {
1293            keepScreenOnForAWhile();
1294        }
1295    }
1296
1297    @Override
1298    public boolean dispatchTouchEvent(MotionEvent ev) {
1299        boolean result = super.dispatchTouchEvent(ev);
1300        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1301            // Real deletion is postponed until the next user interaction after
1302            // the gesture that triggers deletion. Until real deletion is
1303            // performed, users can click the undo button to bring back the
1304            // image that they chose to delete.
1305            if (mPendingDeletion && !mIsUndoingDeletion) {
1306                performDeletion();
1307            }
1308        }
1309        return result;
1310    }
1311
1312    @Override
1313    public void onPause() {
1314        mPaused = true;
1315        mPeekAnimationHandler = null;
1316        mPeekAnimationThread.quitSafely();
1317        mPeekAnimationThread = null;
1318        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1319
1320        // Delete photos that are pending deletion
1321        performDeletion();
1322        mCurrentModule.pause();
1323        mOrientationManager.pause();
1324        // Close the camera and wait for the operation done.
1325        mCameraController.closeCamera();
1326        mPanoramaViewHelper.onPause();
1327
1328        mLocalImagesObserver.setActivityPaused(true);
1329        mLocalVideosObserver.setActivityPaused(true);
1330        resetScreenOn();
1331        super.onPause();
1332    }
1333
1334    @Override
1335    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1336        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1337            mResetToPreviewOnResume = false;
1338        } else {
1339            super.onActivityResult(requestCode, resultCode, data);
1340        }
1341    }
1342
1343    @Override
1344    public void onResume() {
1345        mPaused = false;
1346        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1347
1348        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1349
1350        // TODO: Handle this in OrientationManager.
1351        // Auto-rotate off
1352        if (Settings.System.getInt(getContentResolver(),
1353                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1354            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1355            mAutoRotateScreen = false;
1356        } else {
1357            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1358            mAutoRotateScreen = true;
1359        }
1360
1361        if (isCaptureIntent()) {
1362            // Foreground event caused by photo or video capure intent.
1363            UsageStatistics.foregrounded(
1364                    eventprotos.ForegroundEvent.ForegroundSource.INTENT_PICKER);
1365        } else if (!mSecureCamera) {
1366            // Foreground event that is not caused by an intent.
1367            UsageStatistics.foregrounded(
1368                    eventprotos.ForegroundEvent.ForegroundSource.ICON_LAUNCHER);
1369        }
1370
1371        Drawable galleryLogo;
1372        if (mSecureCamera) {
1373            mGalleryIntent = null;
1374            galleryLogo = null;
1375        } else {
1376            mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext);
1377            galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1378        }
1379        if (galleryLogo == null) {
1380            try {
1381                galleryLogo = getPackageManager().getActivityLogo(getComponentName());
1382            } catch (PackageManager.NameNotFoundException e) {
1383                Log.e(TAG, "Can't get the activity logo");
1384            }
1385        }
1386        if (mGalleryIntent != null) {
1387            mActionBar.setDisplayUseLogoEnabled(true);
1388        }
1389        mActionBar.setLogo(galleryLogo);
1390        mOrientationManager.resume();
1391        super.onResume();
1392        mPeekAnimationThread = new HandlerThread("Peek animation");
1393        mPeekAnimationThread.start();
1394        mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
1395        mCurrentModule.resume();
1396        setSwipingEnabled(true);
1397
1398        if (mResetToPreviewOnResume) {
1399            mCameraAppUI.resume();
1400        } else {
1401            LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1402            if (data != null) {
1403                mDataAdapter.refresh(data.getContentUri(), false);
1404            }
1405        }
1406        // The share button might be disabled to avoid double tapping.
1407        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1408        // Default is showing the preview, unless disabled by explicitly
1409        // starting an activity we want to return from to the filmstrip rather
1410        // than the preview.
1411        mResetToPreviewOnResume = true;
1412
1413        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1414                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1415            if (!mSecureCamera) {
1416                // If it's secure camera, requestLoad() should not be called
1417                // as it will load all the data.
1418                if (!mFilmstripVisible) {
1419                    mDataAdapter.requestLoad();
1420                }
1421            }
1422        }
1423        mLocalImagesObserver.setActivityPaused(false);
1424        mLocalVideosObserver.setActivityPaused(false);
1425
1426        keepScreenOnForAWhile();
1427
1428        // Lights-out mode at all times.
1429        findViewById(R.id.activity_root_view)
1430                .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
1431        mPanoramaViewHelper.onResume();
1432        ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1433        syncLocationManagerSetting();
1434    }
1435
1436    @Override
1437    public void onStart() {
1438        super.onStart();
1439        mPanoramaViewHelper.onStart();
1440    }
1441
1442    @Override
1443    protected void onStop() {
1444        mPanoramaViewHelper.onStop();
1445        if (mFeedbackHelper != null) {
1446            mFeedbackHelper.stopFeedback();
1447        }
1448
1449        mLocationManager.disconnect();
1450        super.onStop();
1451    }
1452
1453    @Override
1454    public void onDestroy() {
1455        if (mSecureCamera) {
1456            unregisterReceiver(mScreenOffReceiver);
1457        }
1458        mActionBar.removeOnMenuVisibilityListener(this);
1459        mSettingsManager.removeAllListeners();
1460        mCameraController.removeCallbackReceiver();
1461        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1462        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1463        getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
1464        mCameraAppUI.onDestroy();
1465        mCameraController = null;
1466        mSettingsManager = null;
1467        mCameraAppUI = null;
1468        mOrientationManager = null;
1469        mButtonManager = null;
1470        CameraManagerFactory.recycle();
1471        super.onDestroy();
1472    }
1473
1474    @Override
1475    public void onConfigurationChanged(Configuration config) {
1476        super.onConfigurationChanged(config);
1477        Log.v(TAG, "onConfigurationChanged");
1478        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1479            return;
1480        }
1481
1482        if (mLastLayoutOrientation != config.orientation) {
1483            mLastLayoutOrientation = config.orientation;
1484            mCurrentModule.onLayoutOrientationChanged(
1485                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1486        }
1487    }
1488
1489    @Override
1490    public boolean onKeyDown(int keyCode, KeyEvent event) {
1491        if (!mFilmstripVisible) {
1492            if (mCurrentModule.onKeyDown(keyCode, event)) {
1493                return true;
1494            }
1495            // Prevent software keyboard or voice search from showing up.
1496            if (keyCode == KeyEvent.KEYCODE_SEARCH
1497                    || keyCode == KeyEvent.KEYCODE_MENU) {
1498                if (event.isLongPress()) {
1499                    return true;
1500                }
1501            }
1502        }
1503
1504        return super.onKeyDown(keyCode, event);
1505    }
1506
1507    @Override
1508    public boolean onKeyUp(int keyCode, KeyEvent event) {
1509        if (!mFilmstripVisible && mCurrentModule.onKeyUp(keyCode, event)) {
1510            return true;
1511        }
1512        return super.onKeyUp(keyCode, event);
1513    }
1514
1515    @Override
1516    public void onBackPressed() {
1517        if (!mCameraAppUI.onBackPressed()) {
1518            if (!mCurrentModule.onBackPressed()) {
1519                super.onBackPressed();
1520            }
1521        }
1522    }
1523
1524    @Override
1525    public boolean isAutoRotateScreen() {
1526        // TODO: Move to OrientationManager.
1527        return mAutoRotateScreen;
1528    }
1529
1530    @Override
1531    public boolean onCreateOptionsMenu(Menu menu) {
1532        MenuInflater inflater = getMenuInflater();
1533        inflater.inflate(R.menu.filmstrip_menu, menu);
1534        mActionBarMenu = menu;
1535        return super.onCreateOptionsMenu(menu);
1536    }
1537
1538    protected void updateStorageSpace() {
1539        mStorageSpaceBytes = Storage.getAvailableSpace();
1540    }
1541
1542    protected long getStorageSpaceBytes() {
1543        return mStorageSpaceBytes;
1544    }
1545
1546    protected void updateStorageSpaceAndHint() {
1547        updateStorageSpace();
1548        updateStorageHint(mStorageSpaceBytes);
1549    }
1550
1551    protected void updateStorageHint(long storageSpace) {
1552        String message = null;
1553        if (storageSpace == Storage.UNAVAILABLE) {
1554            message = getString(R.string.no_storage);
1555        } else if (storageSpace == Storage.PREPARING) {
1556            message = getString(R.string.preparing_sd);
1557        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1558            message = getString(R.string.access_sd_fail);
1559        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1560            message = getString(R.string.spaceIsLow_content);
1561        }
1562
1563        if (message != null) {
1564            if (mStorageHint == null) {
1565                mStorageHint = OnScreenHint.makeText(mAppContext, message);
1566            } else {
1567                mStorageHint.setText(message);
1568            }
1569            mStorageHint.show();
1570        } else if (mStorageHint != null) {
1571            mStorageHint.cancel();
1572            mStorageHint = null;
1573        }
1574    }
1575
1576    protected void setResultEx(int resultCode) {
1577        mResultCodeForTesting = resultCode;
1578        setResult(resultCode);
1579    }
1580
1581    protected void setResultEx(int resultCode, Intent data) {
1582        mResultCodeForTesting = resultCode;
1583        mResultDataForTesting = data;
1584        setResult(resultCode, data);
1585    }
1586
1587    public int getResultCode() {
1588        return mResultCodeForTesting;
1589    }
1590
1591    public Intent getResultData() {
1592        return mResultDataForTesting;
1593    }
1594
1595    public boolean isSecureCamera() {
1596        return mSecureCamera;
1597    }
1598
1599    @Override
1600    public boolean isPaused() {
1601        return mPaused;
1602    }
1603
1604    @Override
1605    public void onModeSelected(int modeIndex) {
1606        if (mCurrentModeIndex == modeIndex) {
1607            return;
1608        }
1609
1610        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
1611        // Record last used camera mode for quick switching
1612        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
1613                || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
1614            mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX,
1615                    modeIndex);
1616        }
1617
1618        closeModule(mCurrentModule);
1619        int oldModuleIndex = mCurrentModeIndex;
1620
1621        // Refocus and Gcam are modes that cannot be selected
1622        // from the mode list view, because they are not list items.
1623        // Check whether we should interpret MODULE_CRAFT as either.
1624        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
1625            boolean hdrPlusOn = mSettingsManager.isHdrPlusOn();
1626            if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1627                modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1628            }
1629        }
1630
1631        setModuleFromModeIndex(modeIndex);
1632
1633        mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
1634        mCameraAppUI.addShutterListener(mCurrentModule);
1635        openModule(mCurrentModule);
1636        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1637        // Store the module index so we can use it the next time the Camera
1638        // starts up.
1639        SharedPreferences prefs = PreferenceManager
1640                .getDefaultSharedPreferences(mAppContext);
1641        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply();
1642    }
1643
1644    /**
1645     * Shows the settings dialog.
1646     */
1647    @Override
1648    public void onSettingsSelected() {
1649        Intent intent = new Intent(this, CameraSettingsActivity.class);
1650        startActivity(intent);
1651    }
1652
1653    /**
1654     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1655     * index an sets it as mCurrentModule.
1656     */
1657    private void setModuleFromModeIndex(int modeIndex) {
1658        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1659        if (agent == null) {
1660            return;
1661        }
1662        if (!agent.requestAppForCamera()) {
1663            mCameraController.closeCamera();
1664        }
1665        mCurrentModeIndex = agent.getModuleId();
1666        mCurrentModule = (CameraModule) agent.createModule(this);
1667    }
1668
1669    @Override
1670    public SettingsManager getSettingsManager() {
1671        return mSettingsManager;
1672    }
1673
1674    @Override
1675    public CameraServices getServices() {
1676        return (CameraServices) getApplication();
1677    }
1678
1679    public List<String> getSupportedModeNames() {
1680        List<Integer> indices = mModuleManager.getSupportedModeIndexList();
1681        List<String> supported = new ArrayList<String>();
1682
1683        for (Integer modeIndex : indices) {
1684            String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
1685            if (name != null && !name.equals("")) {
1686                supported.add(name);
1687            }
1688        }
1689        return supported;
1690    }
1691
1692    @Override
1693    public ButtonManager getButtonManager() {
1694        if (mButtonManager == null) {
1695            mButtonManager = new ButtonManager(this);
1696        }
1697        return mButtonManager;
1698    }
1699
1700    /**
1701     * Creates an AlertDialog appropriate for choosing whether to enable
1702     * location on the first run of the app.
1703     */
1704    public AlertDialog getFirstTimeLocationAlert() {
1705        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1706        builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
1707            @Override
1708            public void onCallback(Boolean locationOn) {
1709                mSettingsManager.setLocation(locationOn, mLocationManager);
1710            }
1711        });
1712        if (builder != null) {
1713            return builder.create();
1714        } else {
1715            return null;
1716        }
1717    }
1718
1719    /**
1720     * Launches an ACTION_EDIT intent for the given local data item. If
1721     * 'withTinyPlanet' is set, this will show a disambig dialog first to let
1722     * the user start either the tiny planet editor or another photo edior.
1723     *
1724     * @param data The data item to edit.
1725     */
1726    public void launchEditor(LocalData data) {
1727        Intent intent = new Intent(Intent.ACTION_EDIT)
1728                .setDataAndType(data.getContentUri(), data.getMimeType())
1729                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1730        try {
1731            launchActivityByIntent(intent);
1732        } catch (ActivityNotFoundException e) {
1733            launchActivityByIntent(Intent.createChooser(intent, null));
1734        }
1735    }
1736
1737    @Override
1738    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1739        super.onCreateContextMenu(menu, v, menuInfo);
1740
1741        MenuInflater inflater = getMenuInflater();
1742        inflater.inflate(R.menu.filmstrip_context_menu, menu);
1743    }
1744
1745    @Override
1746    public boolean onContextItemSelected(MenuItem item) {
1747        switch (item.getItemId()) {
1748            case R.id.tiny_planet_editor:
1749                mMyFilmstripBottomControlListener.onTinyPlanet();
1750                return true;
1751            case R.id.photo_editor:
1752                mMyFilmstripBottomControlListener.onEdit();
1753                return true;
1754        }
1755        return false;
1756    }
1757
1758    /**
1759     * Launch the tiny planet editor.
1760     *
1761     * @param data The data must be a 360 degree stereographically mapped
1762     *            panoramic image. It will not be modified, instead a new item
1763     *            with the result will be added to the filmstrip.
1764     */
1765    public void launchTinyPlanetEditor(LocalData data) {
1766        TinyPlanetFragment fragment = new TinyPlanetFragment();
1767        Bundle bundle = new Bundle();
1768        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1769        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1770        fragment.setArguments(bundle);
1771        fragment.show(getFragmentManager(), "tiny_planet");
1772    }
1773
1774    private void openModule(CameraModule module) {
1775        module.init(this, isSecureCamera(), isCaptureIntent());
1776        module.resume();
1777        int visibility = mFilmstripVisible ? ModuleController.VISIBILITY_HIDDEN
1778                : ModuleController.VISIBILITY_VISIBLE;
1779        module.onPreviewVisibilityChanged(visibility);
1780    }
1781
1782    private void closeModule(CameraModule module) {
1783        module.pause();
1784        mCameraAppUI.clearModuleUI();
1785    }
1786
1787    private void performDeletion() {
1788        if (!mPendingDeletion) {
1789            return;
1790        }
1791        hideUndoDeletionBar(false);
1792        mDataAdapter.executeDeletion();
1793    }
1794
1795    public void showUndoDeletionBar() {
1796        if (mPendingDeletion) {
1797            performDeletion();
1798        }
1799        Log.v(TAG, "showing undo bar");
1800        mPendingDeletion = true;
1801        if (mUndoDeletionBar == null) {
1802            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1803                    mAboveFilmstripControlLayout, true);
1804            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1805            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1806            button.setOnClickListener(new View.OnClickListener() {
1807                @Override
1808                public void onClick(View view) {
1809                    mDataAdapter.undoDataRemoval();
1810                    hideUndoDeletionBar(true);
1811                }
1812            });
1813            // Setting undo bar clickable to avoid touch events going through
1814            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1815            mUndoDeletionBar.setClickable(true);
1816            // When there is user interaction going on with the undo button, we
1817            // do not want to hide the undo bar.
1818            button.setOnTouchListener(new View.OnTouchListener() {
1819                @Override
1820                public boolean onTouch(View v, MotionEvent event) {
1821                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1822                        mIsUndoingDeletion = true;
1823                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1824                        mIsUndoingDeletion = false;
1825                    }
1826                    return false;
1827                }
1828            });
1829        }
1830        mUndoDeletionBar.setAlpha(0f);
1831        mUndoDeletionBar.setVisibility(View.VISIBLE);
1832        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1833    }
1834
1835    private void hideUndoDeletionBar(boolean withAnimation) {
1836        Log.v(TAG, "Hiding undo deletion bar");
1837        mPendingDeletion = false;
1838        if (mUndoDeletionBar != null) {
1839            if (withAnimation) {
1840                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1841                        .setListener(new Animator.AnimatorListener() {
1842                            @Override
1843                            public void onAnimationStart(Animator animation) {
1844                                // Do nothing.
1845                            }
1846
1847                            @Override
1848                            public void onAnimationEnd(Animator animation) {
1849                                mUndoDeletionBar.setVisibility(View.GONE);
1850                            }
1851
1852                            @Override
1853                            public void onAnimationCancel(Animator animation) {
1854                                // Do nothing.
1855                            }
1856
1857                            @Override
1858                            public void onAnimationRepeat(Animator animation) {
1859                                // Do nothing.
1860                            }
1861                        }).start();
1862            } else {
1863                mUndoDeletionBar.setVisibility(View.GONE);
1864            }
1865        }
1866    }
1867
1868    @Override
1869    public void onOrientationChanged(int orientation) {
1870        // We keep the last known orientation. So if the user first orient
1871        // the camera then point the camera to floor or sky, we still have
1872        // the correct orientation.
1873        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
1874            return;
1875        }
1876        mLastRawOrientation = orientation;
1877        if (mCurrentModule != null) {
1878            mCurrentModule.onOrientationChanged(orientation);
1879        }
1880    }
1881
1882    /**
1883     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1884     * capture intent.
1885     *
1886     * @param enable {@code true} to enable swipe.
1887     */
1888    public void setSwipingEnabled(boolean enable) {
1889        // TODO: Bring back the functionality.
1890        if (isCaptureIntent()) {
1891            // lockPreview(true);
1892        } else {
1893            // lockPreview(!enable);
1894        }
1895    }
1896
1897    // Accessor methods for getting latency times used in performance testing
1898    public long getFirstPreviewTime() {
1899        if (mCurrentModule instanceof PhotoModule) {
1900            long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
1901            if (coverHiddenTime != -1) {
1902                return coverHiddenTime - mOnCreateTime;
1903            }
1904        }
1905        return -1;
1906    }
1907
1908    public long getAutoFocusTime() {
1909        return (mCurrentModule instanceof PhotoModule) ?
1910                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1911    }
1912
1913    public long getShutterLag() {
1914        return (mCurrentModule instanceof PhotoModule) ?
1915                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1916    }
1917
1918    public long getShutterToPictureDisplayedTime() {
1919        return (mCurrentModule instanceof PhotoModule) ?
1920                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1921    }
1922
1923    public long getPictureDisplayedToJpegCallbackTime() {
1924        return (mCurrentModule instanceof PhotoModule) ?
1925                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1926    }
1927
1928    public long getJpegCallbackFinishTime() {
1929        return (mCurrentModule instanceof PhotoModule) ?
1930                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1931    }
1932
1933    public long getCaptureStartTime() {
1934        return (mCurrentModule instanceof PhotoModule) ?
1935                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1936    }
1937
1938    public boolean isRecording() {
1939        return (mCurrentModule instanceof VideoModule) ?
1940                ((VideoModule) mCurrentModule).isRecording() : false;
1941    }
1942
1943    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
1944        return mCameraController;
1945    }
1946
1947    // For debugging purposes only.
1948    public CameraModule getCurrentModule() {
1949        return mCurrentModule;
1950    }
1951
1952    /**
1953     * Reads the current location recording settings and passes it on to the
1954     * location manager.
1955     */
1956    public void syncLocationManagerSetting() {
1957        mSettingsManager.syncLocationManager(mLocationManager);
1958    }
1959
1960    private void keepScreenOnForAWhile() {
1961        if (mKeepScreenOn) {
1962            return;
1963        }
1964        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1965        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1966        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
1967    }
1968
1969    private void resetScreenOn() {
1970        mKeepScreenOn = false;
1971        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1972        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1973    }
1974
1975    /**
1976     * @return {@code true} if the Gallery is launched successfully.
1977     */
1978    private boolean startGallery() {
1979        if (mGalleryIntent == null) {
1980            return false;
1981        }
1982        try {
1983            UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY, InteractionCause.BUTTON);
1984            Intent startGalleryIntent = new Intent(mGalleryIntent);
1985            int currentDataId = mFilmstripController.getCurrentId();
1986            LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
1987            if (currentLocalData != null) {
1988                GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getContentUri());
1989            }
1990            launchActivityByIntent(startGalleryIntent);
1991        } catch (ActivityNotFoundException e) {
1992            Log.w(TAG, "Failed to launch gallery activity, closing");
1993        }
1994        return false;
1995    }
1996
1997    private void setNfcBeamPushUriFromData(LocalData data) {
1998        final Uri uri = data.getContentUri();
1999        if (uri != Uri.EMPTY) {
2000            mNfcPushUris[0] = uri;
2001        } else {
2002            mNfcPushUris[0] = null;
2003        }
2004    }
2005
2006    /**
2007     * Updates the visibility of the filmstrip bottom controls and action bar.
2008     */
2009    private void updateUiByData(final int dataId) {
2010        final LocalData currentData = mDataAdapter.getLocalData(dataId);
2011        if (currentData == null) {
2012            Log.w(TAG, "Current data ID not found.");
2013            hideSessionProgress();
2014            return;
2015        }
2016        updateActionBarMenu(currentData);
2017
2018        /* Bottom controls. */
2019        updateBottomControlsByData(currentData);
2020
2021        if (isSecureCamera()) {
2022            // We cannot show buttons in secure camera since go to other
2023            // activities might create a security hole.
2024            mCameraAppUI.getFilmstripBottomControls().hideControls();
2025            return;
2026        }
2027
2028
2029        setNfcBeamPushUriFromData(currentData);
2030
2031        if (!mDataAdapter.isMetadataUpdated(dataId)) {
2032            mDataAdapter.updateMetadata(dataId);
2033        }
2034    }
2035
2036    /**
2037     * Updates the bottom controls based on the data.
2038     */
2039    private void updateBottomControlsByData(final LocalData currentData) {
2040
2041        final CameraAppUI.BottomPanel filmstripBottomPanel =
2042                mCameraAppUI.getFilmstripBottomControls();
2043        filmstripBottomPanel.showControls();
2044        filmstripBottomPanel.setEditButtonVisibility(
2045                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2046        filmstripBottomPanel.setShareButtonVisibility(
2047                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2048        filmstripBottomPanel.setDeleteButtonVisibility(
2049                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2050
2051        /* Progress bar */
2052
2053        Uri contentUri = currentData.getContentUri();
2054        CaptureSessionManager sessionManager = getServices()
2055                .getCaptureSessionManager();
2056        int sessionProgress = sessionManager.getSessionProgress(contentUri);
2057
2058        if (sessionProgress < 0) {
2059            hideSessionProgress();
2060        } else {
2061            CharSequence progressMessage = sessionManager
2062                    .getSessionProgressMessage(contentUri);
2063            showSessionProgress(progressMessage);
2064            updateSessionProgress(sessionProgress);
2065        }
2066
2067        /* View button */
2068
2069        // We need to add this to a separate DB.
2070        final int viewButtonVisibility;
2071        if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2072            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2073        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2074            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2075        } else {
2076            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2077        }
2078
2079        filmstripBottomPanel.setTinyPlanetEnabled(
2080                PanoramaMetadataLoader.isPanorama360(currentData));
2081        filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2082    }
2083
2084    private class PeekAnimationHandler extends Handler {
2085        private class DataAndCallback {
2086            LocalData mData;
2087            com.android.camera.util.Callback<Bitmap> mCallback;
2088
2089            public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2090                    callback) {
2091                mData = data;
2092                mCallback = callback;
2093            }
2094        }
2095
2096        public PeekAnimationHandler(Looper looper) {
2097            super(looper);
2098        }
2099
2100        /**
2101         * Starts the animation decoding job and posts a {@code Runnable} back
2102         * when when the decoding is done.
2103         *
2104         * @param data The data item to decode the thumbnail for.
2105         * @param callback {@link com.android.camera.util.Callback} after the
2106         *                 decoding is done.
2107         */
2108        public void startDecodingJob(final LocalData data,
2109                final com.android.camera.util.Callback<Bitmap> callback) {
2110            PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2111                    new DataAndCallback(data, callback)).sendToTarget();
2112        }
2113
2114        @Override
2115        public void handleMessage(Message msg) {
2116            final LocalData data = ((DataAndCallback) msg.obj).mData;
2117            final com.android.camera.util.Callback<Bitmap> callback =
2118                    ((DataAndCallback) msg.obj).mCallback;
2119            if (data == null || callback == null) {
2120                return;
2121            }
2122
2123            final Bitmap bitmap;
2124            switch (data.getLocalDataType()) {
2125                case LocalData.LOCAL_IMAGE:
2126                case LocalData.LOCAL_IN_PROGRESS_DATA:
2127                    FileInputStream stream;
2128                    try {
2129                        stream = new FileInputStream(data.getPath());
2130                    } catch (FileNotFoundException e) {
2131                        Log.e(TAG, "File not found:" + data.getPath());
2132                        return;
2133                    }
2134                    Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2135                            data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2136                            mAboveFilmstripControlLayout.getMeasuredHeight());
2137                    if (data.getRotation() % 180 != 0) {
2138                        int dummy = dim.x;
2139                        dim.x = dim.y;
2140                        dim.y = dummy;
2141                    }
2142                    bitmap = LocalDataUtil
2143                            .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2144                                    (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2145                                    data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2146                    break;
2147
2148                case LocalData.LOCAL_VIDEO:
2149                    bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2150                    break;
2151
2152                default:
2153                    bitmap = null;
2154                    break;
2155            }
2156
2157            if (bitmap == null) {
2158                return;
2159            }
2160
2161            mMainHandler.post(new Runnable() {
2162                @Override
2163                public void run() {
2164                    callback.onCallback(bitmap);
2165                    mCameraAppUI.startPeekAnimation(bitmap, true);
2166                }
2167            });
2168        }
2169    }
2170
2171    private void showDetailsDialog(int dataId) {
2172        final LocalData data = mDataAdapter.getLocalData(dataId);
2173        if (data == null) {
2174            return;
2175        }
2176        MediaDetails details = data.getMediaDetails(getAndroidContext());
2177        if (details == null) {
2178            return;
2179        }
2180        Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details);
2181        detailDialog.show();
2182
2183    }
2184
2185    /**
2186     * Show or hide action bar items depending on current data type.
2187     */
2188    private void updateActionBarMenu(LocalData data) {
2189        if (mActionBarMenu == null) {
2190            return;
2191        }
2192
2193        MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2194        if (detailsMenuItem == null) {
2195            return;
2196        }
2197
2198        int type = data.getLocalDataType();
2199        boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO);
2200        detailsMenuItem.setVisible(showDetails);
2201    }
2202}
2203