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