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