CameraActivity.java revision f79ecd56c1f985c8b43aa5969a124541567fb611
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.app.Dialog;
25import android.content.ActivityNotFoundException;
26import android.content.BroadcastReceiver;
27import android.content.ContentResolver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.SharedPreferences;
32import android.content.pm.ActivityInfo;
33import android.content.pm.PackageManager;
34import android.content.res.Configuration;
35import android.graphics.Bitmap;
36import android.graphics.BitmapFactory;
37import android.graphics.Matrix;
38import android.graphics.Point;
39import android.graphics.SurfaceTexture;
40import android.graphics.drawable.ColorDrawable;
41import android.graphics.drawable.Drawable;
42import android.net.Uri;
43import android.nfc.NfcAdapter;
44import android.nfc.NfcAdapter.CreateBeamUrisCallback;
45import android.nfc.NfcEvent;
46import android.os.AsyncTask;
47import android.os.Build;
48import android.os.Bundle;
49import android.os.Handler;
50import android.os.HandlerThread;
51import android.os.Looper;
52import android.os.Message;
53import android.preference.PreferenceManager;
54import android.provider.MediaStore;
55import android.provider.Settings;
56import android.util.CameraPerformanceTracker;
57import android.util.Log;
58import android.view.ContextMenu;
59import android.view.ContextMenu.ContextMenuInfo;
60import android.view.KeyEvent;
61import android.view.Menu;
62import android.view.MenuInflater;
63import android.view.MenuItem;
64import android.view.MotionEvent;
65import android.view.View;
66import android.view.ViewGroup;
67import android.view.Window;
68import android.view.WindowManager;
69import android.widget.FrameLayout;
70import android.widget.ImageView;
71import android.widget.ShareActionProvider;
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.LocalDataViewType;
86import com.android.camera.data.FixedLastDataAdapter;
87import com.android.camera.data.LocalData;
88import com.android.camera.data.LocalDataAdapter;
89import com.android.camera.data.LocalDataUtil;
90import com.android.camera.data.LocalMediaData;
91import com.android.camera.data.LocalMediaObserver;
92import com.android.camera.data.LocalSessionData;
93import com.android.camera.data.MediaDetails;
94import com.android.camera.data.PanoramaMetadataLoader;
95import com.android.camera.data.RgbzMetadataLoader;
96import com.android.camera.data.SimpleViewData;
97import com.android.camera.filmstrip.FilmstripContentPanel;
98import com.android.camera.filmstrip.FilmstripController;
99import com.android.camera.hardware.HardwareSpec;
100import com.android.camera.hardware.HardwareSpecImpl;
101import com.android.camera.module.ModuleController;
102import com.android.camera.module.ModulesInfo;
103import com.android.camera.session.CaptureSession;
104import com.android.camera.session.CaptureSessionManager;
105import com.android.camera.session.CaptureSessionManager.SessionListener;
106import com.android.camera.settings.CameraSettingsActivity;
107import com.android.camera.settings.SettingsManager;
108import com.android.camera.settings.SettingsManager.SettingsCapabilities;
109import com.android.camera.settings.SettingsUtil;
110import com.android.camera.tinyplanet.TinyPlanetFragment;
111import com.android.camera.ui.AbstractTutorialOverlay;
112import com.android.camera.ui.DetailsDialog;
113import com.android.camera.ui.MainActivityLayout;
114import com.android.camera.ui.ModeListView;
115import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
116import com.android.camera.ui.PreviewStatusListener;
117import com.android.camera.util.ApiHelper;
118import com.android.camera.util.Callback;
119import com.android.camera.util.CameraUtil;
120import com.android.camera.util.FeedbackHelper;
121import com.android.camera.util.GalleryHelper;
122import com.android.camera.util.GcamHelper;
123import com.android.camera.util.IntentHelper;
124import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
125import com.android.camera.util.ReleaseDialogHelper;
126import com.android.camera.util.UsageStatistics;
127import com.android.camera.widget.FilmstripView;
128import com.android.camera.widget.Preloader;
129import com.android.camera2.R;
130import com.bumptech.glide.Glide;
131import com.bumptech.glide.resize.ImageManager;
132import com.google.common.logging.eventprotos;
133import com.google.common.logging.eventprotos.CameraEvent.InteractionCause;
134import com.google.common.logging.eventprotos.NavigationChange;
135
136import java.io.File;
137import java.io.FileInputStream;
138import java.io.FileNotFoundException;
139import java.lang.ref.WeakReference;
140import java.util.ArrayList;
141import java.util.List;
142import java.util.concurrent.Executors;
143
144public class CameraActivity extends Activity
145        implements AppController, CameraManager.CameraOpenCallback,
146        ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
147        OrientationManager.OnOrientationChangeListener {
148
149    private static final String TAG = "CameraActivity";
150
151    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
152            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
153    public static final String ACTION_IMAGE_CAPTURE_SECURE =
154            "android.media.action.IMAGE_CAPTURE_SECURE";
155
156    // The intent extra for camera from secure lock screen. True if the gallery
157    // should only show newly captured pictures. sSecureAlbumId does not
158    // increment. This is used when switching between camera, camcorder, and
159    // panorama. If the extra is not set, it is in the normal camera mode.
160    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
161
162    /**
163     * Request code from an activity we started that indicated that we do not
164     * want to reset the view to the preview in onResume.
165     */
166    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
167
168    public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
169
170    private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
171    private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
172    private static final int MAX_PEEK_BITMAP_PIXELS = 1600000; // 1.6 * 4 MBs.
173    /** Load metadata for 10 items ahead of our current. */
174    private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
175
176    /** Should be used wherever a context is needed. */
177    private Context mAppContext;
178
179    /**
180     * Whether onResume should reset the view to the preview.
181     */
182    private boolean mResetToPreviewOnResume = true;
183
184    /**
185     * This data adapter is used by FilmStripView.
186     */
187    private LocalDataAdapter mDataAdapter;
188
189    /**
190     * TODO: This should be moved to the app level.
191     */
192    private SettingsManager mSettingsManager;
193
194    private ModeListView mModeListView;
195    private boolean mModeListVisible = false;
196    private int mCurrentModeIndex;
197    private CameraModule mCurrentModule;
198    private ModuleManagerImpl mModuleManager;
199    private FrameLayout mAboveFilmstripControlLayout;
200    private FilmstripController mFilmstripController;
201    private boolean mFilmstripVisible;
202    /** Whether the filmstrip fully covers the preview. */
203    private boolean mFilmstripCoversPreview = false;
204    private int mResultCodeForTesting;
205    private Intent mResultDataForTesting;
206    private OnScreenHint mStorageHint;
207    private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
208    private boolean mAutoRotateScreen;
209    private boolean mSecureCamera;
210    private int mLastRawOrientation;
211    private OrientationManagerImpl mOrientationManager;
212    private LocationManager mLocationManager;
213    private ButtonManager mButtonManager;
214    private Handler mMainHandler;
215    private PanoramaViewHelper mPanoramaViewHelper;
216    private ActionBar mActionBar;
217    private ViewGroup mUndoDeletionBar;
218    private boolean mIsUndoingDeletion = false;
219
220    private final Uri[] mNfcPushUris = new Uri[1];
221
222    private LocalMediaObserver mLocalImagesObserver;
223    private LocalMediaObserver mLocalVideosObserver;
224
225    private boolean mPendingDeletion = false;
226
227    private CameraController mCameraController;
228    private boolean mPaused;
229    private CameraAppUI mCameraAppUI;
230
231    private PeekAnimationHandler mPeekAnimationHandler;
232    private HandlerThread mPeekAnimationThread;
233
234    private FeedbackHelper mFeedbackHelper;
235
236    private Intent mGalleryIntent;
237    private long mOnCreateTime;
238
239    private Menu mActionBarMenu;
240    private Preloader<Integer, AsyncTask> mPreloader;
241
242    @Override
243    public CameraAppUI getCameraAppUI() {
244        return mCameraAppUI;
245    }
246
247    // close activity when screen turns off
248    private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
249        @Override
250        public void onReceive(Context context, Intent intent) {
251            finish();
252        }
253    };
254
255    /**
256     * Whether the screen is kept turned on.
257     */
258    private boolean mKeepScreenOn;
259    private int mLastLayoutOrientation;
260    private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
261            new CameraAppUI.BottomPanel.Listener() {
262
263                /**
264                 * If the current photo is a photo sphere, this will launch the
265                 * Photo Sphere panorama viewer.
266                 */
267                @Override
268                public void onExternalViewer() {
269                    if (mPanoramaViewHelper == null) {
270                        return;
271                    }
272                    final LocalData data = getCurrentLocalData();
273                    if (data == null) {
274                        return;
275                    }
276                    final Uri contentUri = data.getUri();
277                    if (contentUri == Uri.EMPTY) {
278                        return;
279                    }
280
281                    if (PanoramaMetadataLoader.isPanoramaAndUseViewer(data)) {
282                        mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
283                    } else if (RgbzMetadataLoader.hasRGBZData(data)) {
284                        mPanoramaViewHelper.showRgbz(contentUri);
285                    }
286                }
287
288                @Override
289                public void onEdit() {
290                    LocalData data = getCurrentLocalData();
291                    if (data == null) {
292                        return;
293                    }
294                    launchEditor(data);
295                }
296
297                @Override
298                public void onTinyPlanet() {
299                    LocalData data = getCurrentLocalData();
300                    if (data == null) {
301                        return;
302                    }
303                    launchTinyPlanetEditor(data);
304                }
305
306                @Override
307                public void onDelete() {
308                    final int currentDataId = getCurrentDataId();
309                    UsageStatistics.photoInteraction(
310                            UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
311                            eventprotos.CameraEvent.InteractionType.DELETE,
312                            InteractionCause.BUTTON);
313                    removeData(currentDataId);
314                }
315
316                @Override
317                public void onShare() {
318                    final LocalData data = getCurrentLocalData();
319
320                    // If applicable, show release information before this item
321                    // is shared.
322                    if (PanoramaMetadataLoader.isPanorama(data)
323                            || RgbzMetadataLoader.hasRGBZData(data)) {
324                        ReleaseDialogHelper.showReleaseInfoDialog(CameraActivity.this,
325                                new Callback<Void>() {
326                                    @Override
327                                    public void onCallback(Void result) {
328                                        share(data);
329                                    }
330                                });
331                    } else {
332                        share(data);
333                    }
334                }
335
336                private void share(LocalData data) {
337                    Intent shareIntent = getShareIntentByData(data);
338                    if (shareIntent != null) {
339                        try {
340                            launchActivityByIntent(shareIntent);
341                            mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
342                        } catch (ActivityNotFoundException ex) {
343                            // Nothing.
344                        }
345                    }
346                }
347
348                private int getCurrentDataId() {
349                    return mFilmstripController.getCurrentId();
350                }
351
352                private LocalData getCurrentLocalData() {
353                    return mDataAdapter.getLocalData(getCurrentDataId());
354                }
355
356                /**
357                 * Sets up the share intent and NFC properly according to the
358                 * data.
359                 *
360                 * @param data The data to be shared.
361                 */
362                private Intent getShareIntentByData(final LocalData data) {
363                    Intent intent = null;
364                    final Uri contentUri = data.getUri();
365                    if (PanoramaMetadataLoader.isPanorama360(data) &&
366                            data.getUri() != Uri.EMPTY) {
367                        intent = new Intent(Intent.ACTION_SEND);
368                        intent.setType("application/vnd.google.panorama360+jpg");
369                        intent.putExtra(Intent.EXTRA_STREAM, contentUri);
370                    } else if (data.isDataActionSupported(LocalData.DATA_ACTION_SHARE)) {
371                        final String mimeType = data.getMimeType();
372                        intent = getShareIntentFromType(mimeType);
373                        if (intent != null) {
374                            intent.putExtra(Intent.EXTRA_STREAM, contentUri);
375                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
376                        }
377                        intent = Intent.createChooser(intent, null);
378                    }
379                    return intent;
380                }
381
382                /**
383                 * Get the share intent according to the mimeType
384                 *
385                 * @param mimeType The mimeType of current data.
386                 * @return the video/image's ShareIntent or null if mimeType is
387                 *         invalid.
388                 */
389                private Intent getShareIntentFromType(String mimeType) {
390                    // Lazily create the intent object.
391                    Intent intent = new Intent(Intent.ACTION_SEND);
392                    if (mimeType.startsWith("video/")) {
393                        intent.setType("video/*");
394                    } else {
395                        if (mimeType.startsWith("image/")) {
396                            intent.setType("image/*");
397                        } else {
398                            Log.w(TAG, "unsupported mimeType " + mimeType);
399                        }
400                    }
401                    return intent;
402                }
403
404                @Override
405                public void onProgressErrorClicked() {
406                    LocalData data = getCurrentLocalData();
407                    getServices().getCaptureSessionManager().removeErrorMessage(
408                            data.getUri());
409                    updateBottomControlsByData(data);
410                }
411            };
412
413    private ComboPreferences mPreferences;
414
415    @Override
416    public void onCameraOpened(CameraManager.CameraProxy camera) {
417        /**
418         * The current UI requires that the flash option visibility in front-facing
419         * camera be
420         *   * disabled if back facing camera supports flash
421         *   * hidden if back facing camera does not support flash
422         * We save whether back facing camera supports flash because we cannot get
423         * this in front facing camera without a camera switch.
424         *
425         * If this preference is cleared, we also need to clear the camera facing
426         * setting so we default to opening the camera in back facing camera, and
427         * can save this flash support value again.
428         */
429        if (!mSettingsManager.isSet(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA)) {
430            HardwareSpec hardware = new HardwareSpecImpl(camera.getParameters());
431            mSettingsManager.setBoolean(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA,
432                hardware.isFlashSupported());
433        }
434
435        if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
436            // We shouldn't be here. Just close the camera and leave.
437            camera.release(false);
438            throw new IllegalStateException("Camera opened but the module shouldn't be " +
439                    "requesting");
440        }
441        if (mCurrentModule != null) {
442            SettingsCapabilities capabilities =
443                    SettingsUtil.getSettingsCapabilities(camera);
444            mSettingsManager.changeCamera(camera.getCameraId(), capabilities);
445            mCurrentModule.onCameraAvailable(camera);
446        }
447        mCameraAppUI.onChangeCamera();
448    }
449
450    @Override
451    public void onCameraDisabled(int cameraId) {
452        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.SECURITY);
453
454        CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
455    }
456
457    @Override
458    public void onDeviceOpenFailure(int cameraId) {
459        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.OPEN_FAILURE);
460
461        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
462    }
463
464    @Override
465    public void onDeviceOpenedAlready(int cameraId) {
466        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
467    }
468
469    @Override
470    public void onReconnectionFailure(CameraManager mgr) {
471        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE);
472
473        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
474    }
475
476    private static class MainHandler extends Handler {
477        final WeakReference<CameraActivity> mActivity;
478
479        public MainHandler(CameraActivity activity, Looper looper) {
480            super(looper);
481            mActivity = new WeakReference<CameraActivity>(activity);
482        }
483
484        @Override
485        public void handleMessage(Message msg) {
486            CameraActivity activity = mActivity.get();
487            if (activity == null) {
488                return;
489            }
490            switch (msg.what) {
491
492                case MSG_CLEAR_SCREEN_ON_FLAG: {
493                    if (!activity.mPaused) {
494                        activity.getWindow().clearFlags(
495                                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
496                    }
497                    break;
498                }
499            }
500        }
501    }
502
503    private String fileNameFromDataID(int dataID) {
504        final LocalData localData = mDataAdapter.getLocalData(dataID);
505
506        File localFile = new File(localData.getPath());
507        return localFile.getName();
508    }
509
510    private final FilmstripContentPanel.Listener mFilmstripListener =
511            new FilmstripContentPanel.Listener() {
512
513                @Override
514                public void onSwipeOut() {
515                    UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
516                            eventprotos.CameraEvent.InteractionCause.SWIPE_RIGHT);
517                }
518
519                @Override
520                public void onSwipeOutBegin() {
521                    mActionBar.hide();
522                    mFilmstripCoversPreview = false;
523                    updatePreviewVisibility();
524                }
525
526                @Override
527                public void onFilmstripHidden() {
528                    mFilmstripVisible = false;
529                    CameraActivity.this.setFilmstripUiVisibility(false);
530                    // When the user hide the filmstrip (either swipe out or
531                    // tap on back key) we move to the first item so next time
532                    // when the user swipe in the filmstrip, the most recent
533                    // one is shown.
534                    mFilmstripController.goToFirstItem();
535                }
536
537                @Override
538                public void onFilmstripShown() {
539                    mFilmstripVisible = true;
540                    updateUiByData(mFilmstripController.getCurrentId());
541                }
542
543                @Override
544                public void onFocusedDataLongPressed(int dataId) {
545                    // Do nothing.
546                }
547
548                @Override
549                public void onFocusedDataPromoted(int dataID) {
550                    UsageStatistics.photoInteraction(
551                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
552                            eventprotos.CameraEvent.InteractionType.DELETE,
553                            InteractionCause.SWIPE_UP);
554
555                    removeData(dataID);
556                }
557
558                @Override
559                public void onFocusedDataDemoted(int dataID) {
560                    UsageStatistics.photoInteraction(
561                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
562                            eventprotos.CameraEvent.InteractionType.DELETE,
563                            InteractionCause.SWIPE_DOWN);
564
565                    removeData(dataID);
566                }
567
568                @Override
569                public void onEnterFullScreenUiShown(int dataId) {
570                    if (mFilmstripVisible) {
571                        CameraActivity.this.setFilmstripUiVisibility(true);
572                    }
573                }
574
575                @Override
576                public void onLeaveFullScreenUiShown(int dataId) {
577                    // Do nothing.
578                }
579
580                @Override
581                public void onEnterFullScreenUiHidden(int dataId) {
582                    if (mFilmstripVisible) {
583                        CameraActivity.this.setFilmstripUiVisibility(false);
584                    }
585                }
586
587                @Override
588                public void onLeaveFullScreenUiHidden(int dataId) {
589                    // Do nothing.
590                }
591
592                @Override
593                public void onEnterFilmstrip(int dataId) {
594                    if (mFilmstripVisible) {
595                        CameraActivity.this.setFilmstripUiVisibility(true);
596                    }
597                }
598
599                @Override
600                public void onLeaveFilmstrip(int dataId) {
601                    // Do nothing.
602                }
603
604                @Override
605                public void onDataReloaded() {
606                    if (!mFilmstripVisible) {
607                        return;
608                    }
609                    updateUiByData(mFilmstripController.getCurrentId());
610                }
611
612                @Override
613                public void onDataUpdated(int dataId) {
614                    if (!mFilmstripVisible) {
615                        return;
616                    }
617                    updateUiByData(mFilmstripController.getCurrentId());
618                }
619
620                @Override
621                public void onEnterZoomView(int dataID) {
622                    if (mFilmstripVisible) {
623                        CameraActivity.this.setFilmstripUiVisibility(false);
624                    }
625                }
626
627                @Override
628                public void onDataFocusChanged(final int prevDataId, final int newDataId) {
629                    if (!mFilmstripVisible) {
630                        return;
631                    }
632                    // TODO: This callback is UI event callback, should always
633                    // happen on UI thread. Find the reason for this
634                    // runOnUiThread() and fix it.
635                    runOnUiThread(new Runnable() {
636                        @Override
637                        public void run() {
638                            updateUiByData(newDataId);
639                        }
640                    });
641                }
642
643                @Override
644                public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
645                    mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
646                }
647            };
648
649    private final LocalDataAdapter.LocalDataListener mLocalDataListener =
650            new LocalDataAdapter.LocalDataListener() {
651                @Override
652                public void onMetadataUpdated(List<Integer> updatedData) {
653                    int currentDataId = mFilmstripController.getCurrentId();
654                    for (Integer dataId : updatedData) {
655                        if (dataId == currentDataId) {
656                            updateBottomControlsByData(mDataAdapter.getLocalData(dataId));
657                        }
658                    }
659                }
660            };
661
662    public void gotoGallery() {
663        UsageStatistics.changeScreen(NavigationChange.Mode.FILMSTRIP,
664                InteractionCause.BUTTON);
665
666        mFilmstripController.goToNextItem();
667    }
668
669    /**
670     * If 'visible' is false, this hides the action bar and switches the
671     * filmstrip UI to lights-out mode.
672     *
673     * @param visible is false, this hides the action bar and switches the
674     *            filmstrip UI to lights-out mode.
675     */
676    private void setFilmstripUiVisibility(boolean visible) {
677        int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
678        int newSystemUIVisibility = (visible ? View.SYSTEM_UI_FLAG_VISIBLE
679                : View.SYSTEM_UI_FLAG_FULLSCREEN);
680        if (newSystemUIVisibility != currentSystemUIVisibility) {
681            mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
682        }
683
684        mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
685        if (visible != mActionBar.isShowing()) {
686            if (visible) {
687                mActionBar.show();
688            } else {
689                mActionBar.hide();
690            }
691        }
692        mFilmstripCoversPreview = visible;
693        updatePreviewVisibility();
694    }
695
696    private void hideSessionProgress() {
697        mCameraAppUI.getFilmstripBottomControls().hideProgress();
698    }
699
700    private void showSessionProgress(CharSequence message) {
701        CameraAppUI.BottomPanel controls =  mCameraAppUI.getFilmstripBottomControls();
702        controls.setProgressText(message);
703        controls.hideControls();
704        controls.hideProgressError();
705        controls.showProgress();
706    }
707
708    private void showProcessError(CharSequence message) {
709        mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
710    }
711
712    private void updateSessionProgress(int progress) {
713        mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
714    }
715
716    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
717    private void setupNfcBeamPush() {
718        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
719        if (adapter == null) {
720            return;
721        }
722
723        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
724            // Disable beaming
725            adapter.setNdefPushMessage(null, CameraActivity.this);
726            return;
727        }
728
729        adapter.setBeamPushUris(null, CameraActivity.this);
730        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
731            @Override
732            public Uri[] createBeamUris(NfcEvent event) {
733                return mNfcPushUris;
734            }
735        }, CameraActivity.this);
736    }
737
738    @Override
739    public void onMenuVisibilityChanged(boolean isVisible) {
740        // TODO: Remove this or bring back the original implementation: cancel
741        // auto-hide actionbar.
742    }
743
744    @Override
745    public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
746        int currentDataId = mFilmstripController.getCurrentId();
747        if (currentDataId < 0) {
748            return false;
749        }
750        UsageStatistics.photoInteraction(
751                UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
752                eventprotos.CameraEvent.InteractionType.SHARE,
753                InteractionCause.BUTTON);
754        // TODO add intent.getComponent().getPackageName()
755        return true;
756    }
757
758    // Note: All callbacks come back on the main thread.
759    private final SessionListener mSessionListener =
760            new SessionListener() {
761                @Override
762                public void onSessionQueued(final Uri uri) {
763                    if (!Storage.isSessionUri(uri)) {
764                        return;
765                    }
766                    LocalSessionData newData = new LocalSessionData(uri);
767                    mDataAdapter.addData(newData);
768                }
769
770                @Override
771                public void onSessionDone(final Uri sessionUri) {
772                    Log.v(TAG, "onSessionDone:" + sessionUri);
773                    Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
774                    if (contentUri == null) {
775                        mDataAdapter.refresh(sessionUri);
776                        return;
777                    }
778                    LocalData newData = LocalMediaData.PhotoData.fromContentUri(
779                            getContentResolver(), contentUri);
780
781                    final int pos = mDataAdapter.findDataByContentUri(sessionUri);
782                    if (pos == -1) {
783                        // We do not have a placeholder for this image, perhaps due to the
784                        // activity crashing or being killed.
785                        mDataAdapter.addData(newData);
786                    }  else  {
787                        mDataAdapter.updateData(pos, newData);
788                    }
789                }
790
791                @Override
792                public void onSessionProgress(final Uri uri, final int progress) {
793                    if (progress < 0) {
794                        // Do nothing, there is no task for this URI.
795                        return;
796                    }
797                    int currentDataId = mFilmstripController.getCurrentId();
798                    if (currentDataId == -1) {
799                        return;
800                    }
801                    if (uri.equals(
802                            mDataAdapter.getLocalData(currentDataId).getUri())) {
803                        updateSessionProgress(progress);
804                    }
805                }
806
807                @Override
808                public void onSessionUpdated(Uri uri) {
809                    mDataAdapter.refresh(uri);
810                }
811
812                @Override
813                public void onSessionPreviewAvailable(Uri uri) {
814                    mDataAdapter.refresh(uri);
815                    int dataId = mDataAdapter.findDataByContentUri(uri);
816                    if (dataId != -1) {
817                        startPeekAnimation(mDataAdapter.getLocalData(dataId));
818                    }
819                }
820
821                @Override
822                public void onSessionFailed(Uri uri, CharSequence reason) {
823                    Log.v(TAG, "onSessionFailed:" + uri);
824
825                    int failedDataId = mDataAdapter.findDataByContentUri(uri);
826                    int currentDataId = mFilmstripController.getCurrentId();
827
828                    if (currentDataId == failedDataId) {
829                        updateSessionProgress(0);
830                        showProcessError(reason);
831                    }
832                    // HERE
833                    mDataAdapter.refresh(uri);
834                }
835            };
836
837    @Override
838    public Context getAndroidContext() {
839        return mAppContext;
840    }
841
842    @Override
843    public void launchActivityByIntent(Intent intent) {
844        startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
845    }
846
847    @Override
848    public int getCurrentModuleIndex() {
849        return mCurrentModeIndex;
850    }
851
852    @Override
853    public ModuleController getCurrentModuleController() {
854        return mCurrentModule;
855    }
856
857    @Override
858    public int getQuickSwitchToModuleId(int currentModuleIndex) {
859        return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
860                mAppContext);
861    }
862
863    @Override
864    public SurfaceTexture getPreviewBuffer() {
865        // TODO: implement this
866        return null;
867    }
868
869    @Override
870    public void onPreviewReadyToStart() {
871        mCameraAppUI.onPreviewReadyToStart();
872    }
873
874    @Override
875    public void onPreviewStarted() {
876        mCameraAppUI.onPreviewStarted();
877    }
878
879    @Override
880    public void addPreviewAreaSizeChangedListener(
881            PreviewStatusListener.PreviewAreaChangedListener listener) {
882        mCameraAppUI.addPreviewAreaChangedListener(listener);
883    }
884
885    @Override
886    public void removePreviewAreaSizeChangedListener(
887            PreviewStatusListener.PreviewAreaChangedListener listener) {
888        mCameraAppUI.removePreviewAreaChangedListener(listener);
889    }
890
891    @Override
892    public void setupOneShotPreviewListener() {
893        mCameraController.setOneShotPreviewCallback(mMainHandler,
894                new CameraManager.CameraPreviewDataCallback() {
895                    @Override
896                    public void onPreviewFrame(byte[] data, CameraManager.CameraProxy camera) {
897                        mCurrentModule.onPreviewInitialDataReceived();
898                        mCameraAppUI.onNewPreviewFrame();
899                    }
900                });
901    }
902
903    @Override
904    public void updatePreviewAspectRatio(float aspectRatio) {
905        mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
906    }
907
908    @Override
909    public void updatePreviewTransform(Matrix matrix) {
910        mCameraAppUI.updatePreviewTransform(matrix);
911    }
912
913    @Override
914    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
915        mCameraAppUI.setPreviewStatusListener(previewStatusListener);
916    }
917
918    @Override
919    public FrameLayout getModuleLayoutRoot() {
920        return mCameraAppUI.getModuleRootView();
921    }
922
923    @Override
924    public void setShutterEventsListener(ShutterEventsListener listener) {
925        // TODO: implement this
926    }
927
928    @Override
929    public void setShutterEnabled(boolean enabled) {
930        // TODO: implement this
931    }
932
933    @Override
934    public boolean isShutterEnabled() {
935        // TODO: implement this
936        return false;
937    }
938
939    @Override
940    public void startPreCaptureAnimation() {
941        mCameraAppUI.startPreCaptureAnimation();
942    }
943
944    @Override
945    public void cancelPreCaptureAnimation() {
946        // TODO: implement this
947    }
948
949    @Override
950    public void startPostCaptureAnimation() {
951        // TODO: implement this
952    }
953
954    @Override
955    public void startPostCaptureAnimation(Bitmap thumbnail) {
956        // TODO: implement this
957    }
958
959    @Override
960    public void cancelPostCaptureAnimation() {
961        // TODO: implement this
962    }
963
964    @Override
965    public OrientationManager getOrientationManager() {
966        return mOrientationManager;
967    }
968
969    @Override
970    public LocationManager getLocationManager() {
971        return mLocationManager;
972    }
973
974    @Override
975    public void lockOrientation() {
976        if (mOrientationManager != null) {
977            mOrientationManager.lockOrientation();
978        }
979    }
980
981    @Override
982    public void unlockOrientation() {
983        if (mOrientationManager != null) {
984            mOrientationManager.unlockOrientation();
985        }
986    }
987
988    /**
989     * Starts the filmstrip peek animation if the filmstrip is not visible.
990     * Only {@link LocalData#LOCAL_IMAGE}, {@link
991     * LocalData#LOCAL_IN_PROGRESS_DATA} and {@link
992     * LocalData#LOCAL_VIDEO} are supported.
993     *
994     * @param data The data to peek.
995     */
996    private void startPeekAnimation(final LocalData data) {
997        if (mFilmstripVisible || mPeekAnimationHandler == null) {
998            return;
999        }
1000
1001        int dataType = data.getLocalDataType();
1002        if (dataType != LocalData.LOCAL_IMAGE && dataType != LocalData.LOCAL_IN_PROGRESS_DATA &&
1003                dataType != LocalData.LOCAL_VIDEO) {
1004            return;
1005        }
1006
1007        mPeekAnimationHandler.startDecodingJob(data, new Callback<Bitmap>() {
1008            @Override
1009            public void onCallback(Bitmap result) {
1010                mCameraAppUI.startPeekAnimation(result, true);
1011            }
1012        });
1013    }
1014
1015    @Override
1016    public void notifyNewMedia(Uri uri) {
1017        ContentResolver cr = getContentResolver();
1018        String mimeType = cr.getType(uri);
1019        if (LocalDataUtil.isMimeTypeVideo(mimeType)) {
1020            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1021            LocalData newData = LocalMediaData.VideoData.fromContentUri(getContentResolver(), uri);
1022            if (newData == null) {
1023                Log.e(TAG, "Can't find video data in content resolver:" + uri);
1024                return;
1025            }
1026            if (mDataAdapter.addData(newData)) {
1027                startPeekAnimation(newData);
1028            }
1029        } else if (LocalDataUtil.isMimeTypeImage(mimeType)) {
1030            CameraUtil.broadcastNewPicture(mAppContext, uri);
1031            LocalData newData = LocalMediaData.PhotoData.fromContentUri(getContentResolver(), uri);
1032            if (newData == null) {
1033                Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1034                return;
1035            }
1036            if (mDataAdapter.addData(newData)) {
1037                startPeekAnimation(newData);
1038            }
1039        } else {
1040            android.util.Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1041        }
1042    }
1043
1044    @Override
1045    public void enableKeepScreenOn(boolean enabled) {
1046        if (mPaused) {
1047            return;
1048        }
1049
1050        mKeepScreenOn = enabled;
1051        if (mKeepScreenOn) {
1052            mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1053            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1054        } else {
1055            keepScreenOnForAWhile();
1056        }
1057    }
1058
1059    @Override
1060    public CameraProvider getCameraProvider() {
1061        return mCameraController;
1062    }
1063
1064    private void removeData(int dataID) {
1065        mDataAdapter.removeData(dataID);
1066        if (mDataAdapter.getTotalNumber() > 1) {
1067            showUndoDeletionBar();
1068        } else {
1069            // If camera preview is the only view left in filmstrip,
1070            // no need to show undo bar.
1071            mPendingDeletion = true;
1072            performDeletion();
1073            if (mFilmstripVisible) {
1074                mCameraAppUI.getFilmstripContentPanel().animateHide();
1075            }
1076        }
1077    }
1078
1079    @Override
1080    public boolean onOptionsItemSelected(MenuItem item) {
1081        // Handle presses on the action bar items
1082        switch (item.getItemId()) {
1083            case android.R.id.home:
1084                if (mFilmstripVisible && startGallery()) {
1085                    return true;
1086                }
1087                onBackPressed();
1088                return true;
1089            case R.id.action_details:
1090                showDetailsDialog(mFilmstripController.getCurrentId());
1091                return true;
1092            default:
1093                return super.onOptionsItemSelected(item);
1094        }
1095    }
1096
1097    private boolean isCaptureIntent() {
1098        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1099                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1100                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1101            return true;
1102        } else {
1103            return false;
1104        }
1105    }
1106
1107    private final SettingsManager.StrictUpgradeCallback mStrictUpgradeCallback
1108        = new SettingsManager.StrictUpgradeCallback() {
1109                @Override
1110                public void upgrade(SettingsManager settingsManager, int version) {
1111                    // Show the location dialog on upgrade if
1112                    //  (a) the user has never set this option (status quo).
1113                    //  (b) the user opt'ed out previously.
1114                    if (settingsManager.isSet(SettingsManager.SETTING_RECORD_LOCATION)) {
1115                        // Location is set in the source file defined for this setting.
1116                        // Remove the setting if the value is false to launch the dialog.
1117                        if (!settingsManager.getBoolean(SettingsManager.SETTING_RECORD_LOCATION)) {
1118                            settingsManager.remove(SettingsManager.SETTING_RECORD_LOCATION);
1119                        }
1120                    } else {
1121                        // Location is not set, check to see if we're upgrading from
1122                        // a different source file.
1123                        if (settingsManager.isSet(SettingsManager.SETTING_RECORD_LOCATION,
1124                                                  SettingsManager.SOURCE_GLOBAL)) {
1125                            boolean location = settingsManager.getBoolean(
1126                                SettingsManager.SETTING_RECORD_LOCATION,
1127                                SettingsManager.SOURCE_GLOBAL);
1128                            if (location) {
1129                                // Set the old setting only if the value is true, to prevent
1130                                // launching the dialog.
1131                                settingsManager.setBoolean(
1132                                    SettingsManager.SETTING_RECORD_LOCATION, location);
1133                            }
1134                        }
1135                    }
1136
1137                    settingsManager.remove(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1138                }
1139            };
1140
1141    private final CameraManager.CameraExceptionCallback mCameraDefaultExceptionCallback
1142        = new CameraManager.CameraExceptionCallback() {
1143                @Override
1144                public void onCameraException(RuntimeException e) {
1145                    Log.d(TAG, "Camera Exception", e);
1146                    CameraUtil.showErrorAndFinish(CameraActivity.this,
1147                            R.string.cannot_connect_camera);
1148                }
1149            };
1150
1151    @Override
1152    public void onCreate(Bundle state) {
1153        super.onCreate(state);
1154        final Glide glide = Glide.get();
1155        if (!glide.isImageManagerSet()) {
1156            // We load exclusively large images, so we want fewer threads to minimize jank.
1157            glide.setImageManager(new ImageManager.Builder(getApplicationContext())
1158                    .setResizeService(Executors.newSingleThreadExecutor()));
1159        }
1160        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1161        mOnCreateTime = System.currentTimeMillis();
1162        mAppContext = getApplicationContext();
1163        GcamHelper.init(getContentResolver());
1164
1165        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1166        setContentView(R.layout.activity_main);
1167        mActionBar = getActionBar();
1168        mActionBar.addOnMenuVisibilityListener(this);
1169        mMainHandler = new MainHandler(this, getMainLooper());
1170        mCameraController =
1171                new CameraController(mAppContext, this, mMainHandler,
1172                        CameraManagerFactory.getAndroidCameraManager());
1173        mCameraController.setCameraDefaultExceptionCallback(mCameraDefaultExceptionCallback,
1174                mMainHandler);
1175
1176        mPreferences = new ComboPreferences(mAppContext);
1177
1178        mSettingsManager = new SettingsManager(mAppContext, this,
1179                mCameraController.getNumberOfCameras(), mStrictUpgradeCallback);
1180
1181        // Remove this after we get rid of ComboPreferences.
1182        int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID));
1183        mPreferences.setLocalId(mAppContext, cameraId);
1184        CameraSettings.upgradeGlobalPreferences(mPreferences,
1185                mCameraController.getNumberOfCameras());
1186        // TODO: Try to move all the resources allocation to happen as soon as
1187        // possible so we can call module.init() at the earliest time.
1188        mModuleManager = new ModuleManagerImpl();
1189        ModulesInfo.setupModules(mAppContext, mModuleManager);
1190
1191        mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1192        mModeListView.init(mModuleManager.getSupportedModeIndexList());
1193        if (ApiHelper.HAS_ROTATION_ANIMATION) {
1194            setRotationAnimation();
1195        }
1196        mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1197            @Override
1198            public void onVisibilityChanged(boolean visible) {
1199                mModeListVisible = visible;
1200                updatePreviewVisibility();
1201            }
1202        });
1203
1204        // Check if this is in the secure camera mode.
1205        Intent intent = getIntent();
1206        String action = intent.getAction();
1207        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1208                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1209            mSecureCamera = true;
1210        } else {
1211            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1212        }
1213
1214        if (mSecureCamera) {
1215            // Foreground event caused by lock screen startup.
1216            // It is necessary to log this in onCreate, to avoid the
1217            // onResume->onPause->onResume sequence.
1218            UsageStatistics.foregrounded(
1219                    eventprotos.ForegroundEvent.ForegroundSource.LOCK_SCREEN);
1220
1221            // Change the window flags so that secure camera can show when
1222            // locked
1223            Window win = getWindow();
1224            WindowManager.LayoutParams params = win.getAttributes();
1225            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1226            win.setAttributes(params);
1227
1228            // Filter for screen off so that we can finish secure camera
1229            // activity
1230            // when screen is off.
1231            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1232            registerReceiver(mScreenOffReceiver, filter);
1233        }
1234        mCameraAppUI = new CameraAppUI(this,
1235                (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1236
1237        mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1238
1239        mAboveFilmstripControlLayout =
1240                (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1241
1242        // Add the session listener so we can track the session progress
1243        // updates.
1244        getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1245        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1246        mFilmstripController.setImageGap(
1247                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1248        mPanoramaViewHelper = new PanoramaViewHelper(this);
1249        mPanoramaViewHelper.onCreate();
1250        // Set up the camera preview first so the preview shows up ASAP.
1251        mDataAdapter = new CameraDataAdapter(mAppContext, R.color.photo_placeholder);
1252        mDataAdapter.setLocalDataListener(mLocalDataListener);
1253
1254        mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1255                mDataAdapter);
1256
1257        mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1258
1259        mLocationManager = new LocationManager(mAppContext);
1260
1261        int modeIndex = -1;
1262        int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1263        int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1264        int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1265        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1266                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1267            modeIndex = videoIndex;
1268        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1269                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())) {
1270            // TODO: synchronize mode options with photo module without losing
1271            // HDR+ preferences.
1272            modeIndex = photoIndex;
1273        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1274                        .getAction())
1275                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1276            modeIndex = mSettingsManager.getInt(
1277                SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX);
1278        } else {
1279            // If the activity has not been started using an explicit intent,
1280            // read the module index from the last time the user changed modes
1281            modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1282            if ((modeIndex == gcamIndex &&
1283                    !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1284                modeIndex = photoIndex;
1285            }
1286        }
1287
1288        mOrientationManager = new OrientationManagerImpl(this);
1289        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1290
1291        setModuleFromModeIndex(modeIndex);
1292        mCameraAppUI.prepareModuleUI();
1293        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1294
1295        if (!mSecureCamera) {
1296            mFilmstripController.setDataAdapter(mDataAdapter);
1297            if (!isCaptureIntent()) {
1298                mDataAdapter.requestLoad();
1299            }
1300        } else {
1301            // Put a lock placeholder as the last image by setting its date to
1302            // 0.
1303            ImageView v = (ImageView) getLayoutInflater().inflate(
1304                    R.layout.secure_album_placeholder, null);
1305            v.setOnClickListener(new View.OnClickListener() {
1306                @Override
1307                public void onClick(View view) {
1308                    UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY,
1309                            InteractionCause.BUTTON);
1310                    startGallery();
1311                    finish();
1312                }
1313            });
1314            mDataAdapter = new FixedLastDataAdapter(
1315                    mAppContext,
1316                    mDataAdapter,
1317                    new SimpleViewData(
1318                            v,
1319                            LocalDataViewType.SECURE_ALBUM_PLACEHOLDER,
1320                            v.getDrawable().getIntrinsicWidth(),
1321                            v.getDrawable().getIntrinsicHeight(),
1322                            0, 0));
1323            // Flush out all the original data.
1324            mDataAdapter.flush();
1325            mFilmstripController.setDataAdapter(mDataAdapter);
1326        }
1327
1328        setupNfcBeamPush();
1329
1330        mLocalImagesObserver = new LocalMediaObserver();
1331        mLocalVideosObserver = new LocalMediaObserver();
1332
1333        getContentResolver().registerContentObserver(
1334                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1335                mLocalImagesObserver);
1336        getContentResolver().registerContentObserver(
1337                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1338                mLocalVideosObserver);
1339        if (FeedbackHelper.feedbackAvailable()) {
1340            mFeedbackHelper = new FeedbackHelper(mAppContext);
1341        }
1342    }
1343
1344    /**
1345     * Call this whenever the mode drawer or filmstrip change the visibility
1346     * state.
1347     */
1348    private void updatePreviewVisibility() {
1349        if (mCurrentModule == null) {
1350            return;
1351        }
1352
1353        int visibility = getPreviewVisibility();
1354        updatePreviewRendering(visibility);
1355        mCurrentModule.onPreviewVisibilityChanged(visibility);
1356    }
1357
1358    private void updatePreviewRendering(int visibility) {
1359        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1360            mCameraAppUI.pausePreviewRendering();
1361        } else {
1362            mCameraAppUI.resumePreviewRendering();
1363        }
1364    }
1365
1366    private int getPreviewVisibility() {
1367        if (mFilmstripCoversPreview) {
1368            return ModuleController.VISIBILITY_HIDDEN;
1369        } else if (mModeListVisible){
1370            return ModuleController.VISIBILITY_COVERED;
1371        } else {
1372            return ModuleController.VISIBILITY_VISIBLE;
1373        }
1374    }
1375
1376    private void setRotationAnimation() {
1377        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1378        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1379        Window win = getWindow();
1380        WindowManager.LayoutParams winParams = win.getAttributes();
1381        winParams.rotationAnimation = rotationAnimation;
1382        win.setAttributes(winParams);
1383    }
1384
1385    @Override
1386    public void onUserInteraction() {
1387        super.onUserInteraction();
1388        if (!isFinishing()) {
1389            keepScreenOnForAWhile();
1390        }
1391    }
1392
1393    @Override
1394    public boolean dispatchTouchEvent(MotionEvent ev) {
1395        boolean result = super.dispatchTouchEvent(ev);
1396        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1397            // Real deletion is postponed until the next user interaction after
1398            // the gesture that triggers deletion. Until real deletion is
1399            // performed, users can click the undo button to bring back the
1400            // image that they chose to delete.
1401            if (mPendingDeletion && !mIsUndoingDeletion) {
1402                performDeletion();
1403            }
1404        }
1405        return result;
1406    }
1407
1408    @Override
1409    public void onPause() {
1410        mPaused = true;
1411        mPeekAnimationHandler = null;
1412        mPeekAnimationThread.quitSafely();
1413        mPeekAnimationThread = null;
1414        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1415
1416        // Delete photos that are pending deletion
1417        performDeletion();
1418        mCurrentModule.pause();
1419        mOrientationManager.pause();
1420        // Close the camera and wait for the operation done.
1421        mCameraController.closeCamera();
1422        mPanoramaViewHelper.onPause();
1423
1424        mLocalImagesObserver.setForegroundChangeListener(null);
1425        mLocalImagesObserver.setActivityPaused(true);
1426        mLocalVideosObserver.setActivityPaused(true);
1427        mPreloader.cancelAllLoads();
1428        resetScreenOn();
1429        super.onPause();
1430    }
1431
1432    @Override
1433    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1434        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1435            mResetToPreviewOnResume = false;
1436        } else {
1437            super.onActivityResult(requestCode, resultCode, data);
1438        }
1439    }
1440
1441    @Override
1442    public void onResume() {
1443        mPaused = false;
1444        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1445
1446        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1447
1448        // TODO: Handle this in OrientationManager.
1449        // Auto-rotate off
1450        if (Settings.System.getInt(getContentResolver(),
1451                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1452            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1453            mAutoRotateScreen = false;
1454        } else {
1455            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1456            mAutoRotateScreen = true;
1457        }
1458
1459        if (isCaptureIntent()) {
1460            // Foreground event caused by photo or video capure intent.
1461            UsageStatistics.foregrounded(
1462                    eventprotos.ForegroundEvent.ForegroundSource.INTENT_PICKER);
1463        } else if (!mSecureCamera) {
1464            // Foreground event that is not caused by an intent.
1465            UsageStatistics.foregrounded(
1466                    eventprotos.ForegroundEvent.ForegroundSource.ICON_LAUNCHER);
1467        }
1468
1469        Drawable galleryLogo;
1470        if (mSecureCamera) {
1471            mGalleryIntent = null;
1472            galleryLogo = null;
1473        } else {
1474            mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext);
1475            galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1476        }
1477        if (galleryLogo == null) {
1478            try {
1479                galleryLogo = getPackageManager().getActivityLogo(getComponentName());
1480            } catch (PackageManager.NameNotFoundException e) {
1481                Log.e(TAG, "Can't get the activity logo");
1482            }
1483        }
1484        if (mGalleryIntent != null) {
1485            mActionBar.setDisplayUseLogoEnabled(true);
1486        }
1487        mActionBar.setLogo(galleryLogo);
1488        mOrientationManager.resume();
1489        super.onResume();
1490        mPeekAnimationThread = new HandlerThread("Peek animation");
1491        mPeekAnimationThread.start();
1492        mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
1493        mCurrentModule.resume();
1494        setSwipingEnabled(true);
1495
1496        if (mResetToPreviewOnResume) {
1497            mCameraAppUI.resume();
1498        } else {
1499            LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1500            if (data != null) {
1501                mDataAdapter.refresh(data.getUri());
1502            }
1503        }
1504        // The share button might be disabled to avoid double tapping.
1505        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1506        // Default is showing the preview, unless disabled by explicitly
1507        // starting an activity we want to return from to the filmstrip rather
1508        // than the preview.
1509        mResetToPreviewOnResume = true;
1510
1511        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1512                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1513            if (!mSecureCamera) {
1514                // If it's secure camera, requestLoad() should not be called
1515                // as it will load all the data.
1516                if (!mFilmstripVisible) {
1517                    mDataAdapter.requestLoad();
1518                } else {
1519                    mDataAdapter.requestLoadNewPhotos();
1520                }
1521            }
1522        }
1523        mLocalImagesObserver.setActivityPaused(false);
1524        mLocalVideosObserver.setActivityPaused(false);
1525        if (!mSecureCamera) {
1526            mLocalImagesObserver.setForegroundChangeListener(
1527                    new LocalMediaObserver.ChangeListener() {
1528                @Override
1529                public void onChange() {
1530                    mDataAdapter.requestLoadNewPhotos();
1531                }
1532            });
1533        }
1534
1535        keepScreenOnForAWhile();
1536
1537        mPanoramaViewHelper.onResume();
1538        ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1539        syncLocationManagerSetting();
1540
1541        final int previewVisibility = getPreviewVisibility();
1542        updatePreviewRendering(previewVisibility);
1543    }
1544
1545    @Override
1546    public void onStart() {
1547        super.onStart();
1548        mPanoramaViewHelper.onStart();
1549        if (mResetToPreviewOnResume) {
1550            mCameraAppUI.resume();
1551            mResetToPreviewOnResume = false;
1552        }
1553    }
1554
1555    @Override
1556    protected void onStop() {
1557        mPanoramaViewHelper.onStop();
1558        if (mFeedbackHelper != null) {
1559            mFeedbackHelper.stopFeedback();
1560        }
1561
1562        mLocationManager.disconnect();
1563        super.onStop();
1564    }
1565
1566    @Override
1567    public void onDestroy() {
1568        if (mSecureCamera) {
1569            unregisterReceiver(mScreenOffReceiver);
1570        }
1571        mActionBar.removeOnMenuVisibilityListener(this);
1572        mSettingsManager.removeAllListeners();
1573        mCameraController.removeCallbackReceiver();
1574        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1575        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1576        getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
1577        mCameraAppUI.onDestroy();
1578        mCameraController = null;
1579        mSettingsManager = null;
1580        mCameraAppUI = null;
1581        mOrientationManager = null;
1582        mButtonManager = null;
1583        CameraManagerFactory.recycle();
1584        super.onDestroy();
1585    }
1586
1587    @Override
1588    public void onConfigurationChanged(Configuration config) {
1589        super.onConfigurationChanged(config);
1590        Log.v(TAG, "onConfigurationChanged");
1591        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1592            return;
1593        }
1594
1595        if (mLastLayoutOrientation != config.orientation) {
1596            mLastLayoutOrientation = config.orientation;
1597            mCurrentModule.onLayoutOrientationChanged(
1598                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1599        }
1600    }
1601
1602    @Override
1603    public boolean onKeyDown(int keyCode, KeyEvent event) {
1604        if (!mFilmstripVisible) {
1605            if (mCurrentModule.onKeyDown(keyCode, event)) {
1606                return true;
1607            }
1608            // Prevent software keyboard or voice search from showing up.
1609            if (keyCode == KeyEvent.KEYCODE_SEARCH
1610                    || keyCode == KeyEvent.KEYCODE_MENU) {
1611                if (event.isLongPress()) {
1612                    return true;
1613                }
1614            }
1615        }
1616
1617        return super.onKeyDown(keyCode, event);
1618    }
1619
1620    @Override
1621    public boolean onKeyUp(int keyCode, KeyEvent event) {
1622        if (!mFilmstripVisible) {
1623            // If a module is in the middle of capture, it should
1624            // consume the key event.
1625            if (mCurrentModule.onKeyUp(keyCode, event)) {
1626                return true;
1627            } else if (keyCode == KeyEvent.KEYCODE_MENU
1628                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1629                // Let the mode list view consume the event.
1630                mModeListView.onMenuPressed();
1631                return true;
1632            } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1633                mCameraAppUI.showFilmstrip();
1634                return true;
1635            }
1636        } else {
1637            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1638                mFilmstripController.goToNextItem();
1639                return true;
1640            } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1641                boolean wentToPrevious = mFilmstripController.goToPreviousItem();
1642                if (!wentToPrevious) {
1643                  // at beginning of filmstrip, hide and go back to preview
1644                  mCameraAppUI.hideFilmstrip();
1645                }
1646                return true;
1647            }
1648        }
1649        return super.onKeyUp(keyCode, event);
1650    }
1651
1652    @Override
1653    public void onBackPressed() {
1654        if (!mCameraAppUI.onBackPressed()) {
1655            if (!mCurrentModule.onBackPressed()) {
1656                super.onBackPressed();
1657            }
1658        }
1659    }
1660
1661    @Override
1662    public boolean isAutoRotateScreen() {
1663        // TODO: Move to OrientationManager.
1664        return mAutoRotateScreen;
1665    }
1666
1667    @Override
1668    public boolean onCreateOptionsMenu(Menu menu) {
1669        MenuInflater inflater = getMenuInflater();
1670        inflater.inflate(R.menu.filmstrip_menu, menu);
1671        mActionBarMenu = menu;
1672        return super.onCreateOptionsMenu(menu);
1673    }
1674
1675    protected void updateStorageSpace() {
1676        mStorageSpaceBytes = Storage.getAvailableSpace();
1677    }
1678
1679    protected long getStorageSpaceBytes() {
1680        return mStorageSpaceBytes;
1681    }
1682
1683    protected void updateStorageSpaceAndHint() {
1684        updateStorageSpace();
1685        updateStorageHint(mStorageSpaceBytes);
1686    }
1687
1688    protected void updateStorageHint(long storageSpace) {
1689        String message = null;
1690        if (storageSpace == Storage.UNAVAILABLE) {
1691            message = getString(R.string.no_storage);
1692        } else if (storageSpace == Storage.PREPARING) {
1693            message = getString(R.string.preparing_sd);
1694        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1695            message = getString(R.string.access_sd_fail);
1696        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1697            message = getString(R.string.spaceIsLow_content);
1698        }
1699
1700        if (message != null) {
1701            if (mStorageHint == null) {
1702                mStorageHint = OnScreenHint.makeText(mAppContext, message);
1703            } else {
1704                mStorageHint.setText(message);
1705            }
1706            mStorageHint.show();
1707        } else if (mStorageHint != null) {
1708            mStorageHint.cancel();
1709            mStorageHint = null;
1710        }
1711    }
1712
1713    protected void setResultEx(int resultCode) {
1714        mResultCodeForTesting = resultCode;
1715        setResult(resultCode);
1716    }
1717
1718    protected void setResultEx(int resultCode, Intent data) {
1719        mResultCodeForTesting = resultCode;
1720        mResultDataForTesting = data;
1721        setResult(resultCode, data);
1722    }
1723
1724    public int getResultCode() {
1725        return mResultCodeForTesting;
1726    }
1727
1728    public Intent getResultData() {
1729        return mResultDataForTesting;
1730    }
1731
1732    public boolean isSecureCamera() {
1733        return mSecureCamera;
1734    }
1735
1736    @Override
1737    public boolean isPaused() {
1738        return mPaused;
1739    }
1740
1741    @Override
1742    public int getPreferredChildModeIndex(int modeIndex) {
1743        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
1744            boolean hdrPlusOn = mSettingsManager.isHdrPlusOn();
1745            if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1746                modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1747            }
1748        }
1749        return modeIndex;
1750    }
1751
1752    @Override
1753    public void onModeSelected(int modeIndex) {
1754        if (mCurrentModeIndex == modeIndex) {
1755            return;
1756        }
1757
1758        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
1759        // Record last used camera mode for quick switching
1760        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
1761                || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
1762            mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX,
1763                    modeIndex);
1764        }
1765
1766        closeModule(mCurrentModule);
1767        int oldModuleIndex = mCurrentModeIndex;
1768
1769        // Select the correct module index from the mode switcher index.
1770        modeIndex = getPreferredChildModeIndex(modeIndex);
1771        setModuleFromModeIndex(modeIndex);
1772
1773        mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
1774        mCameraAppUI.addShutterListener(mCurrentModule);
1775        openModule(mCurrentModule);
1776        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1777        // Store the module index so we can use it the next time the Camera
1778        // starts up.
1779        SharedPreferences prefs = PreferenceManager
1780                .getDefaultSharedPreferences(mAppContext);
1781        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply();
1782    }
1783
1784    /**
1785     * Shows the settings dialog.
1786     */
1787    @Override
1788    public void onSettingsSelected() {
1789        Intent intent = new Intent(this, CameraSettingsActivity.class);
1790        startActivity(intent);
1791    }
1792
1793    /**
1794     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1795     * index an sets it as mCurrentModule.
1796     */
1797    private void setModuleFromModeIndex(int modeIndex) {
1798        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1799        if (agent == null) {
1800            return;
1801        }
1802        if (!agent.requestAppForCamera()) {
1803            mCameraController.closeCamera();
1804        }
1805        mCurrentModeIndex = agent.getModuleId();
1806        mCurrentModule = (CameraModule) agent.createModule(this);
1807    }
1808
1809    @Override
1810    public SettingsManager getSettingsManager() {
1811        return mSettingsManager;
1812    }
1813
1814    @Override
1815    public CameraServices getServices() {
1816        return (CameraServices) getApplication();
1817    }
1818
1819    public List<String> getSupportedModeNames() {
1820        List<Integer> indices = mModuleManager.getSupportedModeIndexList();
1821        List<String> supported = new ArrayList<String>();
1822
1823        for (Integer modeIndex : indices) {
1824            String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
1825            if (name != null && !name.equals("")) {
1826                supported.add(name);
1827            }
1828        }
1829        return supported;
1830    }
1831
1832    @Override
1833    public ButtonManager getButtonManager() {
1834        if (mButtonManager == null) {
1835            mButtonManager = new ButtonManager(this);
1836        }
1837        return mButtonManager;
1838    }
1839
1840    /**
1841     * Creates an AlertDialog appropriate for choosing whether to enable
1842     * location on the first run of the app.
1843     */
1844    public AlertDialog getFirstTimeLocationAlert() {
1845        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1846        builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
1847            @Override
1848            public void onCallback(Boolean locationOn) {
1849                mSettingsManager.setLocation(locationOn, mLocationManager);
1850            }
1851        });
1852        if (builder != null) {
1853            return builder.create();
1854        } else {
1855            return null;
1856        }
1857    }
1858
1859    /**
1860     * Launches an ACTION_EDIT intent for the given local data item. If
1861     * 'withTinyPlanet' is set, this will show a disambig dialog first to let
1862     * the user start either the tiny planet editor or another photo edior.
1863     *
1864     * @param data The data item to edit.
1865     */
1866    public void launchEditor(LocalData data) {
1867        Intent intent = new Intent(Intent.ACTION_EDIT)
1868                .setDataAndType(data.getUri(), data.getMimeType())
1869                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1870        try {
1871            launchActivityByIntent(intent);
1872        } catch (ActivityNotFoundException e) {
1873            launchActivityByIntent(Intent.createChooser(intent, null));
1874        }
1875    }
1876
1877    @Override
1878    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1879        super.onCreateContextMenu(menu, v, menuInfo);
1880
1881        MenuInflater inflater = getMenuInflater();
1882        inflater.inflate(R.menu.filmstrip_context_menu, menu);
1883    }
1884
1885    @Override
1886    public boolean onContextItemSelected(MenuItem item) {
1887        switch (item.getItemId()) {
1888            case R.id.tiny_planet_editor:
1889                mMyFilmstripBottomControlListener.onTinyPlanet();
1890                return true;
1891            case R.id.photo_editor:
1892                mMyFilmstripBottomControlListener.onEdit();
1893                return true;
1894        }
1895        return false;
1896    }
1897
1898    /**
1899     * Launch the tiny planet editor.
1900     *
1901     * @param data The data must be a 360 degree stereographically mapped
1902     *            panoramic image. It will not be modified, instead a new item
1903     *            with the result will be added to the filmstrip.
1904     */
1905    public void launchTinyPlanetEditor(LocalData data) {
1906        TinyPlanetFragment fragment = new TinyPlanetFragment();
1907        Bundle bundle = new Bundle();
1908        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString());
1909        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1910        fragment.setArguments(bundle);
1911        fragment.show(getFragmentManager(), "tiny_planet");
1912    }
1913
1914    private void openModule(CameraModule module) {
1915        module.init(this, isSecureCamera(), isCaptureIntent());
1916        module.resume();
1917        updatePreviewVisibility();
1918    }
1919
1920    private void closeModule(CameraModule module) {
1921        module.pause();
1922        mCameraAppUI.clearModuleUI();
1923    }
1924
1925    private void performDeletion() {
1926        if (!mPendingDeletion) {
1927            return;
1928        }
1929        hideUndoDeletionBar(false);
1930        mDataAdapter.executeDeletion();
1931    }
1932
1933    public void showUndoDeletionBar() {
1934        if (mPendingDeletion) {
1935            performDeletion();
1936        }
1937        Log.v(TAG, "showing undo bar");
1938        mPendingDeletion = true;
1939        if (mUndoDeletionBar == null) {
1940            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1941                    mAboveFilmstripControlLayout, true);
1942            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1943            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1944            button.setOnClickListener(new View.OnClickListener() {
1945                @Override
1946                public void onClick(View view) {
1947                    mDataAdapter.undoDataRemoval();
1948                    hideUndoDeletionBar(true);
1949                }
1950            });
1951            // Setting undo bar clickable to avoid touch events going through
1952            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1953            mUndoDeletionBar.setClickable(true);
1954            // When there is user interaction going on with the undo button, we
1955            // do not want to hide the undo bar.
1956            button.setOnTouchListener(new View.OnTouchListener() {
1957                @Override
1958                public boolean onTouch(View v, MotionEvent event) {
1959                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1960                        mIsUndoingDeletion = true;
1961                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1962                        mIsUndoingDeletion = false;
1963                    }
1964                    return false;
1965                }
1966            });
1967        }
1968        mUndoDeletionBar.setAlpha(0f);
1969        mUndoDeletionBar.setVisibility(View.VISIBLE);
1970        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1971    }
1972
1973    private void hideUndoDeletionBar(boolean withAnimation) {
1974        Log.v(TAG, "Hiding undo deletion bar");
1975        mPendingDeletion = false;
1976        if (mUndoDeletionBar != null) {
1977            if (withAnimation) {
1978                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1979                        .setListener(new Animator.AnimatorListener() {
1980                            @Override
1981                            public void onAnimationStart(Animator animation) {
1982                                // Do nothing.
1983                            }
1984
1985                            @Override
1986                            public void onAnimationEnd(Animator animation) {
1987                                mUndoDeletionBar.setVisibility(View.GONE);
1988                            }
1989
1990                            @Override
1991                            public void onAnimationCancel(Animator animation) {
1992                                // Do nothing.
1993                            }
1994
1995                            @Override
1996                            public void onAnimationRepeat(Animator animation) {
1997                                // Do nothing.
1998                            }
1999                        }).start();
2000            } else {
2001                mUndoDeletionBar.setVisibility(View.GONE);
2002            }
2003        }
2004    }
2005
2006    @Override
2007    public void onOrientationChanged(int orientation) {
2008        // We keep the last known orientation. So if the user first orient
2009        // the camera then point the camera to floor or sky, we still have
2010        // the correct orientation.
2011        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
2012            return;
2013        }
2014        mLastRawOrientation = orientation;
2015        if (mCurrentModule != null) {
2016            mCurrentModule.onOrientationChanged(orientation);
2017        }
2018    }
2019
2020    /**
2021     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2022     * capture intent.
2023     *
2024     * @param enable {@code true} to enable swipe.
2025     */
2026    public void setSwipingEnabled(boolean enable) {
2027        // TODO: Bring back the functionality.
2028        if (isCaptureIntent()) {
2029            // lockPreview(true);
2030        } else {
2031            // lockPreview(!enable);
2032        }
2033    }
2034
2035    // Accessor methods for getting latency times used in performance testing
2036    public long getFirstPreviewTime() {
2037        if (mCurrentModule instanceof PhotoModule) {
2038            long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2039            if (coverHiddenTime != -1) {
2040                return coverHiddenTime - mOnCreateTime;
2041            }
2042        }
2043        return -1;
2044    }
2045
2046    public long getAutoFocusTime() {
2047        return (mCurrentModule instanceof PhotoModule) ?
2048                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2049    }
2050
2051    public long getShutterLag() {
2052        return (mCurrentModule instanceof PhotoModule) ?
2053                ((PhotoModule) mCurrentModule).mShutterLag : -1;
2054    }
2055
2056    public long getShutterToPictureDisplayedTime() {
2057        return (mCurrentModule instanceof PhotoModule) ?
2058                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2059    }
2060
2061    public long getPictureDisplayedToJpegCallbackTime() {
2062        return (mCurrentModule instanceof PhotoModule) ?
2063                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2064    }
2065
2066    public long getJpegCallbackFinishTime() {
2067        return (mCurrentModule instanceof PhotoModule) ?
2068                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2069    }
2070
2071    public long getCaptureStartTime() {
2072        return (mCurrentModule instanceof PhotoModule) ?
2073                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2074    }
2075
2076    public boolean isRecording() {
2077        return (mCurrentModule instanceof VideoModule) ?
2078                ((VideoModule) mCurrentModule).isRecording() : false;
2079    }
2080
2081    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
2082        return mCameraController;
2083    }
2084
2085    // For debugging purposes only.
2086    public CameraModule getCurrentModule() {
2087        return mCurrentModule;
2088    }
2089
2090    @Override
2091    public void showTutorial(AbstractTutorialOverlay tutorial) {
2092        mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2093    }
2094
2095    /**
2096     * Reads the current location recording settings and passes it on to the
2097     * location manager.
2098     */
2099    public void syncLocationManagerSetting() {
2100        mSettingsManager.syncLocationManager(mLocationManager);
2101    }
2102
2103    private void keepScreenOnForAWhile() {
2104        if (mKeepScreenOn) {
2105            return;
2106        }
2107        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2108        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2109        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2110    }
2111
2112    private void resetScreenOn() {
2113        mKeepScreenOn = false;
2114        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2115        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2116    }
2117
2118    /**
2119     * @return {@code true} if the Gallery is launched successfully.
2120     */
2121    private boolean startGallery() {
2122        if (mGalleryIntent == null) {
2123            return false;
2124        }
2125        try {
2126            UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY, InteractionCause.BUTTON);
2127            Intent startGalleryIntent = new Intent(mGalleryIntent);
2128            int currentDataId = mFilmstripController.getCurrentId();
2129            LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
2130            if (currentLocalData != null) {
2131                GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri());
2132            }
2133            launchActivityByIntent(startGalleryIntent);
2134        } catch (ActivityNotFoundException e) {
2135            Log.w(TAG, "Failed to launch gallery activity, closing");
2136        }
2137        return false;
2138    }
2139
2140    private void setNfcBeamPushUriFromData(LocalData data) {
2141        final Uri uri = data.getUri();
2142        if (uri != Uri.EMPTY) {
2143            mNfcPushUris[0] = uri;
2144        } else {
2145            mNfcPushUris[0] = null;
2146        }
2147    }
2148
2149    /**
2150     * Updates the visibility of the filmstrip bottom controls and action bar.
2151     */
2152    private void updateUiByData(final int dataId) {
2153        final LocalData currentData = mDataAdapter.getLocalData(dataId);
2154        if (currentData == null) {
2155            Log.w(TAG, "Current data ID not found.");
2156            hideSessionProgress();
2157            return;
2158        }
2159        updateActionBarMenu(currentData);
2160
2161        /* Bottom controls. */
2162        updateBottomControlsByData(currentData);
2163
2164        if (isSecureCamera()) {
2165            // We cannot show buttons in secure camera since go to other
2166            // activities might create a security hole.
2167            mCameraAppUI.getFilmstripBottomControls().hideControls();
2168            return;
2169        }
2170
2171
2172        setNfcBeamPushUriFromData(currentData);
2173
2174        if (!mDataAdapter.isMetadataUpdated(dataId)) {
2175            mDataAdapter.updateMetadata(dataId);
2176        }
2177    }
2178
2179    /**
2180     * Updates the bottom controls based on the data.
2181     */
2182    private void updateBottomControlsByData(final LocalData currentData) {
2183
2184        final CameraAppUI.BottomPanel filmstripBottomPanel =
2185                mCameraAppUI.getFilmstripBottomControls();
2186        filmstripBottomPanel.showControls();
2187        filmstripBottomPanel.setEditButtonVisibility(
2188                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2189        filmstripBottomPanel.setShareButtonVisibility(
2190                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2191        filmstripBottomPanel.setDeleteButtonVisibility(
2192                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2193
2194        /* Progress bar */
2195
2196        Uri contentUri = currentData.getUri();
2197        CaptureSessionManager sessionManager = getServices()
2198                .getCaptureSessionManager();
2199
2200        if (sessionManager.hasErrorMessage(contentUri)) {
2201            showProcessError(sessionManager.getErrorMesage(contentUri));
2202        } else {
2203            filmstripBottomPanel.hideProgressError();
2204            CaptureSession session = sessionManager.getSession(contentUri);
2205
2206            if (session != null) {
2207                int sessionProgress = session.getProgress();
2208
2209                if (sessionProgress < 0) {
2210                    hideSessionProgress();
2211                } else {
2212                    CharSequence progressMessage = session.getProgressMessage();
2213                    showSessionProgress(progressMessage);
2214                    updateSessionProgress(sessionProgress);
2215                }
2216            } else {
2217                hideSessionProgress();
2218            }
2219        }
2220
2221        /* View button */
2222
2223        // We need to add this to a separate DB.
2224        final int viewButtonVisibility;
2225        if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2226            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2227        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2228            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2229        } else {
2230            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2231        }
2232
2233        filmstripBottomPanel.setTinyPlanetEnabled(
2234                PanoramaMetadataLoader.isPanorama360(currentData));
2235        filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2236    }
2237
2238    private class PeekAnimationHandler extends Handler {
2239        private class DataAndCallback {
2240            LocalData mData;
2241            com.android.camera.util.Callback<Bitmap> mCallback;
2242
2243            public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2244                    callback) {
2245                mData = data;
2246                mCallback = callback;
2247            }
2248        }
2249
2250        public PeekAnimationHandler(Looper looper) {
2251            super(looper);
2252        }
2253
2254        /**
2255         * Starts the animation decoding job and posts a {@code Runnable} back
2256         * when when the decoding is done.
2257         *
2258         * @param data The data item to decode the thumbnail for.
2259         * @param callback {@link com.android.camera.util.Callback} after the
2260         *                 decoding is done.
2261         */
2262        public void startDecodingJob(final LocalData data,
2263                final com.android.camera.util.Callback<Bitmap> callback) {
2264            PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2265                    new DataAndCallback(data, callback)).sendToTarget();
2266        }
2267
2268        @Override
2269        public void handleMessage(Message msg) {
2270            final LocalData data = ((DataAndCallback) msg.obj).mData;
2271            final com.android.camera.util.Callback<Bitmap> callback =
2272                    ((DataAndCallback) msg.obj).mCallback;
2273            if (data == null || callback == null) {
2274                return;
2275            }
2276
2277            final Bitmap bitmap;
2278            switch (data.getLocalDataType()) {
2279                case LocalData.LOCAL_IN_PROGRESS_DATA:
2280                    byte[] jpegData = Storage.getJpegForSession(data.getUri());
2281                    if (jpegData != null) {
2282                        bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
2283                    } else {
2284                        bitmap = null;
2285                    }
2286                    break;
2287
2288                case LocalData.LOCAL_IMAGE:
2289                    FileInputStream stream;
2290                    try {
2291                        stream = new FileInputStream(data.getPath());
2292                    } catch (FileNotFoundException e) {
2293                        Log.e(TAG, "File not found:" + data.getPath());
2294                        return;
2295                    }
2296                    Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2297                            data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2298                            mAboveFilmstripControlLayout.getMeasuredHeight());
2299                    if (data.getRotation() % 180 != 0) {
2300                        int dummy = dim.x;
2301                        dim.x = dim.y;
2302                        dim.y = dummy;
2303                    }
2304                    bitmap = LocalDataUtil
2305                            .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2306                                    (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2307                                    data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2308                    break;
2309
2310                case LocalData.LOCAL_VIDEO:
2311                    bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2312                    break;
2313
2314                default:
2315                    bitmap = null;
2316                    break;
2317            }
2318
2319            if (bitmap == null) {
2320                return;
2321            }
2322
2323            mMainHandler.post(new Runnable() {
2324                @Override
2325                public void run() {
2326                    callback.onCallback(bitmap);
2327                }
2328            });
2329        }
2330    }
2331
2332    private void showDetailsDialog(int dataId) {
2333        final LocalData data = mDataAdapter.getLocalData(dataId);
2334        if (data == null) {
2335            return;
2336        }
2337        MediaDetails details = data.getMediaDetails(getAndroidContext());
2338        if (details == null) {
2339            return;
2340        }
2341        Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details);
2342        detailDialog.show();
2343
2344        UsageStatistics.photoInteraction(
2345                UsageStatistics.hashFileName(fileNameFromDataID(dataId)),
2346                eventprotos.CameraEvent.InteractionType.DETAILS,
2347                InteractionCause.BUTTON);
2348    }
2349
2350    /**
2351     * Show or hide action bar items depending on current data type.
2352     */
2353    private void updateActionBarMenu(LocalData data) {
2354        if (mActionBarMenu == null) {
2355            return;
2356        }
2357
2358        MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2359        if (detailsMenuItem == null) {
2360            return;
2361        }
2362
2363        int type = data.getLocalDataType();
2364        boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO);
2365        detailsMenuItem.setVisible(showDetails);
2366    }
2367}
2368