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