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