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