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