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