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