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