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