CameraActivity.java revision 4ed20592482d2ab2f3f48ee72d5b1c06bf009034
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.Build;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.IBinder;
39import android.preference.PreferenceManager;
40import android.provider.MediaStore;
41import android.provider.Settings;
42import android.util.Log;
43import android.view.KeyEvent;
44import android.view.LayoutInflater;
45import android.view.Menu;
46import android.view.MenuInflater;
47import android.view.MenuItem;
48import android.view.MotionEvent;
49import android.view.OrientationEventListener;
50import android.view.View;
51import android.view.ViewGroup;
52import android.view.Window;
53import android.view.WindowManager;
54import android.widget.FrameLayout;
55import android.widget.ImageView;
56import android.widget.ProgressBar;
57import android.widget.ShareActionProvider;
58
59import com.android.camera.app.AppManagerFactory;
60import com.android.camera.app.PanoramaStitchingManager;
61import com.android.camera.data.CameraDataAdapter;
62import com.android.camera.data.CameraPreviewData;
63import com.android.camera.data.FixedFirstDataAdapter;
64import com.android.camera.data.FixedLastDataAdapter;
65import com.android.camera.data.LocalData;
66import com.android.camera.data.LocalDataAdapter;
67import com.android.camera.data.MediaDetails;
68import com.android.camera.data.SimpleViewData;
69import com.android.camera.tinyplanet.TinyPlanetFragment;
70import com.android.camera.ui.ModuleSwitcher;
71import com.android.camera.ui.DetailsDialog;
72import com.android.camera.ui.FilmStripView;
73import com.android.camera.util.ApiHelper;
74import com.android.camera.util.CameraUtil;
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 (isKitKatOrHigher()) {
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 isKitKatOrHigher() {
266        // TODO: Remove CODENAME check as soon as VERSION_CODES.KITKAT is final.
267        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
268                || "KeyLimePie".equals(Build.VERSION.CODENAME);
269    }
270
271    public static boolean isFirstStartAfterScreenOn() {
272        return sFirstStartAfterScreenOn;
273    }
274
275    public static void resetFirstStartAfterScreenOn() {
276        sFirstStartAfterScreenOn = false;
277    }
278
279    private FilmStripView.Listener mFilmStripListener =
280            new FilmStripView.Listener() {
281                @Override
282                public void onDataPromoted(int dataID) {
283                    removeData(dataID);
284                }
285
286                @Override
287                public void onDataDemoted(int dataID) {
288                    removeData(dataID);
289                }
290
291                @Override
292                public void onDataFullScreenChange(int dataID, boolean full) {
293                    boolean isCameraID = isCameraPreview(dataID);
294                    if (!isCameraID) {
295                        setActionBarVisibilityAndLightsOut(full);
296                    }
297                }
298
299                /**
300                 * Check if the local data corresponding to dataID is the camera
301                 * preview.
302                 *
303                 * @param dataID the ID of the local data
304                 * @return true if the local data is not null and it is the
305                 *         camera preview.
306                 */
307                private boolean isCameraPreview(int dataID) {
308                    LocalData localData = mDataAdapter.getLocalData(dataID);
309                    if (localData == null) {
310                        Log.w(TAG, "Current data ID not found.");
311                        return false;
312                    }
313                    return localData.getLocalDataType() == LocalData.LOCAL_CAMERA_PREVIEW;
314                }
315
316                @Override
317                public void onCurrentDataChanged(final int dataID, final boolean current) {
318                    runOnUiThread(new Runnable() {
319                        @Override
320                        public void run() {
321                            LocalData currentData = mDataAdapter.getLocalData(dataID);
322                            if (currentData == null) {
323                                Log.w(TAG, "Current data ID not found.");
324                                hidePanoStitchingProgress();
325                                return;
326                            }
327                            boolean isCameraID = currentData.getLocalDataType() ==
328                                    LocalData.LOCAL_CAMERA_PREVIEW;
329                            if (!current) {
330                                if (isCameraID) {
331                                    mCurrentModule.onPreviewFocusChanged(false);
332                                    setActionBarVisibilityAndLightsOut(false);
333                                }
334                                hidePanoStitchingProgress();
335                            } else {
336                                if (isCameraID) {
337                                    mCurrentModule.onPreviewFocusChanged(true);
338                                    // Don't show the action bar in Camera
339                                    // preview.
340                                    setActionBarVisibilityAndLightsOut(true);
341                                    if (mPendingDeletion) {
342                                        performDeletion();
343                                    }
344                                } else {
345                                    updateActionBarMenu(dataID);
346                                }
347
348                                Uri contentUri = currentData.getContentUri();
349                                if (contentUri == null) {
350                                    hidePanoStitchingProgress();
351                                    return;
352                                }
353                                int panoStitchingProgress = mPanoramaManager.getTaskProgress(
354                                        contentUri);
355                                if (panoStitchingProgress < 0) {
356                                    hidePanoStitchingProgress();
357                                    return;
358                                }
359                                showPanoStitchingProgress();
360                                updateStitchingProgress(panoStitchingProgress);
361                            }
362                        }
363                    });
364                }
365
366                @Override
367                public boolean onToggleActionBarVisibility(int dataID) {
368                    if (mActionBar.isShowing()) {
369                        setActionBarVisibilityAndLightsOut(true);
370                        return false;
371                    } else {
372                        // Don't show the action bar if that is the camera preview.
373                        boolean isCameraID = isCameraPreview(dataID);
374                        if (!isCameraID) {
375                            setActionBarVisibilityAndLightsOut(false);
376                            return true;
377                        }
378                        return false;
379                    }
380                }
381            };
382
383    public void gotoGallery() {
384        mFilmStripView.getController().goToNextItem();
385    }
386
387    /**
388     * If enabled, this hides the action bar and switches the system UI to
389     * lights-out mode.
390     */
391    private void setActionBarVisibilityAndLightsOut(boolean enabled) {
392        int visibility = DEFAULT_SYSTEM_UI_VISIBILITY | (enabled ? IMMERSIVE_FLAGS
393                : View.SYSTEM_UI_FLAG_VISIBLE);
394        mAboveFilmstripControlLayout
395                .setSystemUiVisibility(visibility);
396        if (enabled) {
397            mActionBar.hide();
398        } else {
399            mActionBar.show();
400        }
401    }
402
403    private void hidePanoStitchingProgress() {
404        mPanoStitchingPanel.setVisibility(View.GONE);
405    }
406
407    private void showPanoStitchingProgress() {
408        mPanoStitchingPanel.setVisibility(View.VISIBLE);
409    }
410
411    private void updateStitchingProgress(int progress) {
412        mBottomProgress.setProgress(progress);
413    }
414
415    private void setStandardShareIntent(Uri contentUri, String mimeType) {
416        mStandardShareIntent = getShareIntentFromType(mimeType);
417        if (mStandardShareIntent != null) {
418            mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
419            mStandardShareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
420            if (mStandardShareActionProvider != null) {
421                mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
422            }
423        }
424    }
425
426    /**
427     * Get the share intent according to the mimeType
428     *
429     * @param mimeType The mimeType of current data.
430     * @return the video/image's ShareIntent or null if mimeType is invalid.
431     */
432    private Intent getShareIntentFromType(String mimeType) {
433        // Lazily create the intent object.
434        if (mimeType.startsWith("video/")) {
435            if (mVideoShareIntent == null) {
436                mVideoShareIntent = new Intent(Intent.ACTION_SEND);
437                mVideoShareIntent.setType("video/*");
438            }
439            return mVideoShareIntent;
440        } else if (mimeType.startsWith("image/")) {
441            if (mImageShareIntent == null) {
442                mImageShareIntent = new Intent(Intent.ACTION_SEND);
443                mImageShareIntent.setType("image/*");
444            }
445            return mImageShareIntent;
446        }
447        Log.w(TAG, "unsupported mimeType " + mimeType);
448        return null;
449    }
450
451    private void setPanoramaShareIntent(Uri contentUri) {
452        if (mPanoramaShareIntent == null) {
453            mPanoramaShareIntent = new Intent(Intent.ACTION_SEND);
454        }
455        mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg");
456        mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
457        if (mPanoramaShareActionProvider != null) {
458            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
459        }
460    }
461
462    /**
463     * According to the data type, make the menu items for supported operations
464     * visible.
465     *
466     * @param dataID the data ID of the current item.
467     */
468    private void updateActionBarMenu(int dataID) {
469        LocalData currentData = mDataAdapter.getLocalData(dataID);
470        int type = currentData.getLocalDataType();
471
472        if (mActionBarMenu == null) {
473            return;
474        }
475
476        int supported = 0;
477        switch (type) {
478            case LocalData.LOCAL_IMAGE:
479                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
480                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
481                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
482                break;
483            case LocalData.LOCAL_VIDEO:
484                supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM
485                        | SUPPORT_SHARE;
486                break;
487            case LocalData.LOCAL_PHOTO_SPHERE:
488                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
489                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
490                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
491                break;
492            case LocalData.LOCAL_360_PHOTO_SPHERE:
493                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
494                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
495                        | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360
496                        | SUPPORT_SHOW_ON_MAP;
497                break;
498            default:
499                break;
500        }
501
502        setMenuItemVisible(mActionBarMenu, R.id.action_delete,
503                (supported & SUPPORT_DELETE) != 0);
504        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw,
505                (supported & SUPPORT_ROTATE) != 0);
506        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw,
507                (supported & SUPPORT_ROTATE) != 0);
508        setMenuItemVisible(mActionBarMenu, R.id.action_details,
509                (supported & SUPPORT_INFO) != 0);
510        setMenuItemVisible(mActionBarMenu, R.id.action_crop,
511                (supported & SUPPORT_CROP) != 0);
512        setMenuItemVisible(mActionBarMenu, R.id.action_setas,
513                (supported & SUPPORT_SETAS) != 0);
514        setMenuItemVisible(mActionBarMenu, R.id.action_edit,
515                (supported & SUPPORT_EDIT) != 0);
516        setMenuItemVisible(mActionBarMenu, R.id.action_trim,
517                (supported & SUPPORT_TRIM) != 0);
518
519        boolean standardShare = (supported & SUPPORT_SHARE) != 0;
520        boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0;
521        setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare);
522        setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare);
523
524        if (panoramaShare) {
525            // For 360 PhotoSphere, relegate standard share to the overflow menu
526            MenuItem item = mActionBarMenu.findItem(R.id.action_share);
527            if (item != null) {
528                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
529                item.setTitle(getResources().getString(R.string.share_as_photo));
530            }
531            // And, promote "share as panorama" to action bar
532            item = mActionBarMenu.findItem(R.id.action_share_panorama);
533            if (item != null) {
534                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
535            }
536            setPanoramaShareIntent(currentData.getContentUri());
537        }
538        if (standardShare) {
539            if (!panoramaShare) {
540                MenuItem item = mActionBarMenu.findItem(R.id.action_share);
541                if (item != null) {
542                    item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
543                    item.setTitle(getResources().getString(R.string.share));
544                }
545            }
546            setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType());
547        }
548
549        boolean itemHasLocation = currentData.getLatLong() != null;
550        setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map,
551                itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0);
552    }
553
554    private void setMenuItemVisible(Menu menu, int itemId, boolean visible) {
555        MenuItem item = menu.findItem(itemId);
556        if (item != null)
557            item.setVisible(visible);
558    }
559
560    private ImageTaskManager.TaskListener mStitchingListener =
561            new ImageTaskManager.TaskListener() {
562                @Override
563                public void onTaskQueued(String filePath, final Uri imageUri) {
564                    mMainHandler.post(new Runnable() {
565                        @Override
566                        public void run() {
567                            notifyNewMedia(imageUri);
568                        }
569                    });
570                }
571
572                @Override
573                public void onTaskDone(String filePath, final Uri imageUri) {
574                    Log.v(TAG, "onTaskDone:" + filePath);
575                    mMainHandler.post(new Runnable() {
576                        @Override
577                        public void run() {
578                            int doneID = mDataAdapter.findDataByContentUri(imageUri);
579                            int currentDataId = mFilmStripView.getCurrentId();
580
581                            if (currentDataId == doneID) {
582                                hidePanoStitchingProgress();
583                                updateStitchingProgress(0);
584                            }
585
586                            mDataAdapter.refresh(getContentResolver(), imageUri);
587                        }
588                    });
589                }
590
591                @Override
592                public void onTaskProgress(
593                        String filePath, final Uri imageUri, final int progress) {
594                    mMainHandler.post(new Runnable() {
595                        @Override
596                        public void run() {
597                            int currentDataId = mFilmStripView.getCurrentId();
598                            if (currentDataId == -1) {
599                                return;
600                            }
601                            if (imageUri.equals(
602                                    mDataAdapter.getLocalData(currentDataId).getContentUri())) {
603                                updateStitchingProgress(progress);
604                            }
605                        }
606                    });
607                }
608            };
609
610    public MediaSaveService getMediaSaveService() {
611        return mMediaSaveService;
612    }
613
614    public void notifyNewMedia(Uri uri) {
615        ContentResolver cr = getContentResolver();
616        String mimeType = cr.getType(uri);
617        if (mimeType.startsWith("video/")) {
618            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
619            mDataAdapter.addNewVideo(cr, uri);
620        } else if (mimeType.startsWith("image/")) {
621            CameraUtil.broadcastNewPicture(this, uri);
622            mDataAdapter.addNewPhoto(cr, uri);
623        } else if (mimeType.startsWith("application/stitching-preview")) {
624            mDataAdapter.addNewPhoto(cr, uri);
625        } else {
626            android.util.Log.w(TAG, "Unknown new media with MIME type:"
627                    + mimeType + ", uri:" + uri);
628        }
629    }
630
631    private void removeData(int dataID) {
632        mDataAdapter.removeData(CameraActivity.this, dataID);
633        if (mDataAdapter.getTotalNumber() > 1) {
634            showUndoDeletionBar();
635        } else {
636            // If camera preview is the only view left in filmstrip,
637            // no need to show undo bar.
638            performDeletion();
639        }
640    }
641
642    private void bindMediaSaveService() {
643        Intent intent = new Intent(this, MediaSaveService.class);
644        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
645    }
646
647    private void unbindMediaSaveService() {
648        if (mConnection != null) {
649            unbindService(mConnection);
650        }
651    }
652
653    @Override
654    public boolean onCreateOptionsMenu(Menu menu) {
655        // Inflate the menu items for use in the action bar
656        MenuInflater inflater = getMenuInflater();
657        inflater.inflate(R.menu.operations, menu);
658        mActionBarMenu = menu;
659
660        // Configure the standard share action provider
661        MenuItem item = menu.findItem(R.id.action_share);
662        mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider();
663        mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml");
664        if (mStandardShareIntent != null) {
665            mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
666        }
667
668        // Configure the panorama share action provider
669        item = menu.findItem(R.id.action_share_panorama);
670        mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider();
671        mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml");
672        if (mPanoramaShareIntent != null) {
673            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
674        }
675
676        return super.onCreateOptionsMenu(menu);
677    }
678
679    @Override
680    public boolean onOptionsItemSelected(MenuItem item) {
681        int currentDataId = mFilmStripView.getCurrentId();
682        if (currentDataId < 0) {
683            return false;
684        }
685        final LocalData localData = mDataAdapter.getLocalData(currentDataId);
686
687        // Handle presses on the action bar items
688        switch (item.getItemId()) {
689            case android.R.id.home:
690                // ActionBar's Up/Home button was clicked
691                mFilmStripView.getController().goToFirstItem();
692                return true;
693            case R.id.action_delete:
694                removeData(currentDataId);
695                return true;
696            case R.id.action_edit:
697                launchEditor(localData);
698                return true;
699            case R.id.action_trim: {
700                // This is going to be handled by the Gallery app.
701                Intent intent = new Intent(ACTION_TRIM_VIDEO);
702                LocalData currentData = mDataAdapter.getLocalData(
703                        mFilmStripView.getCurrentId());
704                intent.setData(currentData.getContentUri());
705                // We need the file path to wrap this into a RandomAccessFile.
706                intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath());
707                startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
708                return true;
709            }
710            case R.id.action_rotate_ccw:
711                localData.rotate90Degrees(this, mDataAdapter, currentDataId, false);
712                return true;
713            case R.id.action_rotate_cw:
714                localData.rotate90Degrees(this, mDataAdapter, currentDataId, true);
715                return true;
716            case R.id.action_crop:
717                // TODO: add the functionality.
718                return true;
719            case R.id.action_setas: {
720                Intent intent = new Intent(Intent.ACTION_ATTACH_DATA)
721                        .setDataAndType(localData.getContentUri(),
722                                localData.getMimeType())
723                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
724                intent.putExtra("mimeType", intent.getType());
725                startActivityForResult(Intent.createChooser(
726                        intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW);
727                return true;
728            }
729            case R.id.action_details:
730                (new AsyncTask<Void, Void, MediaDetails>() {
731                    @Override
732                    protected MediaDetails doInBackground(Void... params) {
733                        return localData.getMediaDetails(CameraActivity.this);
734                    }
735
736                    @Override
737                    protected void onPostExecute(MediaDetails mediaDetails) {
738                        DetailsDialog.create(CameraActivity.this, mediaDetails).show();
739                    }
740                }).execute();
741                return true;
742            case R.id.action_show_on_map:
743                double[] latLong = localData.getLatLong();
744                if (latLong != null) {
745                    CameraUtil.showOnMap(this, latLong);
746                }
747                return true;
748            default:
749                return super.onOptionsItemSelected(item);
750        }
751    }
752
753    private boolean isCaptureIntent() {
754        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
755                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
756                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
757            return true;
758        } else {
759            return false;
760        }
761    }
762
763    @Override
764    public void onCreate(Bundle state) {
765        super.onCreate(state);
766        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
767        setContentView(R.layout.camera_filmstrip);
768        mActionBar = getActionBar();
769
770        if (ApiHelper.HAS_ROTATION_ANIMATION) {
771            setRotationAnimation();
772        }
773        // Check if this is in the secure camera mode.
774        Intent intent = getIntent();
775        String action = intent.getAction();
776        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
777                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
778            mSecureCamera = true;
779        } else {
780            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
781        }
782
783        if (mSecureCamera) {
784            // Change the window flags so that secure camera can show when locked
785            Window win = getWindow();
786            WindowManager.LayoutParams params = win.getAttributes();
787            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
788            win.setAttributes(params);
789
790            // Filter for screen off so that we can finish secure camera activity
791            // when screen is off.
792            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
793            registerReceiver(mScreenOffReceiver, filter);
794            // TODO: This static screen off event receiver is a workaround to the
795            // double onResume() invocation (onResume->onPause->onResume). We should
796            // find a better solution to this.
797            if (sScreenOffReceiver == null) {
798                sScreenOffReceiver = new ScreenOffReceiver();
799                registerReceiver(sScreenOffReceiver, filter);
800            }
801        }
802        mAboveFilmstripControlLayout =
803                (FrameLayout) findViewById(R.id.camera_above_filmstrip_layout);
804        mAboveFilmstripControlLayout.setFitsSystemWindows(true);
805        // Hide action bar first since we are in full screen mode first, and
806        // switch the system UI to lights-out mode.
807        setActionBarVisibilityAndLightsOut(true);
808        mPanoramaManager = AppManagerFactory.getInstance(this)
809                .getPanoramaStitchingManager();
810        mPanoramaManager.addTaskListener(mStitchingListener);
811        LayoutInflater inflater = getLayoutInflater();
812        View rootLayout = inflater.inflate(R.layout.camera, null, false);
813        mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root);
814        mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
815        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
816        mCameraPreviewData = new CameraPreviewData(rootLayout,
817                FilmStripView.ImageData.SIZE_FULL,
818                FilmStripView.ImageData.SIZE_FULL);
819        // Put a CameraPreviewData at the first position.
820        mWrappedDataAdapter = new FixedFirstDataAdapter(
821                new CameraDataAdapter(new ColorDrawable(
822                        getResources().getColor(R.color.photo_placeholder))),
823                mCameraPreviewData);
824        mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view);
825        mFilmStripView.setViewGap(
826                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
827        mPanoramaViewHelper = new PanoramaViewHelper(this);
828        mPanoramaViewHelper.onCreate();
829        mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper);
830        // Set up the camera preview first so the preview shows up ASAP.
831        mFilmStripView.setListener(mFilmStripListener);
832
833        int moduleIndex = -1;
834        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
835                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
836            moduleIndex = ModuleSwitcher.VIDEO_MODULE_INDEX;
837        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
838                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
839                        .getAction())
840                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
841                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
842            moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
843        } else {
844            // If the activity has not been started using an explicit intent,
845            // read the module index from the last time the user changed modes
846            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
847            moduleIndex = prefs.getInt(PREF_STARTUP_MODULE_INDEX, -1);
848            if (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
1152            default:
1153                // Fall back to photo mode.
1154                mCurrentModule = new PhotoModule();
1155                mCurrentModuleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
1156                break;
1157        }
1158    }
1159
1160    /**
1161     * Launches an ACTION_EDIT intent for the given local data item.
1162     */
1163    public void launchEditor(LocalData data) {
1164        Intent intent = new Intent(Intent.ACTION_EDIT)
1165                .setDataAndType(data.getContentUri(), data.getMimeType())
1166                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1167        startActivityForResult(Intent.createChooser(intent, null), REQ_CODE_EDIT);
1168    }
1169
1170    /**
1171     * Launch the tiny planet editor.
1172     *
1173     * @param data the data must be a 360 degree stereographically mapped
1174     *            panoramic image. It will not be modified, instead a new item
1175     *            with the result will be added to the filmstrip.
1176     */
1177    public void launchTinyPlanetEditor(LocalData data) {
1178        TinyPlanetFragment fragment = new TinyPlanetFragment();
1179        Bundle bundle = new Bundle();
1180        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1181        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1182        fragment.setArguments(bundle);
1183        fragment.show(getFragmentManager(), "tiny_planet");
1184    }
1185
1186    private void openModule(CameraModule module) {
1187        module.init(this, mCameraModuleRootView);
1188        module.onResumeBeforeSuper();
1189        module.onResumeAfterSuper();
1190    }
1191
1192    private void closeModule(CameraModule module) {
1193        module.onPauseBeforeSuper();
1194        module.onPauseAfterSuper();
1195        ((ViewGroup) mCameraModuleRootView).removeAllViews();
1196    }
1197
1198    private void performDeletion() {
1199        if (!mPendingDeletion) {
1200            return;
1201        }
1202        hideUndoDeletionBar(false);
1203        mDataAdapter.executeDeletion(CameraActivity.this);
1204    }
1205
1206    public void showUndoDeletionBar() {
1207        if (mPendingDeletion) {
1208            performDeletion();
1209        }
1210        Log.v(TAG, "showing undo bar");
1211        mPendingDeletion = true;
1212        if (mUndoDeletionBar == null) {
1213            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(
1214                    R.layout.undo_bar, mAboveFilmstripControlLayout, true);
1215            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1216            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1217            button.setOnClickListener(new View.OnClickListener() {
1218                @Override
1219                public void onClick(View view) {
1220                    mDataAdapter.undoDataRemoval();
1221                    hideUndoDeletionBar(true);
1222                }
1223            });
1224            // Setting undo bar clickable to avoid touch events going through
1225            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1226            mUndoDeletionBar.setClickable(true);
1227            // When there is user interaction going on with the undo button, we
1228            // do not want to hide the undo bar.
1229            button.setOnTouchListener(new View.OnTouchListener() {
1230                @Override
1231                public boolean onTouch(View v, MotionEvent event) {
1232                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1233                        mIsUndoingDeletion = true;
1234                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1235                        mIsUndoingDeletion =false;
1236                    }
1237                    return false;
1238                }
1239            });
1240        }
1241        mUndoDeletionBar.setAlpha(0f);
1242        mUndoDeletionBar.setVisibility(View.VISIBLE);
1243        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1244    }
1245
1246    private void hideUndoDeletionBar(boolean withAnimation) {
1247        Log.v(TAG, "Hiding undo deletion bar");
1248        mPendingDeletion = false;
1249        if (mUndoDeletionBar != null) {
1250            if (withAnimation) {
1251                mUndoDeletionBar.animate()
1252                        .setDuration(200)
1253                        .alpha(0f)
1254                        .setListener(new Animator.AnimatorListener() {
1255                            @Override
1256                            public void onAnimationStart(Animator animation) {
1257                                // Do nothing.
1258                            }
1259
1260                            @Override
1261                            public void onAnimationEnd(Animator animation) {
1262                                mUndoDeletionBar.setVisibility(View.GONE);
1263                            }
1264
1265                            @Override
1266                            public void onAnimationCancel(Animator animation) {
1267                                // Do nothing.
1268                            }
1269
1270                            @Override
1271                            public void onAnimationRepeat(Animator animation) {
1272                                // Do nothing.
1273                            }
1274                        })
1275                        .start();
1276            } else {
1277                mUndoDeletionBar.setVisibility(View.GONE);
1278            }
1279        }
1280    }
1281
1282    @Override
1283    public void onShowSwitcherPopup() {
1284    }
1285
1286    /**
1287     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1288     * capture intent.
1289     *
1290     * @param enable {@code true} to enable swipe.
1291     */
1292    public void setSwipingEnabled(boolean enable) {
1293        if (isCaptureIntent()) {
1294            mCameraPreviewData.lockPreview(true);
1295        } else {
1296            mCameraPreviewData.lockPreview(!enable);
1297        }
1298    }
1299
1300    // Accessor methods for getting latency times used in performance testing
1301    public long getAutoFocusTime() {
1302        return (mCurrentModule instanceof PhotoModule) ?
1303                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1304    }
1305
1306    public long getShutterLag() {
1307        return (mCurrentModule instanceof PhotoModule) ?
1308                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1309    }
1310
1311    public long getShutterToPictureDisplayedTime() {
1312        return (mCurrentModule instanceof PhotoModule) ?
1313                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1314    }
1315
1316    public long getPictureDisplayedToJpegCallbackTime() {
1317        return (mCurrentModule instanceof PhotoModule) ?
1318                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1319    }
1320
1321    public long getJpegCallbackFinishTime() {
1322        return (mCurrentModule instanceof PhotoModule) ?
1323                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1324    }
1325
1326    public long getCaptureStartTime() {
1327        return (mCurrentModule instanceof PhotoModule) ?
1328                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1329    }
1330
1331    public boolean isRecording() {
1332        return (mCurrentModule instanceof VideoModule) ?
1333                ((VideoModule) mCurrentModule).isRecording() : false;
1334    }
1335
1336    public CameraOpenErrorCallback getCameraOpenErrorCallback() {
1337        return mCameraOpenErrorCallback;
1338    }
1339}
1340