CameraActivity.java revision fe47650a9cac114ca0a16697e3b13ab70893f352
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 SurfaceTexture getPreviewBuffer() {
698        // TODO: implement this
699        return null;
700    }
701
702    @Override
703    public void onPreviewStarted() {
704        mCameraAppUI.onPreviewStarted();
705    }
706
707    @Override
708    public void addPreviewAreaSizeChangedListener(
709            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
710        mCameraAppUI.addPreviewAreaSizeChangedListener(listener);
711    }
712
713    @Override
714    public void removePreviewAreaSizeChangedListener(
715            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
716        mCameraAppUI.removePreviewAreaSizeChangedListener(listener);
717    }
718
719    @Override
720    public void setupOneShotPreviewListener() {
721        mCameraController.setOneShotPreviewCallback(mMainHandler,
722                new CameraManager.CameraPreviewDataCallback() {
723                    @Override
724                    public void onPreviewFrame(byte[] data, CameraManager.CameraProxy camera) {
725                        mCameraAppUI.onNewPreviewFrame();
726                    }
727                });
728    }
729
730    @Override
731    public void updatePreviewAspectRatio(float aspectRatio) {
732        mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
733    }
734
735    @Override
736    public boolean shouldShowShimmy() {
737        int remainingTimes = mSettingsManager.getInt(
738                SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX);
739        return remainingTimes > 0;
740    }
741
742    @Override
743    public void decrementShimmyPlayTimes() {
744        int remainingTimes = mSettingsManager.getInt(
745                SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX) - 1;
746        if (remainingTimes >= 0) {
747            mSettingsManager.setInt(SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX,
748                    remainingTimes);
749        }
750     }
751
752    @Override
753    public void updatePreviewTransform(Matrix matrix) {
754        mCameraAppUI.updatePreviewTransform(matrix);
755    }
756
757    @Override
758    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
759        mCameraAppUI.setPreviewStatusListener(previewStatusListener);
760    }
761
762    @Override
763    public FrameLayout getModuleLayoutRoot() {
764        return mCameraAppUI.getModuleRootView();
765    }
766
767    @Override
768    public void setShutterEventsListener(ShutterEventsListener listener) {
769        // TODO: implement this
770    }
771
772    @Override
773    public void setShutterEnabled(boolean enabled) {
774        // TODO: implement this
775    }
776
777    @Override
778    public boolean isShutterEnabled() {
779        // TODO: implement this
780        return false;
781    }
782
783    @Override
784    public void startPreCaptureAnimation() {
785        mCameraAppUI.startPreCaptureAnimation();
786    }
787
788    @Override
789    public void cancelPreCaptureAnimation() {
790        // TODO: implement this
791    }
792
793    @Override
794    public void startPostCaptureAnimation() {
795        // TODO: implement this
796    }
797
798    @Override
799    public void startPostCaptureAnimation(Bitmap thumbnail) {
800        // TODO: implement this
801    }
802
803    @Override
804    public void cancelPostCaptureAnimation() {
805        // TODO: implement this
806    }
807
808    @Override
809    public OrientationManager getOrientationManager() {
810        return mOrientationManager;
811    }
812
813    @Override
814    public LocationManager getLocationManager() {
815        return mLocationManager;
816    }
817
818    @Override
819    public void lockOrientation() {
820        mOrientationManager.lockOrientation();
821    }
822
823    @Override
824    public void unlockOrientation() {
825        mOrientationManager.unlockOrientation();
826    }
827
828    @Override
829    public void notifyNewMedia(Uri uri) {
830        ContentResolver cr = getContentResolver();
831        String mimeType = cr.getType(uri);
832        if (mimeType.startsWith("video/")) {
833            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
834            mDataAdapter.addNewVideo(uri);
835        } else if (mimeType.startsWith("image/")) {
836            CameraUtil.broadcastNewPicture(this, uri);
837            mDataAdapter.addNewPhoto(uri);
838        } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) {
839            mDataAdapter.addNewPhoto(uri);
840        } else {
841            android.util.Log.w(TAG, "Unknown new media with MIME type:"
842                    + mimeType + ", uri:" + uri);
843        }
844    }
845
846    @Override
847    public void enableKeepScreenOn(boolean enabled) {
848        if (mPaused) {
849            return;
850        }
851
852        mKeepScreenOn = enabled;
853        if (mKeepScreenOn) {
854            mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
855            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
856        } else {
857            keepScreenOnForAWhile();
858        }
859    }
860
861    @Override
862    public CameraProvider getCameraProvider() {
863        return mCameraController;
864    }
865
866    private void removeData(int dataID) {
867        mDataAdapter.removeData(dataID);
868        if (mDataAdapter.getTotalNumber() > 1) {
869            showUndoDeletionBar();
870        } else {
871            // If camera preview is the only view left in filmstrip,
872            // no need to show undo bar.
873            mPendingDeletion = true;
874            performDeletion();
875        }
876    }
877
878    @Override
879    public boolean onOptionsItemSelected(MenuItem item) {
880        // Handle presses on the action bar items
881        switch (item.getItemId()) {
882            case android.R.id.home:
883                if (mFilmstripVisible && IntentHelper.shouldLaunchGalleryOnUpAction()) {
884                    startGallery();
885                } else {
886                    onBackPressed();
887                }
888                return true;
889            default:
890                return super.onOptionsItemSelected(item);
891        }
892    }
893
894    private boolean isCaptureIntent() {
895        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
896                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
897                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
898            return true;
899        } else {
900            return false;
901        }
902    }
903
904    @Override
905    public void onCreate(Bundle state) {
906        super.onCreate(state);
907        GcamHelper.init(getContentResolver());
908
909        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
910        setContentView(R.layout.activity_main);
911        mActionBar = getActionBar();
912        mActionBar.addOnMenuVisibilityListener(this);
913        mMainHandler = new MainHandler(getMainLooper());
914        mCameraController =
915                new CameraController(this, this, mMainHandler,
916                        CameraManagerFactory.getAndroidCameraManager());
917        mPreferences = new ComboPreferences(getAndroidContext());
918        mContentResolver = this.getContentResolver();
919
920        mSettingsManager = new SettingsManager(this, mCameraController.getNumberOfCameras());
921
922        // Remove this after we get rid of ComboPreferences.
923        int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID));
924        mPreferences.setLocalId(this, cameraId);
925        CameraSettings.upgradeGlobalPreferences(mPreferences,
926                mCameraController.getNumberOfCameras());
927        // TODO: Try to move all the resources allocation to happen as soon as
928        // possible so we can call module.init() at the earliest time.
929        mModuleManager = new ModuleManagerImpl();
930        ModulesInfo.setupModules(this, mModuleManager);
931
932        mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
933        mModeListView.init(mModuleManager.getSupportedModeIndexList());
934        if (ApiHelper.HAS_ROTATION_ANIMATION) {
935            setRotationAnimation();
936        }
937
938        // Check if this is in the secure camera mode.
939        Intent intent = getIntent();
940        String action = intent.getAction();
941        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
942                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
943            mSecureCamera = true;
944        } else {
945            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
946        }
947
948        if (mSecureCamera) {
949            // Change the window flags so that secure camera can show when locked
950            Window win = getWindow();
951            WindowManager.LayoutParams params = win.getAttributes();
952            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
953            win.setAttributes(params);
954
955            // Filter for screen off so that we can finish secure camera activity
956            // when screen is off.
957            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
958            registerReceiver(mScreenOffReceiver, filter);
959        }
960        mCameraAppUI = new CameraAppUI(this,
961                (MainActivityLayout) findViewById(R.id.activity_root_view),
962                isSecureCamera(), isCaptureIntent());
963
964        mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
965
966        mAboveFilmstripControlLayout =
967                (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
968
969        // Add the session listener so we can track the session progress updates.
970        getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
971        mSessionProgressPanel = findViewById(R.id.pano_session_progress_panel);
972        mBottomProgressBar = (ProgressBar) findViewById(R.id.pano_session_progress_bar);
973        mBottomProgressText = (TextView) findViewById(R.id.pano_session_progress_text);
974        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
975        mFilmstripController.setImageGap(
976                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
977        mPanoramaViewHelper = new PanoramaViewHelper(this);
978        mPanoramaViewHelper.onCreate();
979        // Set up the camera preview first so the preview shows up ASAP.
980        mDataAdapter = new CameraDataAdapter(getApplicationContext(),
981                new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
982        mDataAdapter.setLocalDataListener(mLocalDataListener);
983
984        mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
985
986        mLocationManager = new LocationManager(this);
987
988        mSettingsController = new SettingsController(this, mSettingsManager, mLocationManager);
989
990        int modeIndex = -1;
991        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
992                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
993            modeIndex = ModeListView.MODE_VIDEO;
994        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
995                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
996                        .getAction())) {
997            modeIndex = ModeListView.MODE_PHOTO;
998            if (mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX)
999                        == ModeListView.MODE_GCAM && GcamHelper.hasGcamCapture()) {
1000                modeIndex = ModeListView.MODE_GCAM;
1001            }
1002        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1003                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1004            modeIndex = ModeListView.MODE_PHOTO;
1005        } else {
1006            // If the activity has not been started using an explicit intent,
1007            // read the module index from the last time the user changed modes
1008            modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1009            if ((modeIndex == ModeListView.MODE_GCAM &&
1010                    !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1011                modeIndex = ModeListView.MODE_PHOTO;
1012            }
1013        }
1014
1015        mOrientationManager = new OrientationManagerImpl(this);
1016        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1017
1018        setModuleFromModeIndex(modeIndex);
1019
1020        // TODO: Remove this when refactor is done.
1021        if (modeIndex == ModulesInfo.MODULE_PHOTO
1022                || modeIndex == ModulesInfo.MODULE_VIDEO
1023                || modeIndex == ModulesInfo.MODULE_GCAM
1024                || modeIndex == ModulesInfo.MODULE_CRAFT
1025                || modeIndex == ModulesInfo.MODULE_REFOCUS) {
1026            mCameraAppUI.prepareModuleUI();
1027        }
1028
1029        syncBottomBarColor();
1030        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1031
1032        if (!mSecureCamera) {
1033            mFilmstripController.setDataAdapter(mDataAdapter);
1034            if (!isCaptureIntent()) {
1035                mDataAdapter.requestLoad();
1036            }
1037        } else {
1038            // Put a lock placeholder as the last image by setting its date to
1039            // 0.
1040            ImageView v = (ImageView) getLayoutInflater().inflate(
1041                    R.layout.secure_album_placeholder, null);
1042            v.setOnClickListener(new View.OnClickListener() {
1043                @Override
1044                public void onClick(View view) {
1045                    startGallery();
1046                    finish();
1047                }
1048            });
1049            mDataAdapter = new FixedLastDataAdapter(
1050                    getApplicationContext(),
1051                    mDataAdapter,
1052                    new SimpleViewData(
1053                            v,
1054                            v.getDrawable().getIntrinsicWidth(),
1055                            v.getDrawable().getIntrinsicHeight(),
1056                            0, 0));
1057            // Flush out all the original data.
1058            mDataAdapter.flush();
1059            mFilmstripController.setDataAdapter(mDataAdapter);
1060        }
1061
1062        setupNfcBeamPush();
1063
1064        mLocalImagesObserver = new LocalMediaObserver();
1065        mLocalVideosObserver = new LocalMediaObserver();
1066
1067        getContentResolver().registerContentObserver(
1068                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1069                mLocalImagesObserver);
1070        getContentResolver().registerContentObserver(
1071                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1072                mLocalVideosObserver);
1073        if (FeedbackHelper.feedbackAvailable()) {
1074            mFeedbackHelper = new FeedbackHelper(this);
1075        }
1076    }
1077
1078    private void setRotationAnimation() {
1079        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1080        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1081        Window win = getWindow();
1082        WindowManager.LayoutParams winParams = win.getAttributes();
1083        winParams.rotationAnimation = rotationAnimation;
1084        win.setAttributes(winParams);
1085    }
1086
1087    @Override
1088    public void onUserInteraction() {
1089        super.onUserInteraction();
1090        if (!isFinishing()) {
1091            keepScreenOnForAWhile();
1092        }
1093    }
1094
1095    @Override
1096    public boolean dispatchTouchEvent(MotionEvent ev) {
1097        boolean result = super.dispatchTouchEvent(ev);
1098        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1099            // Real deletion is postponed until the next user interaction after
1100            // the gesture that triggers deletion. Until real deletion is performed,
1101            // users can click the undo button to bring back the image that they
1102            // chose to delete.
1103            if (mPendingDeletion && !mIsUndoingDeletion) {
1104                performDeletion();
1105            }
1106        }
1107        return result;
1108    }
1109
1110    @Override
1111    public void onPause() {
1112        mPaused = true;
1113
1114        // Delete photos that are pending deletion
1115        performDeletion();
1116        mCurrentModule.pause();
1117        mOrientationManager.pause();
1118        // Close the camera and wait for the operation done.
1119        mCameraController.closeCamera();
1120
1121        mLocalImagesObserver.setActivityPaused(true);
1122        mLocalVideosObserver.setActivityPaused(true);
1123        resetScreenOn();
1124        super.onPause();
1125    }
1126
1127    @Override
1128    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1129        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1130            mResetToPreviewOnResume = false;
1131        } else {
1132            super.onActivityResult(requestCode, resultCode, data);
1133        }
1134    }
1135
1136    @Override
1137    public void onResume() {
1138        mPaused = false;
1139
1140        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1141
1142        // TODO: Handle this in OrientationManager.
1143        // Auto-rotate off
1144        if (Settings.System.getInt(getContentResolver(),
1145                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1146            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1147            mAutoRotateScreen = false;
1148        } else {
1149            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1150            mAutoRotateScreen = true;
1151        }
1152
1153        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1154                UsageStatistics.ACTION_FOREGROUNDED, this.getClass().getSimpleName());
1155
1156        mOrientationManager.resume();
1157        super.onResume();
1158        mCurrentModule.resume();
1159        setSwipingEnabled(true);
1160
1161        if (mResetToPreviewOnResume) {
1162            mCameraAppUI.resume();
1163        }
1164        // The share button might be disabled to avoid double tapping.
1165        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1166        // Default is showing the preview, unless disabled by explicitly
1167        // starting an activity we want to return from to the filmstrip rather
1168        // than the preview.
1169        mResetToPreviewOnResume = true;
1170
1171        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1172                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1173            if (!mSecureCamera) {
1174                // If it's secure camera, requestLoad() should not be called
1175                // as it will load all the data.
1176                if (!mFilmstripVisible) {
1177                    mDataAdapter.requestLoad();
1178                }
1179            }
1180        }
1181        mLocalImagesObserver.setActivityPaused(false);
1182        mLocalVideosObserver.setActivityPaused(false);
1183
1184        keepScreenOnForAWhile();
1185
1186        // Lights-out mode at all times.
1187        findViewById(R.id.activity_root_view).setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
1188    }
1189
1190    @Override
1191    public void onStart() {
1192        super.onStart();
1193        mPanoramaViewHelper.onStart();
1194        boolean recordLocation = RecordLocationPreference.get(
1195                mPreferences, mContentResolver);
1196        mLocationManager.recordLocation(recordLocation);
1197    }
1198
1199    @Override
1200    protected void onStop() {
1201        mPanoramaViewHelper.onStop();
1202        if (mFeedbackHelper != null) {
1203            mFeedbackHelper.stopFeedback();
1204        }
1205
1206        mLocationManager.disconnect();
1207        CameraManagerFactory.recycle();
1208        super.onStop();
1209    }
1210
1211    @Override
1212    public void onDestroy() {
1213        if (mSecureCamera) {
1214            unregisterReceiver(mScreenOffReceiver);
1215        }
1216        mSettingsManager.removeAllListeners();
1217        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1218        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1219        super.onDestroy();
1220    }
1221
1222    @Override
1223    public void onConfigurationChanged(Configuration config) {
1224        super.onConfigurationChanged(config);
1225        Log.v(TAG, "onConfigurationChanged");
1226        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1227            return;
1228        }
1229
1230        if (mLastLayoutOrientation != config.orientation) {
1231            mLastLayoutOrientation = config.orientation;
1232            mCurrentModule.onLayoutOrientationChanged(
1233                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1234        }
1235    }
1236
1237    @Override
1238    public boolean onKeyDown(int keyCode, KeyEvent event) {
1239        if (!mFilmstripVisible) {
1240            if (mCurrentModule.onKeyDown(keyCode, event)) {
1241                return true;
1242            }
1243            // Prevent software keyboard or voice search from showing up.
1244            if (keyCode == KeyEvent.KEYCODE_SEARCH
1245                    || keyCode == KeyEvent.KEYCODE_MENU) {
1246                if (event.isLongPress()) {
1247                    return true;
1248                }
1249            }
1250        }
1251
1252        return super.onKeyDown(keyCode, event);
1253    }
1254
1255    @Override
1256    public boolean onKeyUp(int keyCode, KeyEvent event) {
1257        if (!mFilmstripVisible && mCurrentModule.onKeyUp(keyCode, event)) {
1258            return true;
1259        }
1260        return super.onKeyUp(keyCode, event);
1261    }
1262
1263    @Override
1264    public void onBackPressed() {
1265        if (!mCameraAppUI.onBackPressed()) {
1266            if (!mCurrentModule.onBackPressed()) {
1267                super.onBackPressed();
1268            }
1269        }
1270    }
1271
1272    public boolean isAutoRotateScreen() {
1273        return mAutoRotateScreen;
1274    }
1275
1276    protected void updateStorageSpace() {
1277        mStorageSpaceBytes = Storage.getAvailableSpace();
1278    }
1279
1280    protected long getStorageSpaceBytes() {
1281        return mStorageSpaceBytes;
1282    }
1283
1284    protected void updateStorageSpaceAndHint() {
1285        updateStorageSpace();
1286        updateStorageHint(mStorageSpaceBytes);
1287    }
1288
1289    protected void updateStorageHint(long storageSpace) {
1290        String message = null;
1291        if (storageSpace == Storage.UNAVAILABLE) {
1292            message = getString(R.string.no_storage);
1293        } else if (storageSpace == Storage.PREPARING) {
1294            message = getString(R.string.preparing_sd);
1295        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1296            message = getString(R.string.access_sd_fail);
1297        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1298            message = getString(R.string.spaceIsLow_content);
1299        }
1300
1301        if (message != null) {
1302            if (mStorageHint == null) {
1303                mStorageHint = OnScreenHint.makeText(this, message);
1304            } else {
1305                mStorageHint.setText(message);
1306            }
1307            mStorageHint.show();
1308        } else if (mStorageHint != null) {
1309            mStorageHint.cancel();
1310            mStorageHint = null;
1311        }
1312    }
1313
1314    protected void setResultEx(int resultCode) {
1315        mResultCodeForTesting = resultCode;
1316        setResult(resultCode);
1317    }
1318
1319    protected void setResultEx(int resultCode, Intent data) {
1320        mResultCodeForTesting = resultCode;
1321        mResultDataForTesting = data;
1322        setResult(resultCode, data);
1323    }
1324
1325    public int getResultCode() {
1326        return mResultCodeForTesting;
1327    }
1328
1329    public Intent getResultData() {
1330        return mResultDataForTesting;
1331    }
1332
1333    public boolean isSecureCamera() {
1334        return mSecureCamera;
1335    }
1336
1337    @Override
1338    public boolean isPaused() {
1339        return mPaused;
1340    }
1341
1342    @Override
1343    public void onModeSelected(int modeIndex) {
1344        if (mCurrentModeIndex == modeIndex) {
1345            return;
1346        }
1347
1348        if (modeIndex == ModeListView.MODE_SETTING) {
1349            onSettingsSelected();
1350            return;
1351        }
1352
1353        closeModule(mCurrentModule);
1354        int oldModuleIndex = mCurrentModeIndex;
1355
1356        // Refocus and Gcam are modes that cannot be selected
1357        // from the mode list view, because they are not list items.
1358        // Check whether we should interpret MODULE_CRAFT as either.
1359        if (modeIndex == ModulesInfo.MODULE_CRAFT) {
1360            boolean refocusOn = mSettingsManager.isRefocusOn();
1361            boolean hdrPlusOn = mSettingsManager.isHdrPlusOn();
1362            if (refocusOn && hdrPlusOn) {
1363                throw new IllegalStateException("Refocus and hdr plus cannot be on together.");
1364            }
1365            if (refocusOn) {
1366                modeIndex = ModulesInfo.MODULE_REFOCUS;
1367            } else if (hdrPlusOn) {
1368                modeIndex = ModulesInfo.MODULE_GCAM;
1369            } else {
1370                // Do nothing, keep MODULE_CRAFT.
1371            }
1372        }
1373
1374        setModuleFromModeIndex(modeIndex);
1375
1376        // TODO: The following check is temporary for modules attached to the
1377        // generic_module layout. When the refactor is done, similar logic will
1378        // be applied to all modules.
1379        if (mCurrentModeIndex == ModulesInfo.MODULE_PHOTO
1380                || mCurrentModeIndex == ModulesInfo.MODULE_VIDEO
1381                || mCurrentModeIndex == ModulesInfo.MODULE_GCAM
1382                || mCurrentModeIndex == ModulesInfo.MODULE_CRAFT
1383                || mCurrentModeIndex == ModulesInfo.MODULE_REFOCUS) {
1384            if (oldModuleIndex != ModulesInfo.MODULE_PHOTO
1385                    && oldModuleIndex != ModulesInfo.MODULE_VIDEO
1386                    && oldModuleIndex != ModulesInfo.MODULE_GCAM
1387                    && oldModuleIndex != ModulesInfo.MODULE_CRAFT
1388                    && oldModuleIndex != ModulesInfo.MODULE_REFOCUS) {
1389                mCameraAppUI.prepareModuleUI();
1390            } else {
1391                mCameraAppUI.clearModuleUI();
1392            }
1393        } else {
1394            // This is the old way of removing all views in CameraRootView. Will
1395            // be deprecated soon. It is here to make sure modules that haven't
1396            // been refactored can still function.
1397            mCameraAppUI.clearCameraUI();
1398        }
1399
1400        openModule(mCurrentModule);
1401        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1402        // Store the module index so we can use it the next time the Camera
1403        // starts up.
1404        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1405        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply();
1406    }
1407
1408    public void onSettingsSelected() {
1409        // Temporary until we finalize the touch flow.
1410        LayoutInflater inflater = getLayoutInflater();
1411        SettingsView settingsView = (SettingsView) inflater.inflate(R.layout.settings_list_layout,
1412            null, false);
1413        settingsView.setSettingsListener(mSettingsController);
1414        if (mFeedbackHelper != null) {
1415            settingsView.setFeedbackHelper(mFeedbackHelper);
1416        }
1417        PopupWindow popup = new PopupWindow(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
1418        popup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
1419        popup.setOutsideTouchable(true);
1420        popup.setFocusable(true);
1421        popup.setContentView(settingsView);
1422        popup.showAtLocation(mModeListView.getRootView(), Gravity.CENTER, 0, 0);
1423    }
1424
1425    /**
1426     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1427     * index an sets it as mCurrentModule.
1428     */
1429    private void setModuleFromModeIndex(int modeIndex) {
1430        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1431        if (agent == null) {
1432            return;
1433        }
1434        if (!agent.requestAppForCamera()) {
1435            mCameraController.closeCamera();
1436        }
1437        mCurrentModeIndex = agent.getModuleId();
1438        mCurrentModule = (CameraModule)  agent.createModule(this);
1439    }
1440
1441    @Override
1442    public SettingsManager getSettingsManager() {
1443        return mSettingsManager;
1444    }
1445
1446    @Override
1447    public CameraServices getServices() {
1448        return (CameraServices) getApplication();
1449    }
1450
1451    @Override
1452    public SettingsController getSettingsController() {
1453        return mSettingsController;
1454    }
1455
1456    @Override
1457    public ButtonManager getButtonManager() {
1458        if (mButtonManager == null) {
1459            mButtonManager = new ButtonManager(this);
1460        }
1461        return mButtonManager;
1462    }
1463
1464    /**
1465     * Creates an AlertDialog appropriate for choosing whether to enable location
1466     * on the first run of the app.
1467     */
1468    public AlertDialog getFirstTimeLocationAlert() {
1469        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1470        builder = SettingsView.getFirstTimeLocationAlertBuilder(builder, mSettingsController);
1471        if (builder != null) {
1472            return builder.create();
1473        } else {
1474            return null;
1475        }
1476    }
1477
1478    /**
1479     * Launches an ACTION_EDIT intent for the given local data item.
1480     */
1481    public void launchEditor(LocalData data) {
1482        Intent intent = new Intent(Intent.ACTION_EDIT)
1483                .setDataAndType(data.getContentUri(), data.getMimeType())
1484                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1485        try {
1486            launchActivityByIntent(intent);
1487        } catch (ActivityNotFoundException e) {
1488            launchActivityByIntent(Intent.createChooser(intent, null));
1489        }
1490    }
1491
1492    /**
1493     * Launch the tiny planet editor.
1494     *
1495     * @param data The data must be a 360 degree stereographically mapped
1496     *             panoramic image. It will not be modified, instead a new item
1497     *             with the result will be added to the filmstrip.
1498     */
1499    public void launchTinyPlanetEditor(LocalData data) {
1500        TinyPlanetFragment fragment = new TinyPlanetFragment();
1501        Bundle bundle = new Bundle();
1502        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1503        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1504        fragment.setArguments(bundle);
1505        fragment.show(getFragmentManager(), "tiny_planet");
1506    }
1507
1508    private void syncBottomBarColor() {
1509        // Currently not all modules use the generic_module UI.
1510        // TODO: once all modules have a bottom bar, remove
1511        // isUsingBottomBar check.
1512        if (mCurrentModule.isUsingBottomBar()) {
1513            int colorResId = mModeListView.getModeThemeColor(mCurrentModeIndex);
1514            mCameraAppUI.setBottomBarColor(getResources().getColor(colorResId));
1515        }
1516    }
1517
1518    private void openModule(CameraModule module) {
1519        syncBottomBarColor();
1520        module.init(this, isSecureCamera(), isCaptureIntent());
1521        module.resume();
1522        module.onPreviewVisibilityChanged(!mFilmstripVisible);
1523    }
1524
1525    private void closeModule(CameraModule module) {
1526        module.pause();
1527    }
1528
1529    private void performDeletion() {
1530        if (!mPendingDeletion) {
1531            return;
1532        }
1533        hideUndoDeletionBar(false);
1534        mDataAdapter.executeDeletion();
1535    }
1536
1537    public void showUndoDeletionBar() {
1538        if (mPendingDeletion) {
1539            performDeletion();
1540        }
1541        Log.v(TAG, "showing undo bar");
1542        mPendingDeletion = true;
1543        if (mUndoDeletionBar == null) {
1544            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1545                    mAboveFilmstripControlLayout, true);
1546            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1547            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1548            button.setOnClickListener(new View.OnClickListener() {
1549                @Override
1550                public void onClick(View view) {
1551                    mDataAdapter.undoDataRemoval();
1552                    hideUndoDeletionBar(true);
1553                }
1554            });
1555            // Setting undo bar clickable to avoid touch events going through
1556            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1557            mUndoDeletionBar.setClickable(true);
1558            // When there is user interaction going on with the undo button, we
1559            // do not want to hide the undo bar.
1560            button.setOnTouchListener(new View.OnTouchListener() {
1561                @Override
1562                public boolean onTouch(View v, MotionEvent event) {
1563                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1564                        mIsUndoingDeletion = true;
1565                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1566                        mIsUndoingDeletion = false;
1567                    }
1568                    return false;
1569                }
1570            });
1571        }
1572        mUndoDeletionBar.setAlpha(0f);
1573        mUndoDeletionBar.setVisibility(View.VISIBLE);
1574        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1575    }
1576
1577    private void hideUndoDeletionBar(boolean withAnimation) {
1578        Log.v(TAG, "Hiding undo deletion bar");
1579        mPendingDeletion = false;
1580        if (mUndoDeletionBar != null) {
1581            if (withAnimation) {
1582                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1583                        .setListener(new Animator.AnimatorListener() {
1584                            @Override
1585                            public void onAnimationStart(Animator animation) {
1586                                // Do nothing.
1587                            }
1588
1589                            @Override
1590                            public void onAnimationEnd(Animator animation) {
1591                                mUndoDeletionBar.setVisibility(View.GONE);
1592                            }
1593
1594                            @Override
1595                            public void onAnimationCancel(Animator animation) {
1596                                // Do nothing.
1597                            }
1598
1599                            @Override
1600                            public void onAnimationRepeat(Animator animation) {
1601                                // Do nothing.
1602                            }
1603                        }).start();
1604            } else {
1605                mUndoDeletionBar.setVisibility(View.GONE);
1606            }
1607        }
1608    }
1609
1610    @Override
1611    public void onOrientationChanged(int orientation) {
1612        // We keep the last known orientation. So if the user first orient
1613        // the camera then point the camera to floor or sky, we still have
1614        // the correct orientation.
1615        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
1616            return;
1617        }
1618        mLastRawOrientation = orientation;
1619        if (mCurrentModule != null) {
1620            mCurrentModule.onOrientationChanged(orientation);
1621        }
1622    }
1623
1624    /**
1625     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1626     * capture intent.
1627     *
1628     * @param enable {@code true} to enable swipe.
1629     */
1630    public void setSwipingEnabled(boolean enable) {
1631        // TODO: Bring back the functionality.
1632        if (isCaptureIntent()) {
1633            //lockPreview(true);
1634        } else {
1635            //lockPreview(!enable);
1636        }
1637    }
1638
1639    // Accessor methods for getting latency times used in performance testing
1640    public long getAutoFocusTime() {
1641        return (mCurrentModule instanceof PhotoModule) ?
1642                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1643    }
1644
1645    public long getShutterLag() {
1646        return (mCurrentModule instanceof PhotoModule) ?
1647                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1648    }
1649
1650    public long getShutterToPictureDisplayedTime() {
1651        return (mCurrentModule instanceof PhotoModule) ?
1652                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1653    }
1654
1655    public long getPictureDisplayedToJpegCallbackTime() {
1656        return (mCurrentModule instanceof PhotoModule) ?
1657                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1658    }
1659
1660    public long getJpegCallbackFinishTime() {
1661        return (mCurrentModule instanceof PhotoModule) ?
1662                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1663    }
1664
1665    public long getCaptureStartTime() {
1666        return (mCurrentModule instanceof PhotoModule) ?
1667                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1668    }
1669
1670    public boolean isRecording() {
1671        return (mCurrentModule instanceof VideoModule) ?
1672                ((VideoModule) mCurrentModule).isRecording() : false;
1673    }
1674
1675    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
1676        return mCameraController;
1677    }
1678
1679    // For debugging purposes only.
1680    public CameraModule getCurrentModule() {
1681        return mCurrentModule;
1682    }
1683
1684    private void keepScreenOnForAWhile() {
1685        if (mKeepScreenOn) {
1686            return;
1687        }
1688        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1689        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1690        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
1691    }
1692
1693    private void resetScreenOn() {
1694        mKeepScreenOn = false;
1695        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1696        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1697    }
1698
1699    private void startGallery() {
1700        try {
1701            UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1702                    UsageStatistics.ACTION_GALLERY, null);
1703            launchActivityByIntent(IntentHelper.getGalleryIntent(CameraActivity.this));
1704        } catch (ActivityNotFoundException e) {
1705            Log.w(TAG, "Failed to launch gallery activity, closing");
1706        }
1707    }
1708
1709    private void setNfcBeamPushUriFromData(LocalData data) {
1710        final Uri uri = data.getContentUri();
1711        if (uri != Uri.EMPTY) {
1712            mNfcPushUris[0] = uri;
1713        } else {
1714            mNfcPushUris[0] = null;
1715        }
1716    }
1717
1718    /**
1719     * Updates the visibility of the filmstrip bottom controls.
1720     */
1721    private void updateUiByData(final int dataId) {
1722        if (isSecureCamera()) {
1723            // We cannot show buttons in secure camera since go to other
1724            // activities might create a security hole.
1725            return;
1726        }
1727
1728        final LocalData currentData = mDataAdapter.getLocalData(dataId);
1729        if (currentData == null) {
1730            Log.w(TAG, "Current data ID not found.");
1731            hideSessionProgress();
1732            return;
1733        }
1734
1735        setNfcBeamPushUriFromData(currentData);
1736
1737        /* Bottom controls. */
1738
1739        updateBottomControlsByData(currentData);
1740        if (!mDataAdapter.isMetadataUpdated(dataId)) {
1741            mDataAdapter.updateMetadata(dataId);
1742        }
1743    }
1744
1745    /**
1746     * Updates the bottom controls based on the data.
1747     */
1748    private void updateBottomControlsByData(final LocalData currentData) {
1749
1750        final CameraAppUI.BottomControls filmstripBottomControls =
1751                mCameraAppUI.getFilmstripBottomControls();
1752        filmstripBottomControls.setEditButtonVisibility(
1753                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
1754        filmstripBottomControls.setShareButtonVisibility(
1755                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
1756        filmstripBottomControls.setDeleteButtonVisibility(
1757                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
1758
1759        /* Progress bar */
1760
1761        Uri contentUri = currentData.getContentUri();
1762        CaptureSessionManager sessionManager = getServices()
1763                .getCaptureSessionManager();
1764        int sessionProgress = sessionManager.getSessionProgress(contentUri);
1765
1766        if (sessionProgress < 0) {
1767            hideSessionProgress();
1768        } else {
1769            CharSequence progressMessage = sessionManager
1770                    .getSessionProgressMessage(contentUri);
1771            showSessionProgress(progressMessage);
1772            updateSessionProgress(sessionProgress);
1773        }
1774
1775        /* View button */
1776
1777        // We need to add this to a separate DB.
1778        final int viewButtonVisibility;
1779        if (PanoramaMetadataLoader.isPanorama(currentData)) {
1780            viewButtonVisibility = CameraAppUI.BottomControls.VIEW_PHOTO_SPHERE;
1781        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
1782            viewButtonVisibility = CameraAppUI.BottomControls.VIEW_RGBZ;
1783        } else {
1784            viewButtonVisibility = CameraAppUI.BottomControls.VIEW_NONE;
1785        }
1786
1787        filmstripBottomControls.setTinyPlanetButtonVisibility(
1788                PanoramaMetadataLoader.isPanorama360(currentData));
1789        filmstripBottomControls.setViewButtonVisibility(viewButtonVisibility);
1790    }
1791}
1792