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