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