CameraActivity.java revision 0374e092eb6fdb33cb6edcb7724601928283dd47
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.app.ActionBar;
21import android.app.Activity;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.ServiceConnection;
29import android.content.SharedPreferences;
30import android.content.pm.ActivityInfo;
31import android.content.res.Configuration;
32import android.graphics.drawable.ColorDrawable;
33import android.net.Uri;
34import android.os.AsyncTask;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.IBinder;
38import android.preference.PreferenceManager;
39import android.provider.MediaStore;
40import android.provider.Settings;
41import android.util.Log;
42import android.view.KeyEvent;
43import android.view.LayoutInflater;
44import android.view.Menu;
45import android.view.MenuInflater;
46import android.view.MenuItem;
47import android.view.MotionEvent;
48import android.view.OrientationEventListener;
49import android.view.View;
50import android.view.ViewGroup;
51import android.view.Window;
52import android.view.WindowManager;
53import android.widget.FrameLayout;
54import android.widget.ImageView;
55import android.widget.ProgressBar;
56import android.widget.ShareActionProvider;
57
58import com.android.camera.app.AppManagerFactory;
59import com.android.camera.app.PanoramaStitchingManager;
60import com.android.camera.data.CameraDataAdapter;
61import com.android.camera.data.CameraPreviewData;
62import com.android.camera.data.FixedFirstDataAdapter;
63import com.android.camera.data.FixedLastDataAdapter;
64import com.android.camera.data.LocalData;
65import com.android.camera.data.LocalDataAdapter;
66import com.android.camera.data.MediaDetails;
67import com.android.camera.data.SimpleViewData;
68import com.android.camera.tinyplanet.TinyPlanetFragment;
69import com.android.camera.ui.ModuleSwitcher;
70import com.android.camera.ui.DetailsDialog;
71import com.android.camera.ui.FilmStripView;
72import com.android.camera.util.ApiHelper;
73import com.android.camera.util.CameraUtil;
74import com.android.camera.util.GcamHelper;
75import com.android.camera.util.PhotoSphereHelper;
76import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
77import com.android.camera2.R;
78
79import static com.android.camera.CameraManager.CameraOpenErrorCallback;
80
81public class CameraActivity extends Activity
82        implements ModuleSwitcher.ModuleSwitchListener {
83
84    private static final String TAG = "CAM_Activity";
85
86    /**
87     * The visibility flags to use to switch the system in either lights-out
88     * mode (pre-K) or hideybar mode (K and up).
89     */
90    private static final int IMMERSIVE_FLAGS = getImmersiveFlags();
91
92    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
93            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
94    public static final String ACTION_IMAGE_CAPTURE_SECURE =
95            "android.media.action.IMAGE_CAPTURE_SECURE";
96    public static final String ACTION_TRIM_VIDEO =
97            "com.android.camera.action.TRIM";
98    public static final String MEDIA_ITEM_PATH = "media-item-path";
99
100    private static final String PREF_STARTUP_MODULE_INDEX = "camera.startup_module";
101
102    // The intent extra for camera from secure lock screen. True if the gallery
103    // should only show newly captured pictures. sSecureAlbumId does not
104    // increment. This is used when switching between camera, camcorder, and
105    // panorama. If the extra is not set, it is in the normal camera mode.
106    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
107
108    /**
109     * Request code from an activity we started that indicated that we do not
110     * want to reset the view to the preview in onResume.
111     */
112    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
113
114    /** Request code for external image editor activities. */
115    private static final int REQ_CODE_EDIT = 1;
116
117
118    /** Whether onResume should reset the view to the preview. */
119    private boolean mResetToPreviewOnResume = true;
120
121    // Supported operations at FilmStripView. Different data has different
122    // set of supported operations.
123    private static final int SUPPORT_DELETE = 1 << 0;
124    private static final int SUPPORT_ROTATE = 1 << 1;
125    private static final int SUPPORT_INFO = 1 << 2;
126    private static final int SUPPORT_CROP = 1 << 3;
127    private static final int SUPPORT_SETAS = 1 << 4;
128    private static final int SUPPORT_EDIT = 1 << 5;
129    private static final int SUPPORT_TRIM = 1 << 6;
130    private static final int SUPPORT_SHARE = 1 << 7;
131    private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8;
132    private static final int SUPPORT_SHOW_ON_MAP = 1 << 9;
133    private static final int SUPPORT_ALL = 0xffffffff;
134
135    /** This data adapter is used by FilmStripView. */
136    private LocalDataAdapter mDataAdapter;
137    /** This data adapter represents the real local camera data. */
138    private LocalDataAdapter mWrappedDataAdapter;
139
140    private PanoramaStitchingManager mPanoramaManager;
141    private int mCurrentModuleIndex;
142    private CameraModule mCurrentModule;
143    private FrameLayout mAboveFilmstripControlLayout;
144    private View mCameraModuleRootView;
145    private FilmStripView mFilmStripView;
146    private ProgressBar mBottomProgress;
147    private View mPanoStitchingPanel;
148    private int mResultCodeForTesting;
149    private Intent mResultDataForTesting;
150    private OnScreenHint mStorageHint;
151    private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD;
152    private boolean mAutoRotateScreen;
153    private boolean mSecureCamera;
154    // This is a hack to speed up the start of SecureCamera.
155    private static boolean sFirstStartAfterScreenOn = true;
156    private int mLastRawOrientation;
157    private MyOrientationEventListener mOrientationListener;
158    private Handler mMainHandler;
159    private PanoramaViewHelper mPanoramaViewHelper;
160    private CameraPreviewData mCameraPreviewData;
161    private ActionBar mActionBar;
162    private Menu mActionBarMenu;
163    private ViewGroup mUndoDeletionBar;
164    private boolean mIsUndoingDeletion = false;
165
166    private ShareActionProvider mStandardShareActionProvider;
167    private Intent mStandardShareIntent;
168    private ShareActionProvider mPanoramaShareActionProvider;
169    private Intent mPanoramaShareIntent;
170
171    private final int DEFAULT_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
172            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
173    private boolean mPendingDeletion = false;
174
175    private Intent mVideoShareIntent;
176    private Intent mImageShareIntent;
177
178    private class MyOrientationEventListener
179            extends OrientationEventListener {
180        public MyOrientationEventListener(Context context) {
181            super(context);
182        }
183
184        @Override
185        public void onOrientationChanged(int orientation) {
186            // We keep the last known orientation. So if the user first orient
187            // the camera then point the camera to floor or sky, we still have
188            // the correct orientation.
189            if (orientation == ORIENTATION_UNKNOWN) {
190                return;
191            }
192            mLastRawOrientation = orientation;
193            mCurrentModule.onOrientationChanged(orientation);
194        }
195    }
196
197    private MediaSaveService mMediaSaveService;
198    private ServiceConnection mConnection = new ServiceConnection() {
199        @Override
200        public void onServiceConnected(ComponentName className, IBinder b) {
201            mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService();
202            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
203        }
204
205        @Override
206        public void onServiceDisconnected(ComponentName className) {
207            if (mMediaSaveService != null) {
208                mMediaSaveService.setListener(null);
209                mMediaSaveService = null;
210            }
211        }
212    };
213
214    private CameraOpenErrorCallback mCameraOpenErrorCallback =
215            new CameraOpenErrorCallback() {
216                @Override
217                public void onCameraDisabled(int cameraId) {
218                    CameraUtil.showErrorAndFinish(CameraActivity.this,
219                            R.string.camera_disabled);
220                }
221
222                @Override
223                public void onDeviceOpenFailure(int cameraId) {
224                    CameraUtil.showErrorAndFinish(CameraActivity.this,
225                            R.string.cannot_connect_camera);
226                }
227
228                @Override
229                public void onReconnectionFailure(CameraManager mgr) {
230                    CameraUtil.showErrorAndFinish(CameraActivity.this,
231                            R.string.cannot_connect_camera);
232                }
233            };
234
235    // close activity when screen turns off
236    private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
237        @Override
238        public void onReceive(Context context, Intent intent) {
239            finish();
240        }
241    };
242
243    private static BroadcastReceiver sScreenOffReceiver;
244
245    private static class ScreenOffReceiver extends BroadcastReceiver {
246        @Override
247        public void onReceive(Context context, Intent intent) {
248            sFirstStartAfterScreenOn = true;
249        }
250    }
251
252    private static int getImmersiveFlags() {
253        if (ApiHelper.HAS_HIDEYBARS) {
254            return View.SYSTEM_UI_FLAG_IMMERSIVE
255                    | View.SYSTEM_UI_FLAG_TRANSPARENT_STATUS
256                    | View.SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION
257                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
258                    | View.SYSTEM_UI_FLAG_FULLSCREEN;
259        } else {
260            // Pre-KitKat we use lights-out mode.
261            return View.SYSTEM_UI_FLAG_LOW_PROFILE;
262        }
263    }
264
265    public static boolean isFirstStartAfterScreenOn() {
266        return sFirstStartAfterScreenOn;
267    }
268
269    public static void resetFirstStartAfterScreenOn() {
270        sFirstStartAfterScreenOn = false;
271    }
272
273    private FilmStripView.Listener mFilmStripListener =
274            new FilmStripView.Listener() {
275                @Override
276                public void onDataPromoted(int dataID) {
277                    removeData(dataID);
278                }
279
280                @Override
281                public void onDataDemoted(int dataID) {
282                    removeData(dataID);
283                }
284
285                @Override
286                public void onDataFullScreenChange(int dataID, boolean full) {
287                    boolean isCameraID = isCameraPreview(dataID);
288                    if (!isCameraID) {
289                        setActionBarVisibilityAndLightsOut(full);
290                    }
291                }
292
293                /**
294                 * Check if the local data corresponding to dataID is the camera
295                 * preview.
296                 *
297                 * @param dataID the ID of the local data
298                 * @return true if the local data is not null and it is the
299                 *         camera preview.
300                 */
301                private boolean isCameraPreview(int dataID) {
302                    LocalData localData = mDataAdapter.getLocalData(dataID);
303                    if (localData == null) {
304                        Log.w(TAG, "Current data ID not found.");
305                        return false;
306                    }
307                    return localData.getLocalDataType() == LocalData.LOCAL_CAMERA_PREVIEW;
308                }
309
310                @Override
311                public void onCurrentDataChanged(final int dataID, final boolean current) {
312                    runOnUiThread(new Runnable() {
313                        @Override
314                        public void run() {
315                            LocalData currentData = mDataAdapter.getLocalData(dataID);
316                            if (currentData == null) {
317                                Log.w(TAG, "Current data ID not found.");
318                                hidePanoStitchingProgress();
319                                return;
320                            }
321                            boolean isCameraID = currentData.getLocalDataType() ==
322                                    LocalData.LOCAL_CAMERA_PREVIEW;
323                            if (!current) {
324                                if (isCameraID) {
325                                    mCurrentModule.onPreviewFocusChanged(false);
326                                    setActionBarVisibilityAndLightsOut(false);
327                                }
328                                hidePanoStitchingProgress();
329                            } else {
330                                if (isCameraID) {
331                                    mCurrentModule.onPreviewFocusChanged(true);
332                                    // Don't show the action bar in Camera
333                                    // preview.
334                                    setActionBarVisibilityAndLightsOut(true);
335                                    if (mPendingDeletion) {
336                                        performDeletion();
337                                    }
338                                } else {
339                                    updateActionBarMenu(dataID);
340                                }
341
342                                Uri contentUri = currentData.getContentUri();
343                                if (contentUri == null) {
344                                    hidePanoStitchingProgress();
345                                    return;
346                                }
347                                int panoStitchingProgress = mPanoramaManager.getTaskProgress(
348                                        contentUri);
349                                if (panoStitchingProgress < 0) {
350                                    hidePanoStitchingProgress();
351                                    return;
352                                }
353                                showPanoStitchingProgress();
354                                updateStitchingProgress(panoStitchingProgress);
355                            }
356                        }
357                    });
358                }
359
360                @Override
361                public boolean onToggleActionBarVisibility(int dataID) {
362                    if (mActionBar.isShowing()) {
363                        setActionBarVisibilityAndLightsOut(true);
364                        return false;
365                    } else {
366                        // Don't show the action bar if that is the camera preview.
367                        boolean isCameraID = isCameraPreview(dataID);
368                        if (!isCameraID) {
369                            setActionBarVisibilityAndLightsOut(false);
370                            return true;
371                        }
372                        return false;
373                    }
374                }
375            };
376
377    public void gotoGallery() {
378        mFilmStripView.getController().goToNextItem();
379    }
380
381    /**
382     * If enabled, this hides the action bar and switches the system UI to
383     * lights-out mode.
384     */
385    private void setActionBarVisibilityAndLightsOut(boolean enabled) {
386        int visibility = DEFAULT_SYSTEM_UI_VISIBILITY | (enabled ? IMMERSIVE_FLAGS
387                : View.SYSTEM_UI_FLAG_VISIBLE);
388        mAboveFilmstripControlLayout
389                .setSystemUiVisibility(visibility);
390        if (enabled) {
391            mActionBar.hide();
392        } else {
393            mActionBar.show();
394        }
395    }
396
397    private void hidePanoStitchingProgress() {
398        mPanoStitchingPanel.setVisibility(View.GONE);
399    }
400
401    private void showPanoStitchingProgress() {
402        mPanoStitchingPanel.setVisibility(View.VISIBLE);
403    }
404
405    private void updateStitchingProgress(int progress) {
406        mBottomProgress.setProgress(progress);
407    }
408
409    private void setStandardShareIntent(Uri contentUri, String mimeType) {
410        mStandardShareIntent = getShareIntentFromType(mimeType);
411        if (mStandardShareIntent != null) {
412            mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
413            mStandardShareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
414            if (mStandardShareActionProvider != null) {
415                mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
416            }
417        }
418    }
419
420    /**
421     * Get the share intent according to the mimeType
422     *
423     * @param mimeType The mimeType of current data.
424     * @return the video/image's ShareIntent or null if mimeType is invalid.
425     */
426    private Intent getShareIntentFromType(String mimeType) {
427        // Lazily create the intent object.
428        if (mimeType.startsWith("video/")) {
429            if (mVideoShareIntent == null) {
430                mVideoShareIntent = new Intent(Intent.ACTION_SEND);
431                mVideoShareIntent.setType("video/*");
432            }
433            return mVideoShareIntent;
434        } else if (mimeType.startsWith("image/")) {
435            if (mImageShareIntent == null) {
436                mImageShareIntent = new Intent(Intent.ACTION_SEND);
437                mImageShareIntent.setType("image/*");
438            }
439            return mImageShareIntent;
440        }
441        Log.w(TAG, "unsupported mimeType " + mimeType);
442        return null;
443    }
444
445    private void setPanoramaShareIntent(Uri contentUri) {
446        if (mPanoramaShareIntent == null) {
447            mPanoramaShareIntent = new Intent(Intent.ACTION_SEND);
448        }
449        mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg");
450        mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
451        if (mPanoramaShareActionProvider != null) {
452            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
453        }
454    }
455
456    /**
457     * According to the data type, make the menu items for supported operations
458     * visible.
459     *
460     * @param dataID the data ID of the current item.
461     */
462    private void updateActionBarMenu(int dataID) {
463        LocalData currentData = mDataAdapter.getLocalData(dataID);
464        int type = currentData.getLocalDataType();
465
466        if (mActionBarMenu == null) {
467            return;
468        }
469
470        int supported = 0;
471        switch (type) {
472            case LocalData.LOCAL_IMAGE:
473                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
474                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
475                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
476                break;
477            case LocalData.LOCAL_VIDEO:
478                supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM
479                        | SUPPORT_SHARE;
480                break;
481            case LocalData.LOCAL_PHOTO_SPHERE:
482                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
483                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
484                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
485                break;
486            case LocalData.LOCAL_360_PHOTO_SPHERE:
487                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
488                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
489                        | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360
490                        | SUPPORT_SHOW_ON_MAP;
491                break;
492            default:
493                break;
494        }
495
496        setMenuItemVisible(mActionBarMenu, R.id.action_delete,
497                (supported & SUPPORT_DELETE) != 0);
498        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw,
499                (supported & SUPPORT_ROTATE) != 0);
500        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw,
501                (supported & SUPPORT_ROTATE) != 0);
502        setMenuItemVisible(mActionBarMenu, R.id.action_details,
503                (supported & SUPPORT_INFO) != 0);
504        setMenuItemVisible(mActionBarMenu, R.id.action_crop,
505                (supported & SUPPORT_CROP) != 0);
506        setMenuItemVisible(mActionBarMenu, R.id.action_setas,
507                (supported & SUPPORT_SETAS) != 0);
508        setMenuItemVisible(mActionBarMenu, R.id.action_edit,
509                (supported & SUPPORT_EDIT) != 0);
510        setMenuItemVisible(mActionBarMenu, R.id.action_trim,
511                (supported & SUPPORT_TRIM) != 0);
512
513        boolean standardShare = (supported & SUPPORT_SHARE) != 0;
514        boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0;
515        setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare);
516        setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare);
517
518        if (panoramaShare) {
519            // For 360 PhotoSphere, relegate standard share to the overflow menu
520            MenuItem item = mActionBarMenu.findItem(R.id.action_share);
521            if (item != null) {
522                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
523                item.setTitle(getResources().getString(R.string.share_as_photo));
524            }
525            // And, promote "share as panorama" to action bar
526            item = mActionBarMenu.findItem(R.id.action_share_panorama);
527            if (item != null) {
528                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
529            }
530            setPanoramaShareIntent(currentData.getContentUri());
531        }
532        if (standardShare) {
533            if (!panoramaShare) {
534                MenuItem item = mActionBarMenu.findItem(R.id.action_share);
535                if (item != null) {
536                    item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
537                    item.setTitle(getResources().getString(R.string.share));
538                }
539            }
540            setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType());
541        }
542
543        boolean itemHasLocation = currentData.getLatLong() != null;
544        setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map,
545                itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0);
546    }
547
548    private void setMenuItemVisible(Menu menu, int itemId, boolean visible) {
549        MenuItem item = menu.findItem(itemId);
550        if (item != null)
551            item.setVisible(visible);
552    }
553
554    private ImageTaskManager.TaskListener mStitchingListener =
555            new ImageTaskManager.TaskListener() {
556                @Override
557                public void onTaskQueued(String filePath, final Uri imageUri) {
558                    mMainHandler.post(new Runnable() {
559                        @Override
560                        public void run() {
561                            notifyNewMedia(imageUri);
562                        }
563                    });
564                }
565
566                @Override
567                public void onTaskDone(String filePath, final Uri imageUri) {
568                    Log.v(TAG, "onTaskDone:" + filePath);
569                    mMainHandler.post(new Runnable() {
570                        @Override
571                        public void run() {
572                            int doneID = mDataAdapter.findDataByContentUri(imageUri);
573                            int currentDataId = mFilmStripView.getCurrentId();
574
575                            if (currentDataId == doneID) {
576                                hidePanoStitchingProgress();
577                                updateStitchingProgress(0);
578                            }
579
580                            mDataAdapter.refresh(getContentResolver(), imageUri);
581                        }
582                    });
583                }
584
585                @Override
586                public void onTaskProgress(
587                        String filePath, final Uri imageUri, final int progress) {
588                    mMainHandler.post(new Runnable() {
589                        @Override
590                        public void run() {
591                            int currentDataId = mFilmStripView.getCurrentId();
592                            if (currentDataId == -1) {
593                                return;
594                            }
595                            if (imageUri.equals(
596                                    mDataAdapter.getLocalData(currentDataId).getContentUri())) {
597                                updateStitchingProgress(progress);
598                            }
599                        }
600                    });
601                }
602            };
603
604    public MediaSaveService getMediaSaveService() {
605        return mMediaSaveService;
606    }
607
608    public void notifyNewMedia(Uri uri) {
609        ContentResolver cr = getContentResolver();
610        String mimeType = cr.getType(uri);
611        if (mimeType.startsWith("video/")) {
612            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
613            mDataAdapter.addNewVideo(cr, uri);
614        } else if (mimeType.startsWith("image/")) {
615            CameraUtil.broadcastNewPicture(this, uri);
616            mDataAdapter.addNewPhoto(cr, uri);
617        } else if (mimeType.startsWith("application/stitching-preview")) {
618            mDataAdapter.addNewPhoto(cr, uri);
619        } else {
620            android.util.Log.w(TAG, "Unknown new media with MIME type:"
621                    + mimeType + ", uri:" + uri);
622        }
623    }
624
625    private void removeData(int dataID) {
626        mDataAdapter.removeData(CameraActivity.this, dataID);
627        if (mDataAdapter.getTotalNumber() > 1) {
628            showUndoDeletionBar();
629        } else {
630            // If camera preview is the only view left in filmstrip,
631            // no need to show undo bar.
632            performDeletion();
633        }
634    }
635
636    private void bindMediaSaveService() {
637        Intent intent = new Intent(this, MediaSaveService.class);
638        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
639    }
640
641    private void unbindMediaSaveService() {
642        if (mConnection != null) {
643            unbindService(mConnection);
644        }
645    }
646
647    @Override
648    public boolean onCreateOptionsMenu(Menu menu) {
649        // Inflate the menu items for use in the action bar
650        MenuInflater inflater = getMenuInflater();
651        inflater.inflate(R.menu.operations, menu);
652        mActionBarMenu = menu;
653
654        // Configure the standard share action provider
655        MenuItem item = menu.findItem(R.id.action_share);
656        mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider();
657        mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml");
658        if (mStandardShareIntent != null) {
659            mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
660        }
661
662        // Configure the panorama share action provider
663        item = menu.findItem(R.id.action_share_panorama);
664        mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider();
665        mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml");
666        if (mPanoramaShareIntent != null) {
667            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
668        }
669
670        return super.onCreateOptionsMenu(menu);
671    }
672
673    @Override
674    public boolean onOptionsItemSelected(MenuItem item) {
675        int currentDataId = mFilmStripView.getCurrentId();
676        if (currentDataId < 0) {
677            return false;
678        }
679        final LocalData localData = mDataAdapter.getLocalData(currentDataId);
680
681        // Handle presses on the action bar items
682        switch (item.getItemId()) {
683            case android.R.id.home:
684                // ActionBar's Up/Home button was clicked
685                if (ApiHelper.HAS_APP_GALLERY) {
686                    startActivity(Intent.makeMainSelectorActivity(
687                                Intent.ACTION_MAIN, Intent.CATEGORY_APP_GALLERY));
688                } else {
689                    mFilmStripView.getController().goToFirstItem();
690                }
691                return true;
692            case R.id.action_delete:
693                removeData(currentDataId);
694                return true;
695            case R.id.action_edit:
696                launchEditor(localData);
697                return true;
698            case R.id.action_trim: {
699                // This is going to be handled by the Gallery app.
700                Intent intent = new Intent(ACTION_TRIM_VIDEO);
701                LocalData currentData = mDataAdapter.getLocalData(
702                        mFilmStripView.getCurrentId());
703                intent.setData(currentData.getContentUri());
704                // We need the file path to wrap this into a RandomAccessFile.
705                intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath());
706                startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
707                return true;
708            }
709            case R.id.action_rotate_ccw:
710                localData.rotate90Degrees(this, mDataAdapter, currentDataId, false);
711                return true;
712            case R.id.action_rotate_cw:
713                localData.rotate90Degrees(this, mDataAdapter, currentDataId, true);
714                return true;
715            case R.id.action_crop:
716                // TODO: add the functionality.
717                return true;
718            case R.id.action_setas: {
719                Intent intent = new Intent(Intent.ACTION_ATTACH_DATA)
720                        .setDataAndType(localData.getContentUri(),
721                                localData.getMimeType())
722                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
723                intent.putExtra("mimeType", intent.getType());
724                startActivityForResult(Intent.createChooser(
725                        intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW);
726                return true;
727            }
728            case R.id.action_details:
729                (new AsyncTask<Void, Void, MediaDetails>() {
730                    @Override
731                    protected MediaDetails doInBackground(Void... params) {
732                        return localData.getMediaDetails(CameraActivity.this);
733                    }
734
735                    @Override
736                    protected void onPostExecute(MediaDetails mediaDetails) {
737                        DetailsDialog.create(CameraActivity.this, mediaDetails).show();
738                    }
739                }).execute();
740                return true;
741            case R.id.action_show_on_map:
742                double[] latLong = localData.getLatLong();
743                if (latLong != null) {
744                    CameraUtil.showOnMap(this, latLong);
745                }
746                return true;
747            default:
748                return super.onOptionsItemSelected(item);
749        }
750    }
751
752    private boolean isCaptureIntent() {
753        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
754                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
755                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
756            return true;
757        } else {
758            return false;
759        }
760    }
761
762    @Override
763    public void onCreate(Bundle state) {
764        super.onCreate(state);
765        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
766        setContentView(R.layout.camera_filmstrip);
767        mActionBar = getActionBar();
768
769        if (ApiHelper.HAS_ROTATION_ANIMATION) {
770            setRotationAnimation();
771        }
772        // Check if this is in the secure camera mode.
773        Intent intent = getIntent();
774        String action = intent.getAction();
775        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
776                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
777            mSecureCamera = true;
778        } else {
779            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
780        }
781
782        if (mSecureCamera) {
783            // Change the window flags so that secure camera can show when locked
784            Window win = getWindow();
785            WindowManager.LayoutParams params = win.getAttributes();
786            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
787            win.setAttributes(params);
788
789            // Filter for screen off so that we can finish secure camera activity
790            // when screen is off.
791            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
792            registerReceiver(mScreenOffReceiver, filter);
793            // TODO: This static screen off event receiver is a workaround to the
794            // double onResume() invocation (onResume->onPause->onResume). We should
795            // find a better solution to this.
796            if (sScreenOffReceiver == null) {
797                sScreenOffReceiver = new ScreenOffReceiver();
798                registerReceiver(sScreenOffReceiver, filter);
799            }
800        }
801        mAboveFilmstripControlLayout =
802                (FrameLayout) findViewById(R.id.camera_above_filmstrip_layout);
803        mAboveFilmstripControlLayout.setFitsSystemWindows(true);
804        // Hide action bar first since we are in full screen mode first, and
805        // switch the system UI to lights-out mode.
806        setActionBarVisibilityAndLightsOut(true);
807        mPanoramaManager = AppManagerFactory.getInstance(this)
808                .getPanoramaStitchingManager();
809        mPanoramaManager.addTaskListener(mStitchingListener);
810        LayoutInflater inflater = getLayoutInflater();
811        View rootLayout = inflater.inflate(R.layout.camera, null, false);
812        mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root);
813        mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
814        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
815        mCameraPreviewData = new CameraPreviewData(rootLayout,
816                FilmStripView.ImageData.SIZE_FULL,
817                FilmStripView.ImageData.SIZE_FULL);
818        // Put a CameraPreviewData at the first position.
819        mWrappedDataAdapter = new FixedFirstDataAdapter(
820                new CameraDataAdapter(new ColorDrawable(
821                        getResources().getColor(R.color.photo_placeholder))),
822                mCameraPreviewData);
823        mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view);
824        mFilmStripView.setViewGap(
825                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
826        mPanoramaViewHelper = new PanoramaViewHelper(this);
827        mPanoramaViewHelper.onCreate();
828        mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper);
829        // Set up the camera preview first so the preview shows up ASAP.
830        mFilmStripView.setListener(mFilmStripListener);
831
832        int moduleIndex = -1;
833        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
834                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
835            moduleIndex = ModuleSwitcher.VIDEO_MODULE_INDEX;
836        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
837                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
838                        .getAction())
839                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
840                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
841            moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
842        } else {
843            // If the activity has not been started using an explicit intent,
844            // read the module index from the last time the user changed modes
845            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
846            moduleIndex = prefs.getInt(PREF_STARTUP_MODULE_INDEX, -1);
847            if ((moduleIndex == ModuleSwitcher.GCAM_MODULE_INDEX &&
848                    !GcamHelper.hasGcamCapture(this)) || moduleIndex < 0) {
849                moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
850            }
851        }
852
853        mOrientationListener = new MyOrientationEventListener(this);
854        setModuleFromIndex(moduleIndex);
855        mCurrentModule.init(this, mCameraModuleRootView);
856        mMainHandler = new Handler(getMainLooper());
857
858        if (!mSecureCamera) {
859            mDataAdapter = mWrappedDataAdapter;
860            mFilmStripView.setDataAdapter(mDataAdapter);
861            if (!isCaptureIntent()) {
862                mDataAdapter.requestLoad(getContentResolver());
863            }
864        } else {
865            // Put a lock placeholder as the last image by setting its date to
866            // 0.
867            ImageView v = (ImageView) getLayoutInflater().inflate(
868                    R.layout.secure_album_placeholder, null);
869            mDataAdapter = new FixedLastDataAdapter(
870                    mWrappedDataAdapter,
871                    new SimpleViewData(
872                            v,
873                            v.getDrawable().getIntrinsicWidth(),
874                            v.getDrawable().getIntrinsicHeight(),
875                            0, 0));
876            // Flush out all the original data.
877            mDataAdapter.flush();
878            mFilmStripView.setDataAdapter(mDataAdapter);
879        }
880    }
881
882    private void setRotationAnimation() {
883        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
884        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
885        Window win = getWindow();
886        WindowManager.LayoutParams winParams = win.getAttributes();
887        winParams.rotationAnimation = rotationAnimation;
888        win.setAttributes(winParams);
889    }
890
891    @Override
892    public void onUserInteraction() {
893        super.onUserInteraction();
894        mCurrentModule.onUserInteraction();
895    }
896
897    @Override
898    public boolean dispatchTouchEvent(MotionEvent ev) {
899        boolean result = super.dispatchTouchEvent(ev);
900        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
901            // Real deletion is postponed until the next user interaction after
902            // the gesture that triggers deletion. Until real deletion is performed,
903            // users can click the undo button to bring back the image that they
904            // chose to delete.
905            if (mPendingDeletion && !mIsUndoingDeletion) {
906                 performDeletion();
907            }
908        }
909        return result;
910    }
911
912    @Override
913    public void onPause() {
914        mOrientationListener.disable();
915        mCurrentModule.onPauseBeforeSuper();
916        super.onPause();
917        mCurrentModule.onPauseAfterSuper();
918    }
919
920    @Override
921    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
922        if (requestCode == REQ_CODE_EDIT && resultCode == RESULT_OK) {
923            Uri uri = data.getData();
924            ContentResolver contentResolver = getContentResolver();
925            if (uri == null) {
926                // If we don't have a particular uri returned, then we have
927                // to refresh all, it is not optimal, but works best so far.
928                // Also don't requestLoad() when in secure camera mode.
929                if (!mSecureCamera) {
930                    mDataAdapter.requestLoad(contentResolver);
931                }
932            } else {
933                mDataAdapter.refresh(contentResolver, uri);
934            }
935        }
936
937        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW | requestCode == REQ_CODE_EDIT) {
938            mResetToPreviewOnResume = false;
939        } else {
940            super.onActivityResult(requestCode, resultCode, data);
941        }
942    }
943
944    @Override
945    public void onResume() {
946        // TODO: Handle this in OrientationManager.
947        // Auto-rotate off
948        if (Settings.System.getInt(getContentResolver(),
949                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
950            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
951            mAutoRotateScreen = false;
952        } else {
953            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
954            mAutoRotateScreen = true;
955        }
956        mOrientationListener.enable();
957        mCurrentModule.onResumeBeforeSuper();
958        super.onResume();
959        mCurrentModule.onResumeAfterSuper();
960
961        setSwipingEnabled(true);
962
963        if (mResetToPreviewOnResume) {
964            // Go to the preview on resume.
965            mFilmStripView.getController().goToFirstItem();
966        }
967        // Default is showing the preview, unless disabled by explicitly
968        // starting an activity we want to return from to the filmstrip rather
969        // than the preview.
970        mResetToPreviewOnResume = true;
971    }
972
973    @Override
974    public void onStart() {
975        super.onStart();
976        bindMediaSaveService();
977        mPanoramaViewHelper.onStart();
978    }
979
980    @Override
981    protected void onStop() {
982        super.onStop();
983        mPanoramaViewHelper.onStop();
984        unbindMediaSaveService();
985    }
986
987    @Override
988    public void onDestroy() {
989        if (mSecureCamera) {
990            unregisterReceiver(mScreenOffReceiver);
991        }
992        super.onDestroy();
993    }
994
995    @Override
996    public void onConfigurationChanged(Configuration config) {
997        super.onConfigurationChanged(config);
998        mCurrentModule.onConfigurationChanged(config);
999    }
1000
1001    @Override
1002    public boolean onKeyDown(int keyCode, KeyEvent event) {
1003        if (mCurrentModule.onKeyDown(keyCode, event)) {
1004            return true;
1005        }
1006        // Prevent software keyboard or voice search from showing up.
1007        if (keyCode == KeyEvent.KEYCODE_SEARCH
1008                || keyCode == KeyEvent.KEYCODE_MENU) {
1009            if (event.isLongPress()) {
1010                return true;
1011            }
1012        }
1013
1014        return super.onKeyDown(keyCode, event);
1015    }
1016
1017    @Override
1018    public boolean onKeyUp(int keyCode, KeyEvent event) {
1019        if (mCurrentModule.onKeyUp(keyCode, event)) {
1020            return true;
1021        }
1022        return super.onKeyUp(keyCode, event);
1023    }
1024
1025    @Override
1026    public void onBackPressed() {
1027        if (!mFilmStripView.inCameraFullscreen()) {
1028            mFilmStripView.getController().goToFirstItem();
1029        } else if (!mCurrentModule.onBackPressed()) {
1030            super.onBackPressed();
1031        }
1032    }
1033
1034    public boolean isAutoRotateScreen() {
1035        return mAutoRotateScreen;
1036    }
1037
1038    protected void updateStorageSpace() {
1039        mStorageSpace = Storage.getAvailableSpace();
1040    }
1041
1042    protected long getStorageSpace() {
1043        return mStorageSpace;
1044    }
1045
1046    protected void updateStorageSpaceAndHint() {
1047        updateStorageSpace();
1048        updateStorageHint(mStorageSpace);
1049    }
1050
1051    protected void updateStorageHint() {
1052        updateStorageHint(mStorageSpace);
1053    }
1054
1055    protected boolean updateStorageHintOnResume() {
1056        return true;
1057    }
1058
1059    protected void updateStorageHint(long storageSpace) {
1060        String message = null;
1061        if (storageSpace == Storage.UNAVAILABLE) {
1062            message = getString(R.string.no_storage);
1063        } else if (storageSpace == Storage.PREPARING) {
1064            message = getString(R.string.preparing_sd);
1065        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1066            message = getString(R.string.access_sd_fail);
1067        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
1068            message = getString(R.string.spaceIsLow_content);
1069        }
1070
1071        if (message != null) {
1072            if (mStorageHint == null) {
1073                mStorageHint = OnScreenHint.makeText(this, message);
1074            } else {
1075                mStorageHint.setText(message);
1076            }
1077            mStorageHint.show();
1078        } else if (mStorageHint != null) {
1079            mStorageHint.cancel();
1080            mStorageHint = null;
1081        }
1082    }
1083
1084    protected void setResultEx(int resultCode) {
1085        mResultCodeForTesting = resultCode;
1086        setResult(resultCode);
1087    }
1088
1089    protected void setResultEx(int resultCode, Intent data) {
1090        mResultCodeForTesting = resultCode;
1091        mResultDataForTesting = data;
1092        setResult(resultCode, data);
1093    }
1094
1095    public int getResultCode() {
1096        return mResultCodeForTesting;
1097    }
1098
1099    public Intent getResultData() {
1100        return mResultDataForTesting;
1101    }
1102
1103    public boolean isSecureCamera() {
1104        return mSecureCamera;
1105    }
1106
1107    @Override
1108    public void onModuleSelected(int moduleIndex) {
1109        if (mCurrentModuleIndex == moduleIndex) {
1110            return;
1111        }
1112
1113        CameraHolder.instance().keep();
1114        closeModule(mCurrentModule);
1115        setModuleFromIndex(moduleIndex);
1116
1117        openModule(mCurrentModule);
1118        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1119        if (mMediaSaveService != null) {
1120            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
1121        }
1122
1123        // Store the module index so we can use it the next time the Camera
1124        // starts up.
1125        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1126        prefs.edit().putInt(PREF_STARTUP_MODULE_INDEX, moduleIndex).apply();
1127    }
1128
1129    /**
1130     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1131     * index an sets it as mCurrentModule.
1132     */
1133    private void setModuleFromIndex(int moduleIndex) {
1134        mCurrentModuleIndex = moduleIndex;
1135        switch (moduleIndex) {
1136            case ModuleSwitcher.VIDEO_MODULE_INDEX:
1137                mCurrentModule = new VideoModule();
1138                break;
1139
1140            case ModuleSwitcher.PHOTO_MODULE_INDEX:
1141                mCurrentModule = new PhotoModule();
1142                break;
1143
1144            case ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX:
1145                mCurrentModule = new WideAnglePanoramaModule();
1146                break;
1147
1148            case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX:
1149                mCurrentModule = PhotoSphereHelper.createPanoramaModule();
1150                break;
1151            case ModuleSwitcher.GCAM_MODULE_INDEX:
1152                // Force immediate release of Camera instance
1153                CameraHolder.instance().strongRelease();
1154                mCurrentModule = GcamHelper.createGcamModule();
1155                break;
1156            default:
1157                // Fall back to photo mode.
1158                mCurrentModule = new PhotoModule();
1159                mCurrentModuleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
1160                break;
1161        }
1162    }
1163
1164    /**
1165     * Launches an ACTION_EDIT intent for the given local data item.
1166     */
1167    public void launchEditor(LocalData data) {
1168        Intent intent = new Intent(Intent.ACTION_EDIT)
1169                .setDataAndType(data.getContentUri(), data.getMimeType())
1170                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1171        startActivityForResult(Intent.createChooser(intent, null), REQ_CODE_EDIT);
1172    }
1173
1174    /**
1175     * Launch the tiny planet editor.
1176     *
1177     * @param data the data must be a 360 degree stereographically mapped
1178     *            panoramic image. It will not be modified, instead a new item
1179     *            with the result will be added to the filmstrip.
1180     */
1181    public void launchTinyPlanetEditor(LocalData data) {
1182        TinyPlanetFragment fragment = new TinyPlanetFragment();
1183        Bundle bundle = new Bundle();
1184        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1185        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1186        fragment.setArguments(bundle);
1187        fragment.show(getFragmentManager(), "tiny_planet");
1188    }
1189
1190    private void openModule(CameraModule module) {
1191        module.init(this, mCameraModuleRootView);
1192        module.onResumeBeforeSuper();
1193        module.onResumeAfterSuper();
1194    }
1195
1196    private void closeModule(CameraModule module) {
1197        module.onPauseBeforeSuper();
1198        module.onPauseAfterSuper();
1199        ((ViewGroup) mCameraModuleRootView).removeAllViews();
1200    }
1201
1202    private void performDeletion() {
1203        if (!mPendingDeletion) {
1204            return;
1205        }
1206        hideUndoDeletionBar(false);
1207        mDataAdapter.executeDeletion(CameraActivity.this);
1208    }
1209
1210    public void showUndoDeletionBar() {
1211        if (mPendingDeletion) {
1212            performDeletion();
1213        }
1214        Log.v(TAG, "showing undo bar");
1215        mPendingDeletion = true;
1216        if (mUndoDeletionBar == null) {
1217            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(
1218                    R.layout.undo_bar, mAboveFilmstripControlLayout, true);
1219            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1220            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1221            button.setOnClickListener(new View.OnClickListener() {
1222                @Override
1223                public void onClick(View view) {
1224                    mDataAdapter.undoDataRemoval();
1225                    hideUndoDeletionBar(true);
1226                }
1227            });
1228            // Setting undo bar clickable to avoid touch events going through
1229            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1230            mUndoDeletionBar.setClickable(true);
1231            // When there is user interaction going on with the undo button, we
1232            // do not want to hide the undo bar.
1233            button.setOnTouchListener(new View.OnTouchListener() {
1234                @Override
1235                public boolean onTouch(View v, MotionEvent event) {
1236                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1237                        mIsUndoingDeletion = true;
1238                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1239                        mIsUndoingDeletion =false;
1240                    }
1241                    return false;
1242                }
1243            });
1244        }
1245        mUndoDeletionBar.setAlpha(0f);
1246        mUndoDeletionBar.setVisibility(View.VISIBLE);
1247        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1248    }
1249
1250    private void hideUndoDeletionBar(boolean withAnimation) {
1251        Log.v(TAG, "Hiding undo deletion bar");
1252        mPendingDeletion = false;
1253        if (mUndoDeletionBar != null) {
1254            if (withAnimation) {
1255                mUndoDeletionBar.animate()
1256                        .setDuration(200)
1257                        .alpha(0f)
1258                        .setListener(new Animator.AnimatorListener() {
1259                            @Override
1260                            public void onAnimationStart(Animator animation) {
1261                                // Do nothing.
1262                            }
1263
1264                            @Override
1265                            public void onAnimationEnd(Animator animation) {
1266                                mUndoDeletionBar.setVisibility(View.GONE);
1267                            }
1268
1269                            @Override
1270                            public void onAnimationCancel(Animator animation) {
1271                                // Do nothing.
1272                            }
1273
1274                            @Override
1275                            public void onAnimationRepeat(Animator animation) {
1276                                // Do nothing.
1277                            }
1278                        })
1279                        .start();
1280            } else {
1281                mUndoDeletionBar.setVisibility(View.GONE);
1282            }
1283        }
1284    }
1285
1286    @Override
1287    public void onShowSwitcherPopup() {
1288    }
1289
1290    /**
1291     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1292     * capture intent.
1293     *
1294     * @param enable {@code true} to enable swipe.
1295     */
1296    public void setSwipingEnabled(boolean enable) {
1297        if (isCaptureIntent()) {
1298            mCameraPreviewData.lockPreview(true);
1299        } else {
1300            mCameraPreviewData.lockPreview(!enable);
1301        }
1302    }
1303
1304    // Accessor methods for getting latency times used in performance testing
1305    public long getAutoFocusTime() {
1306        return (mCurrentModule instanceof PhotoModule) ?
1307                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1308    }
1309
1310    public long getShutterLag() {
1311        return (mCurrentModule instanceof PhotoModule) ?
1312                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1313    }
1314
1315    public long getShutterToPictureDisplayedTime() {
1316        return (mCurrentModule instanceof PhotoModule) ?
1317                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1318    }
1319
1320    public long getPictureDisplayedToJpegCallbackTime() {
1321        return (mCurrentModule instanceof PhotoModule) ?
1322                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1323    }
1324
1325    public long getJpegCallbackFinishTime() {
1326        return (mCurrentModule instanceof PhotoModule) ?
1327                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1328    }
1329
1330    public long getCaptureStartTime() {
1331        return (mCurrentModule instanceof PhotoModule) ?
1332                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1333    }
1334
1335    public boolean isRecording() {
1336        return (mCurrentModule instanceof VideoModule) ?
1337                ((VideoModule) mCurrentModule).isRecording() : false;
1338    }
1339
1340    public CameraOpenErrorCallback getCameraOpenErrorCallback() {
1341        return mCameraOpenErrorCallback;
1342    }
1343}
1344