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