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