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