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