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