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