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