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