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