CameraActivity.java revision b2510252fb020f3ccb88787c870930427377b9df
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.annotation.TargetApi;
21import android.app.ActionBar;
22import android.app.Activity;
23import android.app.AlertDialog;
24import android.content.ActivityNotFoundException;
25import android.content.BroadcastReceiver;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.SharedPreferences;
31import android.content.pm.ActivityInfo;
32import android.content.res.Configuration;
33import android.graphics.Bitmap;
34import android.graphics.Color;
35import android.graphics.SurfaceTexture;
36import android.graphics.drawable.ColorDrawable;
37import android.net.Uri;
38import android.nfc.NfcAdapter;
39import android.nfc.NfcAdapter.CreateBeamUrisCallback;
40import android.nfc.NfcEvent;
41import android.os.AsyncTask;
42import android.os.Build;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.Looper;
46import android.os.Message;
47import android.preference.PreferenceManager;
48import android.provider.MediaStore;
49import android.provider.Settings;
50import android.util.Log;
51import android.view.Gravity;
52import android.view.KeyEvent;
53import android.view.LayoutInflater;
54import android.view.Menu;
55import android.view.MenuInflater;
56import android.view.MenuItem;
57import android.view.MotionEvent;
58import android.view.View;
59import android.view.ViewGroup;
60import android.view.Window;
61import android.view.WindowManager;
62import android.widget.FrameLayout;
63import android.widget.FrameLayout.LayoutParams;
64import android.widget.ImageView;
65import android.widget.PopupWindow;
66import android.widget.ProgressBar;
67import android.widget.ShareActionProvider;
68
69import com.android.camera.app.AppController;
70import com.android.camera.app.AppManagerFactory;
71import com.android.camera.app.CameraAppUI;
72import com.android.camera.app.CameraController;
73import com.android.camera.app.CameraManager;
74import com.android.camera.app.CameraManagerFactory;
75import com.android.camera.app.CameraProvider;
76import com.android.camera.app.CameraServices;
77import com.android.camera.app.ImageTaskManager;
78import com.android.camera.app.MediaSaver;
79import com.android.camera.app.ModuleManagerImpl;
80import com.android.camera.app.OrientationManager;
81import com.android.camera.app.OrientationManagerImpl;
82import com.android.camera.app.PanoramaStitchingManager;
83import com.android.camera.app.PlaceholderManager;
84import com.android.camera.crop.CropActivity;
85import com.android.camera.data.CameraDataAdapter;
86import com.android.camera.data.FixedLastDataAdapter;
87import com.android.camera.data.InProgressDataWrapper;
88import com.android.camera.data.LocalData;
89import com.android.camera.data.LocalDataAdapter;
90import com.android.camera.data.LocalMediaObserver;
91import com.android.camera.data.MediaDetails;
92import com.android.camera.data.SimpleViewData;
93import com.android.camera.filmstrip.FilmstripContentPanel;
94import com.android.camera.filmstrip.FilmstripController;
95import com.android.camera.module.ModulesInfo;
96import com.android.camera.settings.SettingsManager;
97import com.android.camera.settings.SettingsManager.SettingsCapabilities;
98import com.android.camera.tinyplanet.TinyPlanetFragment;
99import com.android.camera.ui.DetailsDialog;
100import com.android.camera.widget.FilmstripView;
101import com.android.camera.ui.MainActivityLayout;
102import com.android.camera.ui.ModeListView;
103import com.android.camera.ui.PreviewStatusListener;
104import com.android.camera.ui.SettingsView;
105import com.android.camera.util.ApiHelper;
106import com.android.camera.util.CameraUtil;
107import com.android.camera.util.FeedbackHelper;
108import com.android.camera.util.GcamHelper;
109import com.android.camera.util.IntentHelper;
110import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
111import com.android.camera.util.UsageStatistics;
112import com.android.camera2.R;
113
114import java.io.File;
115
116public class CameraActivity extends Activity
117        implements AppController, CameraManager.CameraOpenCallback,
118        ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
119        OrientationManager.OnOrientationChangeListener {
120
121    private static final String TAG = "CAM_Activity";
122
123    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
124            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
125    public static final String ACTION_IMAGE_CAPTURE_SECURE =
126            "android.media.action.IMAGE_CAPTURE_SECURE";
127    public static final String ACTION_TRIM_VIDEO =
128            "com.android.camera.action.TRIM";
129    public static final String MEDIA_ITEM_PATH = "media-item-path";
130
131    // The intent extra for camera from secure lock screen. True if the gallery
132    // should only show newly captured pictures. sSecureAlbumId does not
133    // increment. This is used when switching between camera, camcorder, and
134    // panorama. If the extra is not set, it is in the normal camera mode.
135    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
136
137    /**
138     * Request code from an activity we started that indicated that we do not want
139     * to reset the view to the preview in onResume.
140     */
141    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
142
143    public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
144
145    private static final int MSG_HIDE_ACTION_BAR = 1;
146    private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
147    private static final long SCREEN_DELAY_MS = 2 * 60 * 1000;  // 2 mins.
148
149    /**
150     * Whether onResume should reset the view to the preview.
151     */
152    private boolean mResetToPreviewOnResume = true;
153
154    // Supported operations at FilmStripView. Different data has different
155    // set of supported operations.
156    private static final int SUPPORT_DELETE = 1 << 0;
157    private static final int SUPPORT_ROTATE = 1 << 1;
158    private static final int SUPPORT_INFO = 1 << 2;
159    private static final int SUPPORT_CROP = 1 << 3;
160    private static final int SUPPORT_SETAS = 1 << 4;
161    private static final int SUPPORT_EDIT = 1 << 5;
162    private static final int SUPPORT_TRIM = 1 << 6;
163    private static final int SUPPORT_SHARE = 1 << 7;
164    private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8;
165    private static final int SUPPORT_SHOW_ON_MAP = 1 << 9;
166    private static final int SUPPORT_ALL = 0xffffffff;
167
168    /**
169     * This data adapter is used by FilmStripView.
170     */
171    private LocalDataAdapter mDataAdapter;
172
173    private SettingsManager mSettingsManager;
174    private SettingsController mSettingsController;
175    private PanoramaStitchingManager mPanoramaManager;
176    private PlaceholderManager mPlaceholderManager;
177    private ModeListView mModeListView;
178    private int mCurrentModeIndex;
179    private CameraModule mCurrentModule;
180    private ModuleManagerImpl mModuleManager;
181    private FrameLayout mAboveFilmstripControlLayout;
182    private FilmstripController mFilmstripController;
183    private boolean mFilmstripVisible;
184    private ProgressBar mBottomProgress;
185    private View mPanoStitchingPanel;
186    private int mResultCodeForTesting;
187    private Intent mResultDataForTesting;
188    private OnScreenHint mStorageHint;
189    private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
190    private boolean mAutoRotateScreen;
191    private boolean mSecureCamera;
192    private int mLastRawOrientation;
193    private OrientationManagerImpl mOrientationManager;
194    private LocationManager mLocationManager;
195    private ButtonManager mButtonManager;
196    private Handler mMainHandler;
197    private PanoramaViewHelper mPanoramaViewHelper;
198    private ActionBar mActionBar;
199    private Menu mActionBarMenu;
200    private ViewGroup mUndoDeletionBar;
201    private boolean mIsUndoingDeletion = false;
202
203    private final Uri[] mNfcPushUris = new Uri[1];
204
205    private ShareActionProvider mStandardShareActionProvider;
206    private Intent mStandardShareIntent;
207    private ShareActionProvider mPanoramaShareActionProvider;
208    private Intent mPanoramaShareIntent;
209    private LocalMediaObserver mLocalImagesObserver;
210    private LocalMediaObserver mLocalVideosObserver;
211
212    private boolean mPendingDeletion = false;
213
214    private Intent mVideoShareIntent;
215    private Intent mImageShareIntent;
216
217    private CameraController mCameraController;
218    private boolean mPaused;
219    private CameraAppUI mCameraAppUI;
220
221    private MediaSaver mMediaSaver;
222
223    private FeedbackHelper mFeedbackHelper;
224
225    // close activity when screen turns off
226    private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
227        @Override
228        public void onReceive(Context context, Intent intent) {
229            finish();
230        }
231    };
232
233    private static BroadcastReceiver sScreenOffReceiver;
234
235    /**
236     * Whether the screen is kept turned on.
237     */
238    private boolean mKeepScreenOn;
239    private int mLastLayoutOrientation;
240    private CameraAppUI.BottomControls.Listener mMyFilmstripBottomControlListener =
241            new CameraAppUI.BottomControls.Listener() {
242
243                /**
244                 * If the current photo is a photo sphere, this will launch the Photo Sphere
245                 * panorama viewer.
246                 */
247                @Override
248                public void onView() {
249                    LocalData data = getCurrentLocalData();
250                    if (data != null) {
251                        data.view(mPanoramaViewHelper);
252                    }
253                }
254
255                @Override
256                public void onEdit() {
257                    LocalData data = getCurrentLocalData();
258                    if (data == null) {
259                        return;
260                    }
261                    launchEditor(data);
262                }
263
264                @Override
265                public void onTinyPlanet() {
266                    LocalData data = getCurrentLocalData();
267                    if (data == null) {
268                        return;
269                    }
270                    launchTinyPlanetEditor(data);
271                }
272
273                private LocalData getCurrentLocalData() {
274                    return mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
275                }
276            };
277
278    @Override
279    public void onCameraOpened(CameraManager.CameraProxy camera) {
280        if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
281            // We shouldn't be here. Just close the camera and leave.
282            camera.release(false);
283            throw new IllegalStateException("Camera opened but the module shouldn't be " +
284                    "requesting");
285        }
286        if (mCurrentModule != null) {
287            SettingsCapabilities capabilities =
288                SettingsController.getSettingsCapabilities(camera);
289            mSettingsManager.changeCamera(camera.getCameraId(), capabilities);
290            mCurrentModule.onCameraAvailable(camera);
291        }
292    }
293
294    @Override
295    public void onCameraDisabled(int cameraId) {
296        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_OPEN_FAIL,
297                "security");
298
299        CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
300    }
301
302    @Override
303    public void onDeviceOpenFailure(int cameraId) {
304        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
305                UsageStatistics.ACTION_OPEN_FAIL, "open");
306
307        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
308    }
309
310    @Override
311    public void onReconnectionFailure(CameraManager mgr) {
312        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
313                UsageStatistics.ACTION_OPEN_FAIL, "reconnect");
314
315        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
316    }
317
318    private class MainHandler extends Handler {
319        public MainHandler(Looper looper) {
320            super(looper);
321        }
322
323        @Override
324        public void handleMessage(Message msg) {
325            switch (msg.what) {
326                case MSG_HIDE_ACTION_BAR: {
327                    removeMessages(MSG_HIDE_ACTION_BAR);
328                    CameraActivity.this.setFilmstripUiVisibility(false);
329                    break;
330                }
331
332                case MSG_CLEAR_SCREEN_ON_FLAG:  {
333                    if (!mPaused) {
334                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
335                    }
336                    break;
337                }
338
339                default:
340            }
341        }
342    }
343
344    private String fileNameFromDataID(int dataID) {
345        final LocalData localData = mDataAdapter.getLocalData(dataID);
346
347        File localFile = new File(localData.getPath());
348        return localFile.getName();
349    }
350
351    private final FilmstripContentPanel.Listener mFilmstripListener =
352            new FilmstripContentPanel.Listener() {
353
354                @Override
355                public void onFilmstripHidden() {
356                    mFilmstripVisible = false;
357                    CameraActivity.this.setFilmstripUiVisibility(false);
358                    // When the user hide the filmstrip (either swipe out or
359                    // tap on back key) we move to the first item so next time
360                    // when the user swipe in the filmstrip, the most recent
361                    // one is shown.
362                    mFilmstripController.goToFirstItem();
363                    if (mCurrentModule != null) {
364                        mCurrentModule.onPreviewVisibilityChanged(true);
365                    }
366                }
367
368                @Override
369                public void onFilmstripShown() {
370                    mFilmstripVisible = true;
371                    updateUiByData(mFilmstripController.getCurrentId());
372                    if (mCurrentModule != null) {
373                        mCurrentModule.onPreviewVisibilityChanged(false);
374                    }
375                }
376
377                @Override
378                public void onDataPromoted(int dataID) {
379                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
380                            UsageStatistics.ACTION_DELETE, "promoted", 0,
381                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)));
382
383                    removeData(dataID);
384                }
385
386                @Override
387                public void onDataDemoted(int dataID) {
388                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
389                            UsageStatistics.ACTION_DELETE, "demoted", 0,
390                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)));
391
392                    removeData(dataID);
393                }
394
395                @Override
396                public void onEnterFullScreen(int dataId) {
397                    if (mFilmstripVisible) {
398                        CameraActivity.this.setFilmstripUiVisibility(false);
399                    }
400                }
401
402                @Override
403                public void onLeaveFullScreen(int dataId) {
404                    // Do nothing.
405                }
406
407                @Override
408                public void onEnterFilmstrip(int dataId) {
409                    if (mFilmstripVisible) {
410                        CameraActivity.this.setFilmstripUiVisibility(true);
411                    }
412                }
413
414                @Override
415                public void onLeaveFilmstrip(int dataId) {
416                    // Do nothing.
417                }
418
419                @Override
420                public void onDataReloaded() {
421                    if (!mFilmstripVisible) {
422                        return;
423                    }
424                    updateUiByData(mFilmstripController.getCurrentId());
425                }
426
427                @Override
428                public void onEnterZoomView(int dataID) {
429                    if (mFilmstripVisible) {
430                        CameraActivity.this.setFilmstripUiVisibility(false);
431                    }
432                }
433
434                @Override
435                public void onDataFocusChanged(final int prevDataId, final int newDataId) {
436                    if (!mFilmstripVisible) {
437                        return;
438                    }
439                    // TODO: This callback is UI event callback, should always
440                    // happen on UI thread. Find the reason for this
441                    // runOnUiThread() and fix it.
442                    runOnUiThread(new Runnable() {
443                        @Override
444                        public void run() {
445                            updateUiByData(newDataId);
446                        }
447                    });
448                }
449
450            };
451
452    public void gotoGallery() {
453        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_FILMSTRIP,
454                "thumbnailTap");
455
456        mFilmstripController.goToNextItem();
457    }
458
459    /**
460     * If {@param visible} is false, this hides the action bar and switches the
461     * filmstrip UI to lights-out mode.
462     */
463    // TODO: This should not be called outside of the activity.
464    public void setFilmstripUiVisibility(boolean visible) {
465        mMainHandler.removeMessages(MSG_HIDE_ACTION_BAR);
466
467        int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
468        int newSystemUIVisibility = (visible ? View.SYSTEM_UI_FLAG_VISIBLE :
469                        View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN);
470        if (newSystemUIVisibility != currentSystemUIVisibility) {
471            mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
472        }
473
474        boolean currentActionBarVisibility = mActionBar.isShowing();
475        mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
476        if (visible != currentActionBarVisibility) {
477            if (visible) {
478                mActionBar.show();
479            } else {
480                mActionBar.hide();
481            }
482        }
483    }
484
485    private void hidePanoStitchingProgress() {
486        mPanoStitchingPanel.setVisibility(View.GONE);
487    }
488
489    private void showPanoStitchingProgress() {
490        mPanoStitchingPanel.setVisibility(View.VISIBLE);
491    }
492
493    private void updateStitchingProgress(int progress) {
494        mBottomProgress.setProgress(progress);
495    }
496
497    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
498    private void setupNfcBeamPush() {
499        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(CameraActivity.this);
500        if (adapter == null) {
501            return;
502        }
503
504        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
505            // Disable beaming
506            adapter.setNdefPushMessage(null, CameraActivity.this);
507            return;
508        }
509
510        adapter.setBeamPushUris(null, CameraActivity.this);
511        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
512            @Override
513            public Uri[] createBeamUris(NfcEvent event) {
514                return mNfcPushUris;
515            }
516        }, CameraActivity.this);
517    }
518
519    private void setNfcBeamPushUri(Uri uri) {
520        mNfcPushUris[0] = uri;
521    }
522
523    private void setStandardShareIntent(Uri contentUri, String mimeType) {
524        mStandardShareIntent = getShareIntentFromType(mimeType);
525        if (mStandardShareIntent != null) {
526            mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
527            mStandardShareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
528            if (mStandardShareActionProvider != null) {
529                mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
530            }
531        }
532    }
533
534    /**
535     * Get the share intent according to the mimeType
536     *
537     * @param mimeType The mimeType of current data.
538     * @return the video/image's ShareIntent or null if mimeType is invalid.
539     */
540    private Intent getShareIntentFromType(String mimeType) {
541        // Lazily create the intent object.
542        if (mimeType.startsWith("video/")) {
543            if (mVideoShareIntent == null) {
544                mVideoShareIntent = new Intent(Intent.ACTION_SEND);
545                mVideoShareIntent.setType("video/*");
546            }
547            return mVideoShareIntent;
548        } else if (mimeType.startsWith("image/")) {
549            if (mImageShareIntent == null) {
550                mImageShareIntent = new Intent(Intent.ACTION_SEND);
551                mImageShareIntent.setType("image/*");
552            }
553            return mImageShareIntent;
554        }
555        Log.w(TAG, "unsupported mimeType " + mimeType);
556        return null;
557    }
558
559    private void setPanoramaShareIntent(Uri contentUri) {
560        if (mPanoramaShareIntent == null) {
561            mPanoramaShareIntent = new Intent(Intent.ACTION_SEND);
562        }
563        mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg");
564        mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
565        if (mPanoramaShareActionProvider != null) {
566            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
567        }
568    }
569
570    @Override
571    public void onMenuVisibilityChanged(boolean isVisible) {
572        // TODO: Remove this or bring back the original implementation: cancel
573        // auto-hide actionbar.
574    }
575
576    @Override
577    public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
578        int currentDataId = mFilmstripController.getCurrentId();
579        if (currentDataId < 0) {
580            return false;
581        }
582        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_SHARE,
583                intent.getComponent().getPackageName(), 0,
584                UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
585        return true;
586    }
587
588    /**
589     * According to the data type, make the menu items for supported operations
590     * visible.
591     *
592     * @param dataID the data ID of the current item.
593     */
594    private void updateActionBarMenu(int dataID) {
595        LocalData currentData = mDataAdapter.getLocalData(dataID);
596        if (currentData == null) {
597            return;
598        }
599        int type = currentData.getLocalDataType();
600
601        if (mActionBarMenu == null) {
602            return;
603        }
604
605        int supported = 0;
606
607        switch (type) {
608            case LocalData.LOCAL_IMAGE:
609                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
610                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
611                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
612                break;
613            case LocalData.LOCAL_VIDEO:
614                supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM
615                        | SUPPORT_SHARE;
616                break;
617            case LocalData.LOCAL_PHOTO_SPHERE:
618                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
619                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
620                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
621                break;
622            case LocalData.LOCAL_360_PHOTO_SPHERE:
623                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
624                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
625                        | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360
626                        | SUPPORT_SHOW_ON_MAP;
627                break;
628            default:
629                break;
630        }
631
632        // In secure camera mode, we only support delete operation.
633        if (isSecureCamera()) {
634            supported &= SUPPORT_DELETE;
635        }
636
637        setMenuItemVisible(mActionBarMenu, R.id.action_delete,
638                (supported & SUPPORT_DELETE) != 0);
639        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw,
640                (supported & SUPPORT_ROTATE) != 0);
641        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw,
642                (supported & SUPPORT_ROTATE) != 0);
643        setMenuItemVisible(mActionBarMenu, R.id.action_details,
644                (supported & SUPPORT_INFO) != 0);
645        setMenuItemVisible(mActionBarMenu, R.id.action_crop,
646                (supported & SUPPORT_CROP) != 0);
647        setMenuItemVisible(mActionBarMenu, R.id.action_setas,
648                (supported & SUPPORT_SETAS) != 0);
649        setMenuItemVisible(mActionBarMenu, R.id.action_edit,
650                (supported & SUPPORT_EDIT) != 0);
651        setMenuItemVisible(mActionBarMenu, R.id.action_trim,
652                (supported & SUPPORT_TRIM) != 0);
653
654        boolean standardShare = (supported & SUPPORT_SHARE) != 0;
655        boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0;
656        setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare);
657        setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare);
658
659        if (panoramaShare) {
660            // For 360 PhotoSphere, relegate standard share to the overflow menu
661            MenuItem item = mActionBarMenu.findItem(R.id.action_share);
662            if (item != null) {
663                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
664                item.setTitle(getResources().getString(R.string.share_as_photo));
665            }
666            // And, promote "share as panorama" to action bar
667            item = mActionBarMenu.findItem(R.id.action_share_panorama);
668            if (item != null) {
669                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
670            }
671            setPanoramaShareIntent(currentData.getContentUri());
672        }
673        if (standardShare) {
674            if (!panoramaShare) {
675                MenuItem item = mActionBarMenu.findItem(R.id.action_share);
676                if (item != null) {
677                    item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
678                    item.setTitle(getResources().getString(R.string.share));
679                }
680            }
681            setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType());
682            setNfcBeamPushUri(currentData.getContentUri());
683        }
684
685        boolean itemHasLocation = currentData.getLatLong() != null;
686        setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map,
687                itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0);
688    }
689
690    private void setMenuItemVisible(Menu menu, int itemId, boolean visible) {
691        MenuItem item = menu.findItem(itemId);
692        if (item != null) {
693            item.setVisible(visible);
694        }
695    }
696
697    private final ImageTaskManager.TaskListener mPlaceholderListener =
698            new ImageTaskManager.TaskListener() {
699
700                @Override
701                public void onTaskQueued(String filePath, final Uri imageUri) {
702                    mMainHandler.post(new Runnable() {
703                        @Override
704                        public void run() {
705                            notifyNewMedia(imageUri);
706                            int dataID = mDataAdapter.findDataByContentUri(imageUri);
707                            if (dataID != -1) {
708                                LocalData d = mDataAdapter.getLocalData(dataID);
709                                InProgressDataWrapper newData = new InProgressDataWrapper(d, true);
710                                mDataAdapter.updateData(dataID, newData);
711                            }
712                        }
713                    });
714                }
715
716                @Override
717                public void onTaskDone(String filePath, final Uri imageUri) {
718                    mMainHandler.post(new Runnable() {
719                        @Override
720                        public void run() {
721                            mDataAdapter.refresh(getContentResolver(), imageUri);
722                        }
723                    });
724                }
725
726                @Override
727                public void onTaskProgress(String filePath, Uri imageUri, int progress) {
728                    // Do nothing
729                }
730            };
731
732    private final ImageTaskManager.TaskListener mStitchingListener =
733            new ImageTaskManager.TaskListener() {
734                @Override
735                public void onTaskQueued(String filePath, final Uri imageUri) {
736                    mMainHandler.post(new Runnable() {
737                        @Override
738                        public void run() {
739                            notifyNewMedia(imageUri);
740                            int dataID = mDataAdapter.findDataByContentUri(imageUri);
741                            if (dataID != -1) {
742                                // Don't allow special UI actions (swipe to
743                                // delete, for example) on in-progress data.
744                                LocalData d = mDataAdapter.getLocalData(dataID);
745                                InProgressDataWrapper newData = new InProgressDataWrapper(d);
746                                mDataAdapter.updateData(dataID, newData);
747                            }
748                        }
749                    });
750                }
751
752                @Override
753                public void onTaskDone(String filePath, final Uri imageUri) {
754                    Log.v(TAG, "onTaskDone:" + filePath);
755                    mMainHandler.post(new Runnable() {
756                        @Override
757                        public void run() {
758                            int doneID = mDataAdapter.findDataByContentUri(imageUri);
759                            int currentDataId = mFilmstripController.getCurrentId();
760
761                            if (currentDataId == doneID) {
762                                hidePanoStitchingProgress();
763                                updateStitchingProgress(0);
764                            }
765
766                            mDataAdapter.refresh(getContentResolver(), imageUri);
767                        }
768                    });
769                }
770
771                @Override
772                public void onTaskProgress(
773                        String filePath, final Uri imageUri, final int progress) {
774                    mMainHandler.post(new Runnable() {
775                        @Override
776                        public void run() {
777                            int currentDataId = mFilmstripController.getCurrentId();
778                            if (currentDataId == -1) {
779                                return;
780                            }
781                            if (imageUri.equals(
782                                    mDataAdapter.getLocalData(currentDataId).getContentUri())) {
783                                updateStitchingProgress(progress);
784                            }
785                        }
786                    });
787                }
788            };
789
790    @Override
791    public Context getAndroidContext() {
792        return this;
793    }
794
795    @Override
796    public int getCurrentModuleIndex() {
797        return mCurrentModeIndex;
798    }
799
800    @Override
801    public SurfaceTexture getPreviewBuffer() {
802        // TODO: implement this
803        return null;
804    }
805
806    @Override
807    public void onPreviewStarted() {
808        mCameraAppUI.onPreviewStarted();
809    }
810
811    @Override
812    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
813        mCameraAppUI.setPreviewStatusListener(previewStatusListener);
814    }
815
816    @Override
817    public FrameLayout getModuleLayoutRoot() {
818        return mCameraAppUI.getModuleRootView();
819    }
820
821    @Override
822    public void setShutterEventsListener(ShutterEventsListener listener) {
823        // TODO: implement this
824    }
825
826    @Override
827    public void setShutterEnabled(boolean enabled) {
828        // TODO: implement this
829    }
830
831    @Override
832    public boolean isShutterEnabled() {
833        // TODO: implement this
834        return false;
835    }
836
837    @Override
838    public void startPreCaptureAnimation() {
839        // TODO: implement this
840    }
841
842    @Override
843    public void cancelPreCaptureAnimation() {
844        // TODO: implement this
845    }
846
847    @Override
848    public void startPostCaptureAnimation() {
849        // TODO: implement this
850    }
851
852    @Override
853    public void startPostCaptureAnimation(Bitmap thumbnail) {
854        // TODO: implement this
855    }
856
857    @Override
858    public void cancelPostCaptureAnimation() {
859        // TODO: implement this
860    }
861
862    @Override
863    public OrientationManager getOrientationManager() {
864        return mOrientationManager;
865    }
866
867    @Override
868    public LocationManager getLocationManager() {
869        return mLocationManager;
870    }
871
872    @Override
873    public void lockOrientation() {
874        mOrientationManager.lockOrientation();
875    }
876
877    @Override
878    public void unlockOrientation() {
879        mOrientationManager.unlockOrientation();
880    }
881
882    @Override
883    public void notifyNewMedia(Uri uri) {
884        ContentResolver cr = getContentResolver();
885        String mimeType = cr.getType(uri);
886        if (mimeType.startsWith("video/")) {
887            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
888            mDataAdapter.addNewVideo(cr, uri);
889        } else if (mimeType.startsWith("image/")) {
890            CameraUtil.broadcastNewPicture(this, uri);
891            mDataAdapter.addNewPhoto(cr, uri);
892        } else if (mimeType.startsWith("application/stitching-preview")) {
893            mDataAdapter.addNewPhoto(cr, uri);
894        } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) {
895            mDataAdapter.addNewPhoto(cr, uri);
896        } else {
897            android.util.Log.w(TAG, "Unknown new media with MIME type:"
898                    + mimeType + ", uri:" + uri);
899        }
900    }
901
902    @Override
903    public void enableKeepScreenOn(boolean enabled) {
904        if (mPaused) {
905            return;
906        }
907
908        mKeepScreenOn = enabled;
909        if (mKeepScreenOn) {
910            mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
911            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
912        } else {
913            keepScreenOnForAWhile();
914        }
915    }
916
917    @Override
918    public CameraProvider getCameraProvider() {
919        return mCameraController;
920    }
921
922    private void removeData(int dataID) {
923        mDataAdapter.removeData(CameraActivity.this, dataID);
924        if (mDataAdapter.getTotalNumber() > 1) {
925            showUndoDeletionBar();
926        } else {
927            // If camera preview is the only view left in filmstrip,
928            // no need to show undo bar.
929            mPendingDeletion = true;
930            performDeletion();
931        }
932    }
933
934
935    @Override
936    public boolean onCreateOptionsMenu(Menu menu) {
937        // Inflate the menu items for use in the action bar
938        MenuInflater inflater = getMenuInflater();
939        inflater.inflate(R.menu.operations, menu);
940        mActionBarMenu = menu;
941
942        // Configure the standard share action provider
943        MenuItem item = menu.findItem(R.id.action_share);
944        mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider();
945        mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml");
946        if (mStandardShareIntent != null) {
947            mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
948        }
949
950        // Configure the panorama share action provider
951        item = menu.findItem(R.id.action_share_panorama);
952        mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider();
953        mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml");
954        if (mPanoramaShareIntent != null) {
955            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
956        }
957
958        mStandardShareActionProvider.setOnShareTargetSelectedListener(this);
959        mPanoramaShareActionProvider.setOnShareTargetSelectedListener(this);
960
961        return super.onCreateOptionsMenu(menu);
962    }
963
964    @Override
965    public boolean onOptionsItemSelected(MenuItem item) {
966        int currentDataId = mFilmstripController.getCurrentId();
967        if (currentDataId < 0) {
968            return false;
969        }
970        final LocalData localData = mDataAdapter.getLocalData(currentDataId);
971
972        // Handle presses on the action bar items
973        switch (item.getItemId()) {
974            case android.R.id.home:
975                onBackPressed();
976                return true;
977            case R.id.action_delete:
978                UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
979                        UsageStatistics.ACTION_DELETE, null, 0,
980                        UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
981                removeData(currentDataId);
982                return true;
983            case R.id.action_edit:
984                UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
985                        UsageStatistics.ACTION_EDIT, null, 0,
986                        UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
987                launchEditor(localData);
988                return true;
989            case R.id.action_trim: {
990                // This is going to be handled by the Gallery app.
991                Intent intent = new Intent(ACTION_TRIM_VIDEO);
992                LocalData currentData = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
993                intent.setData(currentData.getContentUri());
994                // We need the file path to wrap this into a RandomAccessFile.
995                intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath());
996                startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
997                return true;
998            }
999            case R.id.action_rotate_ccw:
1000                localData.rotate90Degrees(this, mDataAdapter, currentDataId, false);
1001                return true;
1002            case R.id.action_rotate_cw:
1003                localData.rotate90Degrees(this, mDataAdapter, currentDataId, true);
1004                return true;
1005            case R.id.action_crop: {
1006                UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1007                        UsageStatistics.ACTION_CROP, null, 0,
1008                        UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
1009                Intent intent = new Intent(CropActivity.CROP_ACTION);
1010                intent.setClass(this, CropActivity.class);
1011                intent.setDataAndType(localData.getContentUri(), localData.getMimeType())
1012                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1013                startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1014                return true;
1015            }
1016            case R.id.action_setas: {
1017                Intent intent = new Intent(Intent.ACTION_ATTACH_DATA)
1018                        .setDataAndType(localData.getContentUri(),
1019                                localData.getMimeType())
1020                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1021                intent.putExtra("mimeType", intent.getType());
1022                startActivityForResult(Intent.createChooser(
1023                        intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1024                return true;
1025            }
1026            case R.id.action_details:
1027                (new AsyncTask<Void, Void, MediaDetails>() {
1028                    @Override
1029                    protected MediaDetails doInBackground(Void... params) {
1030                        return localData.getMediaDetails(CameraActivity.this);
1031                    }
1032
1033                    @Override
1034                    protected void onPostExecute(MediaDetails mediaDetails) {
1035                        if (mediaDetails != null) {
1036                            DetailsDialog.create(CameraActivity.this, mediaDetails).show();
1037                        }
1038                    }
1039                }).execute();
1040                return true;
1041            case R.id.action_show_on_map:
1042                double[] latLong = localData.getLatLong();
1043                if (latLong != null) {
1044                    CameraUtil.showOnMap(this, latLong);
1045                }
1046                return true;
1047            default:
1048                return super.onOptionsItemSelected(item);
1049        }
1050    }
1051
1052    private boolean isCaptureIntent() {
1053        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1054                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1055                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1056            return true;
1057        } else {
1058            return false;
1059        }
1060    }
1061
1062    @Override
1063    public void onCreate(Bundle state) {
1064        super.onCreate(state);
1065        GcamHelper.init(getContentResolver());
1066
1067        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1068        setContentView(R.layout.activity_main);
1069        mActionBar = getActionBar();
1070        mActionBar.addOnMenuVisibilityListener(this);
1071        mMainHandler = new MainHandler(getMainLooper());
1072        mCameraController =
1073                new CameraController(this, this, mMainHandler,
1074                        CameraManagerFactory.getAndroidCameraManager());
1075        ComboPreferences prefs = new ComboPreferences(getAndroidContext());
1076
1077        mSettingsManager = new SettingsManager(this, null, mCameraController.getNumberOfCameras());
1078        // Remove this after we get rid of ComboPreferences.
1079        int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID));
1080        prefs.setLocalId(this, cameraId);
1081        CameraSettings.upgradeGlobalPreferences(prefs, mCameraController.getNumberOfCameras());
1082        // TODO: Try to move all the resources allocation to happen as soon as
1083        // possible so we can call module.init() at the earliest time.
1084        mModuleManager = new ModuleManagerImpl();
1085        ModulesInfo.setupModules(this, mModuleManager);
1086
1087        mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1088        mModeListView.init(mModuleManager.getSupportedModeIndexList());
1089        if (ApiHelper.HAS_ROTATION_ANIMATION) {
1090            setRotationAnimation();
1091        }
1092
1093        // Check if this is in the secure camera mode.
1094        Intent intent = getIntent();
1095        String action = intent.getAction();
1096        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1097                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1098            mSecureCamera = true;
1099        } else {
1100            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1101        }
1102
1103        if (mSecureCamera) {
1104            // Change the window flags so that secure camera can show when locked
1105            Window win = getWindow();
1106            WindowManager.LayoutParams params = win.getAttributes();
1107            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1108            win.setAttributes(params);
1109
1110            // Filter for screen off so that we can finish secure camera activity
1111            // when screen is off.
1112            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1113            registerReceiver(mScreenOffReceiver, filter);
1114        }
1115        mCameraAppUI = new CameraAppUI(this,
1116                (MainActivityLayout) findViewById(R.id.activity_root_view),
1117                isSecureCamera(), isCaptureIntent());
1118
1119        mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1120
1121        mAboveFilmstripControlLayout =
1122                (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1123        mPanoramaManager = AppManagerFactory.getInstance(this)
1124                .getPanoramaStitchingManager();
1125        mPlaceholderManager = AppManagerFactory.getInstance(this)
1126                .getGcamProcessingManager();
1127        mPanoramaManager.addTaskListener(mStitchingListener);
1128        mPlaceholderManager.addTaskListener(mPlaceholderListener);
1129        mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
1130        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
1131        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1132        mFilmstripController.setImageGap(
1133                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1134        mPanoramaViewHelper = new PanoramaViewHelper(this);
1135        mPanoramaViewHelper.onCreate();
1136        // Set up the camera preview first so the preview shows up ASAP.
1137        mDataAdapter = new CameraDataAdapter(
1138                new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
1139        mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1140
1141
1142        mLocationManager = new LocationManager(this,
1143            new LocationManager.Listener() {
1144                @Override
1145                public void showGpsOnScreenIndicator(boolean hasSignal) {
1146                }
1147
1148                @Override
1149                public void hideGpsOnScreenIndicator() {
1150                }
1151            });
1152
1153        mSettingsController = new SettingsController(this, mSettingsManager, mLocationManager);
1154
1155        int modeIndex = -1;
1156        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1157                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1158            modeIndex = ModeListView.MODE_VIDEO;
1159        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1160                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1161                        .getAction())) {
1162            modeIndex = ModeListView.MODE_PHOTO;
1163            if (mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX)
1164                        == ModeListView.MODE_GCAM && GcamHelper.hasGcamCapture()) {
1165                modeIndex = ModeListView.MODE_GCAM;
1166            }
1167        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1168                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1169            modeIndex = ModeListView.MODE_PHOTO;
1170        } else {
1171            // If the activity has not been started using an explicit intent,
1172            // read the module index from the last time the user changed modes
1173            modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1174            if ((modeIndex == ModeListView.MODE_GCAM &&
1175                    !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1176                modeIndex = ModeListView.MODE_PHOTO;
1177            }
1178        }
1179
1180        mOrientationManager = new OrientationManagerImpl(this);
1181        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1182
1183        setModuleFromModeIndex(modeIndex);
1184
1185        // TODO: Remove this when refactor is done.
1186        if (modeIndex == ModulesInfo.MODULE_PHOTO
1187                || modeIndex == ModulesInfo.MODULE_VIDEO
1188                || modeIndex == ModulesInfo.MODULE_GCAM) {
1189            mCameraAppUI.prepareModuleUI();
1190        }
1191        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1192
1193        if (!mSecureCamera) {
1194            mFilmstripController.setDataAdapter(mDataAdapter);
1195            if (!isCaptureIntent()) {
1196                mDataAdapter.requestLoad(getContentResolver());
1197            }
1198        } else {
1199            // Put a lock placeholder as the last image by setting its date to
1200            // 0.
1201            ImageView v = (ImageView) getLayoutInflater().inflate(
1202                    R.layout.secure_album_placeholder, null);
1203            v.setOnClickListener(new View.OnClickListener() {
1204                @Override
1205                public void onClick(View view) {
1206                    try {
1207                        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1208                                UsageStatistics.ACTION_GALLERY, null);
1209                        startActivity(IntentHelper.getGalleryIntent(CameraActivity.this));
1210                    } catch (ActivityNotFoundException e) {
1211                        Log.w(TAG, "Failed to launch gallery activity, closing");
1212                    }
1213                    finish();
1214                }
1215            });
1216            mDataAdapter = new FixedLastDataAdapter(
1217                    mDataAdapter,
1218                    new SimpleViewData(
1219                            v,
1220                            v.getDrawable().getIntrinsicWidth(),
1221                            v.getDrawable().getIntrinsicHeight(),
1222                            0, 0));
1223            // Flush out all the original data.
1224            mDataAdapter.flush();
1225            mFilmstripController.setDataAdapter(mDataAdapter);
1226        }
1227
1228        setupNfcBeamPush();
1229
1230        mLocalImagesObserver = new LocalMediaObserver();
1231        mLocalVideosObserver = new LocalMediaObserver();
1232
1233        getContentResolver().registerContentObserver(
1234                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1235                mLocalImagesObserver);
1236        getContentResolver().registerContentObserver(
1237                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1238                mLocalVideosObserver);
1239        if (FeedbackHelper.feedbackAvailable()) {
1240            mFeedbackHelper = new FeedbackHelper(this);
1241        }
1242    }
1243
1244    private void setRotationAnimation() {
1245        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1246        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1247        Window win = getWindow();
1248        WindowManager.LayoutParams winParams = win.getAttributes();
1249        winParams.rotationAnimation = rotationAnimation;
1250        win.setAttributes(winParams);
1251    }
1252
1253    @Override
1254    public void onUserInteraction() {
1255        super.onUserInteraction();
1256        if (!isFinishing()) {
1257            keepScreenOnForAWhile();
1258        }
1259    }
1260
1261    @Override
1262    public boolean dispatchTouchEvent(MotionEvent ev) {
1263        boolean result = super.dispatchTouchEvent(ev);
1264        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1265            // Real deletion is postponed until the next user interaction after
1266            // the gesture that triggers deletion. Until real deletion is performed,
1267            // users can click the undo button to bring back the image that they
1268            // chose to delete.
1269            if (mPendingDeletion && !mIsUndoingDeletion) {
1270                performDeletion();
1271            }
1272        }
1273        return result;
1274    }
1275
1276    @Override
1277    public void onPause() {
1278        mPaused = true;
1279
1280        // Delete photos that are pending deletion
1281        performDeletion();
1282        mCurrentModule.pause();
1283        mOrientationManager.pause();
1284        // Close the camera and wait for the operation done.
1285        mCameraController.closeCamera();
1286
1287        mLocalImagesObserver.setActivityPaused(true);
1288        mLocalVideosObserver.setActivityPaused(true);
1289        resetScreenOn();
1290        super.onPause();
1291    }
1292
1293    @Override
1294    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1295        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1296            mResetToPreviewOnResume = false;
1297        } else {
1298            super.onActivityResult(requestCode, resultCode, data);
1299        }
1300    }
1301
1302    @Override
1303    public void onResume() {
1304        mPaused = false;
1305
1306        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1307
1308        // TODO: Handle this in OrientationManager.
1309        // Auto-rotate off
1310        if (Settings.System.getInt(getContentResolver(),
1311                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1312            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1313            mAutoRotateScreen = false;
1314        } else {
1315            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1316            mAutoRotateScreen = true;
1317        }
1318
1319        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1320                UsageStatistics.ACTION_FOREGROUNDED, this.getClass().getSimpleName());
1321
1322        mOrientationManager.resume();
1323        super.onResume();
1324        mCurrentModule.resume();
1325        setSwipingEnabled(true);
1326
1327        if (mResetToPreviewOnResume) {
1328            mCameraAppUI.resume();
1329        }
1330        // Default is showing the preview, unless disabled by explicitly
1331        // starting an activity we want to return from to the filmstrip rather
1332        // than the preview.
1333        mResetToPreviewOnResume = true;
1334
1335        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1336                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1337            if (!mSecureCamera) {
1338                // If it's secure camera, requestLoad() should not be called
1339                // as it will load all the data.
1340                if (!mFilmstripVisible) {
1341                    mDataAdapter.requestLoad(getContentResolver());
1342                }
1343            }
1344        }
1345        mLocalImagesObserver.setActivityPaused(false);
1346        mLocalVideosObserver.setActivityPaused(false);
1347
1348        keepScreenOnForAWhile();
1349    }
1350
1351    @Override
1352    public void onStart() {
1353        super.onStart();
1354        mPanoramaViewHelper.onStart();
1355    }
1356
1357    @Override
1358    protected void onStop() {
1359        mPanoramaViewHelper.onStop();
1360        if (mFeedbackHelper != null) {
1361            mFeedbackHelper.stopFeedback();
1362        }
1363
1364        CameraManagerFactory.recycle();
1365        super.onStop();
1366    }
1367
1368    @Override
1369    public void onDestroy() {
1370        if (mSecureCamera) {
1371            unregisterReceiver(mScreenOffReceiver);
1372        }
1373        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1374        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1375        super.onDestroy();
1376    }
1377
1378    @Override
1379    public void onConfigurationChanged(Configuration config) {
1380        super.onConfigurationChanged(config);
1381        Log.v(TAG, "onConfigurationChanged");
1382        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1383            return;
1384        }
1385
1386        if (mLastLayoutOrientation != config.orientation) {
1387            mLastLayoutOrientation = config.orientation;
1388            mCurrentModule.onLayoutOrientationChanged(
1389                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1390        }
1391    }
1392
1393    @Override
1394    public boolean onKeyDown(int keyCode, KeyEvent event) {
1395        if (mFilmstripController.inCameraFullscreen()) {
1396            if (mCurrentModule.onKeyDown(keyCode, event)) {
1397                return true;
1398            }
1399            // Prevent software keyboard or voice search from showing up.
1400            if (keyCode == KeyEvent.KEYCODE_SEARCH
1401                    || keyCode == KeyEvent.KEYCODE_MENU) {
1402                if (event.isLongPress()) {
1403                    return true;
1404                }
1405            }
1406        }
1407
1408        return super.onKeyDown(keyCode, event);
1409    }
1410
1411    @Override
1412    public boolean onKeyUp(int keyCode, KeyEvent event) {
1413        if (mFilmstripController.inCameraFullscreen() && mCurrentModule.onKeyUp(keyCode, event)) {
1414            return true;
1415        }
1416        return super.onKeyUp(keyCode, event);
1417    }
1418
1419    @Override
1420    public void onBackPressed() {
1421        if (!mCameraAppUI.onBackPressed()) {
1422            if (!mCurrentModule.onBackPressed()) {
1423                super.onBackPressed();
1424            }
1425        }
1426    }
1427
1428    public boolean isAutoRotateScreen() {
1429        return mAutoRotateScreen;
1430    }
1431
1432    protected void updateStorageSpace() {
1433        mStorageSpaceBytes = Storage.getAvailableSpace();
1434    }
1435
1436    protected long getStorageSpaceBytes() {
1437        return mStorageSpaceBytes;
1438    }
1439
1440    protected void updateStorageSpaceAndHint() {
1441        updateStorageSpace();
1442        updateStorageHint(mStorageSpaceBytes);
1443    }
1444
1445    protected void updateStorageHint(long storageSpace) {
1446        String message = null;
1447        if (storageSpace == Storage.UNAVAILABLE) {
1448            message = getString(R.string.no_storage);
1449        } else if (storageSpace == Storage.PREPARING) {
1450            message = getString(R.string.preparing_sd);
1451        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1452            message = getString(R.string.access_sd_fail);
1453        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1454            message = getString(R.string.spaceIsLow_content);
1455        }
1456
1457        if (message != null) {
1458            if (mStorageHint == null) {
1459                mStorageHint = OnScreenHint.makeText(this, message);
1460            } else {
1461                mStorageHint.setText(message);
1462            }
1463            mStorageHint.show();
1464        } else if (mStorageHint != null) {
1465            mStorageHint.cancel();
1466            mStorageHint = null;
1467        }
1468    }
1469
1470    protected void setResultEx(int resultCode) {
1471        mResultCodeForTesting = resultCode;
1472        setResult(resultCode);
1473    }
1474
1475    protected void setResultEx(int resultCode, Intent data) {
1476        mResultCodeForTesting = resultCode;
1477        mResultDataForTesting = data;
1478        setResult(resultCode, data);
1479    }
1480
1481    public int getResultCode() {
1482        return mResultCodeForTesting;
1483    }
1484
1485    public Intent getResultData() {
1486        return mResultDataForTesting;
1487    }
1488
1489    public boolean isSecureCamera() {
1490        return mSecureCamera;
1491    }
1492
1493    @Override
1494    public boolean isPaused() {
1495        return mPaused;
1496    }
1497
1498    @Override
1499    public void onModeSelected(int modeIndex) {
1500        if (mCurrentModeIndex == modeIndex) {
1501            return;
1502        }
1503
1504        if (modeIndex == ModeListView.MODE_SETTING) {
1505            onSettingsSelected();
1506            return;
1507        }
1508
1509        CameraHolder.instance().keep();
1510        closeModule(mCurrentModule);
1511        int oldModuleIndex = mCurrentModeIndex;
1512        setModuleFromModeIndex(modeIndex);
1513
1514        // TODO: The following check is temporary for modules attached to the
1515        // generic_module layout. When the refactor is done, similar logic will
1516        // be applied to all modules.
1517        if (mCurrentModeIndex == ModulesInfo.MODULE_PHOTO
1518                || mCurrentModeIndex == ModulesInfo.MODULE_VIDEO
1519                || mCurrentModeIndex == ModulesInfo.MODULE_GCAM) {
1520            if (oldModuleIndex != ModulesInfo.MODULE_PHOTO
1521                    && oldModuleIndex != ModulesInfo.MODULE_VIDEO
1522                    && oldModuleIndex != ModulesInfo.MODULE_GCAM) {
1523                mCameraAppUI.prepareModuleUI();
1524            } else {
1525                mCameraAppUI.clearModuleUI();
1526            }
1527        } else {
1528            // This is the old way of removing all views in CameraRootView. Will
1529            // be deprecated soon. It is here to make sure modules that haven't
1530            // been refactored can still function.
1531            mCameraAppUI.clearCameraUI();
1532        }
1533
1534        openModule(mCurrentModule);
1535        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1536        if (mMediaSaver != null) {
1537            mCurrentModule.onMediaSaverAvailable(mMediaSaver);
1538        }
1539        // Store the module index so we can use it the next time the Camera
1540        // starts up.
1541        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1542        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply();
1543    }
1544
1545    public void onSettingsSelected() {
1546        // Temporary until we finalize the touch flow.
1547        LayoutInflater inflater = getLayoutInflater();
1548        SettingsView settingsView = (SettingsView) inflater.inflate(R.layout.settings_list_layout,
1549            null, false);
1550        settingsView.setSettingsListener(mSettingsController);
1551        if (mFeedbackHelper != null) {
1552            settingsView.setFeedbackHelper(mFeedbackHelper);
1553        }
1554        PopupWindow popup = new PopupWindow(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
1555        popup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
1556        popup.setOutsideTouchable(true);
1557        popup.setFocusable(true);
1558        popup.setContentView(settingsView);
1559        popup.showAtLocation(mModeListView.getRootView(), Gravity.CENTER, 0, 0);
1560    }
1561
1562    /**
1563     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1564     * index an sets it as mCurrentModule.
1565     */
1566    private void setModuleFromModeIndex(int modeIndex) {
1567        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1568        if (agent == null) {
1569            return;
1570        }
1571        if (!agent.requestAppForCamera()) {
1572            mCameraController.closeCamera();
1573        }
1574        mCurrentModeIndex = agent.getModuleId();
1575        mCurrentModule = (CameraModule)  agent.createModule(this);
1576    }
1577
1578    @Override
1579    public SettingsManager getSettingsManager() {
1580        return mSettingsManager;
1581    }
1582
1583    @Override
1584    public CameraServices getServices() {
1585        return (CameraServices) getApplication();
1586    }
1587
1588    @Override
1589    public SettingsController getSettingsController() {
1590        return mSettingsController;
1591    }
1592
1593    public ButtonManager getButtonManager() {
1594        if (mButtonManager == null) {
1595            mButtonManager = new ButtonManager(this);
1596        }
1597        return mButtonManager;
1598    }
1599
1600    /**
1601     * Creates an AlertDialog appropriate for choosing whether to enable location
1602     * on the first run of the app.
1603     */
1604    public AlertDialog getFirstTimeLocationAlert() {
1605        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1606        builder = SettingsView.getFirstTimeLocationAlertBuilder(builder, mSettingsController);
1607        if (builder != null) {
1608            return builder.create();
1609        } else {
1610            return null;
1611        }
1612    }
1613
1614    /**
1615     * Launches an ACTION_EDIT intent for the given local data item.
1616     */
1617    public void launchEditor(LocalData data) {
1618        Intent intent = new Intent(Intent.ACTION_EDIT)
1619                .setDataAndType(data.getContentUri(), data.getMimeType())
1620                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1621        try {
1622            startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1623        } catch (ActivityNotFoundException e) {
1624            startActivityForResult(Intent.createChooser(intent, null),
1625                    REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1626        }
1627    }
1628
1629    /**
1630     * Launch the tiny planet editor.
1631     *
1632     * @param data The data must be a 360 degree stereographically mapped
1633     *             panoramic image. It will not be modified, instead a new item
1634     *             with the result will be added to the filmstrip.
1635     */
1636    public void launchTinyPlanetEditor(LocalData data) {
1637        TinyPlanetFragment fragment = new TinyPlanetFragment();
1638        Bundle bundle = new Bundle();
1639        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1640        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1641        fragment.setArguments(bundle);
1642        fragment.show(getFragmentManager(), "tiny_planet");
1643    }
1644
1645    private void openModule(CameraModule module) {
1646        module.init(this, isSecureCamera(), isCaptureIntent());
1647        module.resume();
1648        module.onPreviewVisibilityChanged(!mFilmstripVisible);
1649    }
1650
1651    private void closeModule(CameraModule module) {
1652        module.pause();
1653    }
1654
1655    private void performDeletion() {
1656        if (!mPendingDeletion) {
1657            return;
1658        }
1659        hideUndoDeletionBar(false);
1660        mDataAdapter.executeDeletion(CameraActivity.this);
1661
1662        int currentId = mFilmstripController.getCurrentId();
1663        updateActionBarMenu(currentId);
1664    }
1665
1666    public void showUndoDeletionBar() {
1667        if (mPendingDeletion) {
1668            performDeletion();
1669        }
1670        Log.v(TAG, "showing undo bar");
1671        mPendingDeletion = true;
1672        if (mUndoDeletionBar == null) {
1673            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1674                    mAboveFilmstripControlLayout, true);
1675            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1676            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1677            button.setOnClickListener(new View.OnClickListener() {
1678                @Override
1679                public void onClick(View view) {
1680                    mDataAdapter.undoDataRemoval();
1681                    hideUndoDeletionBar(true);
1682                }
1683            });
1684            // Setting undo bar clickable to avoid touch events going through
1685            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1686            mUndoDeletionBar.setClickable(true);
1687            // When there is user interaction going on with the undo button, we
1688            // do not want to hide the undo bar.
1689            button.setOnTouchListener(new View.OnTouchListener() {
1690                @Override
1691                public boolean onTouch(View v, MotionEvent event) {
1692                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1693                        mIsUndoingDeletion = true;
1694                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1695                        mIsUndoingDeletion = false;
1696                    }
1697                    return false;
1698                }
1699            });
1700        }
1701        mUndoDeletionBar.setAlpha(0f);
1702        mUndoDeletionBar.setVisibility(View.VISIBLE);
1703        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1704    }
1705
1706    private void hideUndoDeletionBar(boolean withAnimation) {
1707        Log.v(TAG, "Hiding undo deletion bar");
1708        mPendingDeletion = false;
1709        if (mUndoDeletionBar != null) {
1710            if (withAnimation) {
1711                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1712                        .setListener(new Animator.AnimatorListener() {
1713                            @Override
1714                            public void onAnimationStart(Animator animation) {
1715                                // Do nothing.
1716                            }
1717
1718                            @Override
1719                            public void onAnimationEnd(Animator animation) {
1720                                mUndoDeletionBar.setVisibility(View.GONE);
1721                            }
1722
1723                            @Override
1724                            public void onAnimationCancel(Animator animation) {
1725                                // Do nothing.
1726                            }
1727
1728                            @Override
1729                            public void onAnimationRepeat(Animator animation) {
1730                                // Do nothing.
1731                            }
1732                        }).start();
1733            } else {
1734                mUndoDeletionBar.setVisibility(View.GONE);
1735            }
1736        }
1737    }
1738
1739    @Override
1740    public void onOrientationChanged(int orientation) {
1741        // We keep the last known orientation. So if the user first orient
1742        // the camera then point the camera to floor or sky, we still have
1743        // the correct orientation.
1744        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
1745            return;
1746        }
1747        mLastRawOrientation = orientation;
1748        if (mCurrentModule != null) {
1749            mCurrentModule.onOrientationChanged(orientation);
1750        }
1751    }
1752
1753    /**
1754     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1755     * capture intent.
1756     *
1757     * @param enable {@code true} to enable swipe.
1758     */
1759    public void setSwipingEnabled(boolean enable) {
1760        // TODO: Bring back the functionality.
1761        if (isCaptureIntent()) {
1762            //lockPreview(true);
1763        } else {
1764            //lockPreview(!enable);
1765        }
1766    }
1767
1768    // Accessor methods for getting latency times used in performance testing
1769    public long getAutoFocusTime() {
1770        return (mCurrentModule instanceof PhotoModule) ?
1771                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1772    }
1773
1774    public long getShutterLag() {
1775        return (mCurrentModule instanceof PhotoModule) ?
1776                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1777    }
1778
1779    public long getShutterToPictureDisplayedTime() {
1780        return (mCurrentModule instanceof PhotoModule) ?
1781                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1782    }
1783
1784    public long getPictureDisplayedToJpegCallbackTime() {
1785        return (mCurrentModule instanceof PhotoModule) ?
1786                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1787    }
1788
1789    public long getJpegCallbackFinishTime() {
1790        return (mCurrentModule instanceof PhotoModule) ?
1791                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1792    }
1793
1794    public long getCaptureStartTime() {
1795        return (mCurrentModule instanceof PhotoModule) ?
1796                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1797    }
1798
1799    public boolean isRecording() {
1800        return (mCurrentModule instanceof VideoModule) ?
1801                ((VideoModule) mCurrentModule).isRecording() : false;
1802    }
1803
1804    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
1805        return mCameraController;
1806    }
1807
1808    // For debugging purposes only.
1809    public CameraModule getCurrentModule() {
1810        return mCurrentModule;
1811    }
1812
1813    private void keepScreenOnForAWhile() {
1814        if (mKeepScreenOn) {
1815            return;
1816        }
1817        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1818        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1819        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
1820    }
1821
1822    private void resetScreenOn() {
1823        mKeepScreenOn = false;
1824        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1825        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1826    }
1827
1828    /**
1829     * Updates the visibility of the filmstrip bottom controls.
1830     */
1831    private void updateUiByData(final int dataId) {
1832        if (isSecureCamera()) {
1833            // We cannot show buttons in secure camera since go to other
1834            // activities might create a security hole.
1835            return;
1836        }
1837
1838        final LocalData currentData = mDataAdapter.getLocalData(dataId);
1839        if (currentData == null) {
1840            Log.w(TAG, "Current data ID not found.");
1841            hidePanoStitchingProgress();
1842            return;
1843        }
1844
1845        updateActionBarMenu(dataId);
1846
1847        /* Bottom controls. */
1848
1849        final CameraAppUI.BottomControls filmstripBottomControls =
1850                mCameraAppUI.getFilmstripBottomControls();
1851        // We can only edit photos, not videos.
1852        filmstripBottomControls.setEditButtonVisibility(currentData.isPhoto());
1853
1854        /* Progress bar */
1855
1856        final int panoStitchingProgress =
1857                mPanoramaManager.getTaskProgress(currentData.getContentUri());
1858        if (panoStitchingProgress < 0) {
1859            hidePanoStitchingProgress();
1860        } else {
1861            showPanoStitchingProgress();
1862            updateStitchingProgress(panoStitchingProgress);
1863        }
1864
1865        /* View button */
1866
1867        // We need to add this to a separate DB.
1868        // TODO: Redesign this.
1869        currentData.requestAuxInfo(this, new LocalData.AuxInfoSupportCallback() {
1870            @Override
1871            public void auxInfoAvailable(final boolean isPanorama,
1872                    final boolean isPanorama360, boolean isRgbz) {
1873                // Make sure the returned data is for the current image.
1874                if (dataId != mFilmstripController.getCurrentId()) {
1875                    return;
1876                }
1877
1878                // If this is a photo sphere, show the button to view it. If it's a full
1879                // 360 photo sphere, show the tiny planet button.
1880                final int viewButtonVisibility;
1881                if (isPanorama) {
1882                    viewButtonVisibility = CameraAppUI.BottomControls.VIEW_PHOTO_SPHERE;
1883                } else if (isRgbz) {
1884                    viewButtonVisibility = CameraAppUI.BottomControls.VIEW_RGBZ;
1885                } else {
1886                    viewButtonVisibility = CameraAppUI.BottomControls.VIEW_NONE;
1887                }
1888
1889                runOnUiThread(new Runnable() {
1890
1891                    @Override
1892                    public void run() {
1893                        if (mFilmstripController.getCurrentId() == dataId) {
1894                            filmstripBottomControls.setTinyPlanetButtonVisibility(isPanorama360);
1895                            filmstripBottomControls.setViewButtonVisibility(viewButtonVisibility);
1896                        }
1897                    }
1898                });
1899            }
1900        });
1901    }
1902}
1903