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