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