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