CameraActivity.java revision fdd34d2d28a3c8b88098772881afabac8f8c7957
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        updateCaptureControls(visibility);
1356        mCurrentModule.onPreviewVisibilityChanged(visibility);
1357    }
1358
1359    private void updateCaptureControls(int visibility) {
1360        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1361            mCameraAppUI.setBottomBarVisible(false);
1362            mCameraAppUI.setModeOptionsVisible(false);
1363        } else {
1364            mCameraAppUI.setBottomBarVisible(true);
1365            mCameraAppUI.setModeOptionsVisible(true);
1366        }
1367    }
1368
1369    private void updatePreviewRendering(int visibility) {
1370        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1371            mCameraAppUI.pausePreviewRendering();
1372        } else {
1373            mCameraAppUI.resumePreviewRendering();
1374        }
1375    }
1376
1377    private int getPreviewVisibility() {
1378        if (mFilmstripCoversPreview) {
1379            return ModuleController.VISIBILITY_HIDDEN;
1380        } else if (mModeListVisible){
1381            return ModuleController.VISIBILITY_COVERED;
1382        } else {
1383            return ModuleController.VISIBILITY_VISIBLE;
1384        }
1385    }
1386
1387    private void setRotationAnimation() {
1388        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1389        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1390        Window win = getWindow();
1391        WindowManager.LayoutParams winParams = win.getAttributes();
1392        winParams.rotationAnimation = rotationAnimation;
1393        win.setAttributes(winParams);
1394    }
1395
1396    @Override
1397    public void onUserInteraction() {
1398        super.onUserInteraction();
1399        if (!isFinishing()) {
1400            keepScreenOnForAWhile();
1401        }
1402    }
1403
1404    @Override
1405    public boolean dispatchTouchEvent(MotionEvent ev) {
1406        boolean result = super.dispatchTouchEvent(ev);
1407        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1408            // Real deletion is postponed until the next user interaction after
1409            // the gesture that triggers deletion. Until real deletion is
1410            // performed, users can click the undo button to bring back the
1411            // image that they chose to delete.
1412            if (mPendingDeletion && !mIsUndoingDeletion) {
1413                performDeletion();
1414            }
1415        }
1416        return result;
1417    }
1418
1419    @Override
1420    public void onPause() {
1421        mPaused = true;
1422        mPeekAnimationHandler = null;
1423        mPeekAnimationThread.quitSafely();
1424        mPeekAnimationThread = null;
1425        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1426
1427        // Delete photos that are pending deletion
1428        performDeletion();
1429        mCurrentModule.pause();
1430        mOrientationManager.pause();
1431        // Close the camera and wait for the operation done.
1432        mCameraController.closeCamera();
1433        mPanoramaViewHelper.onPause();
1434
1435        mLocalImagesObserver.setForegroundChangeListener(null);
1436        mLocalImagesObserver.setActivityPaused(true);
1437        mLocalVideosObserver.setActivityPaused(true);
1438        mPreloader.cancelAllLoads();
1439        resetScreenOn();
1440        super.onPause();
1441    }
1442
1443    @Override
1444    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1445        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1446            mResetToPreviewOnResume = false;
1447        } else {
1448            super.onActivityResult(requestCode, resultCode, data);
1449        }
1450    }
1451
1452    @Override
1453    public void onResume() {
1454        mPaused = false;
1455        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1456
1457        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1458
1459        // TODO: Handle this in OrientationManager.
1460        // Auto-rotate off
1461        if (Settings.System.getInt(getContentResolver(),
1462                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1463            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1464            mAutoRotateScreen = false;
1465        } else {
1466            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1467            mAutoRotateScreen = true;
1468        }
1469
1470        if (isCaptureIntent()) {
1471            // Foreground event caused by photo or video capure intent.
1472            UsageStatistics.foregrounded(
1473                    eventprotos.ForegroundEvent.ForegroundSource.INTENT_PICKER);
1474        } else if (!mSecureCamera) {
1475            // Foreground event that is not caused by an intent.
1476            UsageStatistics.foregrounded(
1477                    eventprotos.ForegroundEvent.ForegroundSource.ICON_LAUNCHER);
1478        }
1479
1480        Drawable galleryLogo;
1481        if (mSecureCamera) {
1482            mGalleryIntent = null;
1483            galleryLogo = null;
1484        } else {
1485            mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext);
1486            galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1487        }
1488        if (galleryLogo == null) {
1489            try {
1490                galleryLogo = getPackageManager().getActivityLogo(getComponentName());
1491            } catch (PackageManager.NameNotFoundException e) {
1492                Log.e(TAG, "Can't get the activity logo");
1493            }
1494        }
1495        if (mGalleryIntent != null) {
1496            mActionBar.setDisplayUseLogoEnabled(true);
1497        }
1498        mActionBar.setLogo(galleryLogo);
1499        mOrientationManager.resume();
1500        super.onResume();
1501        mPeekAnimationThread = new HandlerThread("Peek animation");
1502        mPeekAnimationThread.start();
1503        mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
1504        mCurrentModule.resume();
1505        setSwipingEnabled(true);
1506
1507        if (mResetToPreviewOnResume) {
1508            mCameraAppUI.resume();
1509        } else {
1510            LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1511            if (data != null) {
1512                mDataAdapter.refresh(data.getUri());
1513            }
1514        }
1515        // The share button might be disabled to avoid double tapping.
1516        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1517        // Default is showing the preview, unless disabled by explicitly
1518        // starting an activity we want to return from to the filmstrip rather
1519        // than the preview.
1520        mResetToPreviewOnResume = true;
1521
1522        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1523                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1524            if (!mSecureCamera) {
1525                // If it's secure camera, requestLoad() should not be called
1526                // as it will load all the data.
1527                if (!mFilmstripVisible) {
1528                    mDataAdapter.requestLoad();
1529                } else {
1530                    mDataAdapter.requestLoadNewPhotos();
1531                }
1532            }
1533        }
1534        mLocalImagesObserver.setActivityPaused(false);
1535        mLocalVideosObserver.setActivityPaused(false);
1536        if (!mSecureCamera) {
1537            mLocalImagesObserver.setForegroundChangeListener(
1538                    new LocalMediaObserver.ChangeListener() {
1539                @Override
1540                public void onChange() {
1541                    mDataAdapter.requestLoadNewPhotos();
1542                }
1543            });
1544        }
1545
1546        keepScreenOnForAWhile();
1547
1548        mPanoramaViewHelper.onResume();
1549        ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1550        syncLocationManagerSetting();
1551
1552        final int previewVisibility = getPreviewVisibility();
1553        updatePreviewRendering(previewVisibility);
1554    }
1555
1556    @Override
1557    public void onStart() {
1558        super.onStart();
1559        mPanoramaViewHelper.onStart();
1560        if (mResetToPreviewOnResume) {
1561            mCameraAppUI.resume();
1562            mResetToPreviewOnResume = false;
1563        }
1564    }
1565
1566    @Override
1567    protected void onStop() {
1568        mPanoramaViewHelper.onStop();
1569        if (mFeedbackHelper != null) {
1570            mFeedbackHelper.stopFeedback();
1571        }
1572
1573        mLocationManager.disconnect();
1574        super.onStop();
1575    }
1576
1577    @Override
1578    public void onDestroy() {
1579        if (mSecureCamera) {
1580            unregisterReceiver(mScreenOffReceiver);
1581        }
1582        mActionBar.removeOnMenuVisibilityListener(this);
1583        mSettingsManager.removeAllListeners();
1584        mCameraController.removeCallbackReceiver();
1585        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1586        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1587        getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
1588        mCameraAppUI.onDestroy();
1589        mCameraController = null;
1590        mSettingsManager = null;
1591        mCameraAppUI = null;
1592        mOrientationManager = null;
1593        mButtonManager = null;
1594        CameraManagerFactory.recycle();
1595        super.onDestroy();
1596    }
1597
1598    @Override
1599    public void onConfigurationChanged(Configuration config) {
1600        super.onConfigurationChanged(config);
1601        Log.v(TAG, "onConfigurationChanged");
1602        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1603            return;
1604        }
1605
1606        if (mLastLayoutOrientation != config.orientation) {
1607            mLastLayoutOrientation = config.orientation;
1608            mCurrentModule.onLayoutOrientationChanged(
1609                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1610        }
1611    }
1612
1613    @Override
1614    public boolean onKeyDown(int keyCode, KeyEvent event) {
1615        if (!mFilmstripVisible) {
1616            if (mCurrentModule.onKeyDown(keyCode, event)) {
1617                return true;
1618            }
1619            // Prevent software keyboard or voice search from showing up.
1620            if (keyCode == KeyEvent.KEYCODE_SEARCH
1621                    || keyCode == KeyEvent.KEYCODE_MENU) {
1622                if (event.isLongPress()) {
1623                    return true;
1624                }
1625            }
1626        }
1627
1628        return super.onKeyDown(keyCode, event);
1629    }
1630
1631    @Override
1632    public boolean onKeyUp(int keyCode, KeyEvent event) {
1633        if (!mFilmstripVisible) {
1634            // If a module is in the middle of capture, it should
1635            // consume the key event.
1636            if (mCurrentModule.onKeyUp(keyCode, event)) {
1637                return true;
1638            } else if (keyCode == KeyEvent.KEYCODE_MENU
1639                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1640                // Let the mode list view consume the event.
1641                mModeListView.onMenuPressed();
1642                return true;
1643            } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1644                mCameraAppUI.showFilmstrip();
1645                return true;
1646            }
1647        } else {
1648            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1649                mFilmstripController.goToNextItem();
1650                return true;
1651            } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1652                boolean wentToPrevious = mFilmstripController.goToPreviousItem();
1653                if (!wentToPrevious) {
1654                  // at beginning of filmstrip, hide and go back to preview
1655                  mCameraAppUI.hideFilmstrip();
1656                }
1657                return true;
1658            }
1659        }
1660        return super.onKeyUp(keyCode, event);
1661    }
1662
1663    @Override
1664    public void onBackPressed() {
1665        if (!mCameraAppUI.onBackPressed()) {
1666            if (!mCurrentModule.onBackPressed()) {
1667                super.onBackPressed();
1668            }
1669        }
1670    }
1671
1672    @Override
1673    public boolean isAutoRotateScreen() {
1674        // TODO: Move to OrientationManager.
1675        return mAutoRotateScreen;
1676    }
1677
1678    @Override
1679    public boolean onCreateOptionsMenu(Menu menu) {
1680        MenuInflater inflater = getMenuInflater();
1681        inflater.inflate(R.menu.filmstrip_menu, menu);
1682        mActionBarMenu = menu;
1683        return super.onCreateOptionsMenu(menu);
1684    }
1685
1686    protected void updateStorageSpace() {
1687        mStorageSpaceBytes = Storage.getAvailableSpace();
1688    }
1689
1690    protected long getStorageSpaceBytes() {
1691        return mStorageSpaceBytes;
1692    }
1693
1694    protected void updateStorageSpaceAndHint() {
1695        updateStorageSpace();
1696        updateStorageHint(mStorageSpaceBytes);
1697    }
1698
1699    protected void updateStorageHint(long storageSpace) {
1700        String message = null;
1701        if (storageSpace == Storage.UNAVAILABLE) {
1702            message = getString(R.string.no_storage);
1703        } else if (storageSpace == Storage.PREPARING) {
1704            message = getString(R.string.preparing_sd);
1705        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1706            message = getString(R.string.access_sd_fail);
1707        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1708            message = getString(R.string.spaceIsLow_content);
1709        }
1710
1711        if (message != null) {
1712            if (mStorageHint == null) {
1713                mStorageHint = OnScreenHint.makeText(mAppContext, message);
1714            } else {
1715                mStorageHint.setText(message);
1716            }
1717            mStorageHint.show();
1718        } else if (mStorageHint != null) {
1719            mStorageHint.cancel();
1720            mStorageHint = null;
1721        }
1722    }
1723
1724    protected void setResultEx(int resultCode) {
1725        mResultCodeForTesting = resultCode;
1726        setResult(resultCode);
1727    }
1728
1729    protected void setResultEx(int resultCode, Intent data) {
1730        mResultCodeForTesting = resultCode;
1731        mResultDataForTesting = data;
1732        setResult(resultCode, data);
1733    }
1734
1735    public int getResultCode() {
1736        return mResultCodeForTesting;
1737    }
1738
1739    public Intent getResultData() {
1740        return mResultDataForTesting;
1741    }
1742
1743    public boolean isSecureCamera() {
1744        return mSecureCamera;
1745    }
1746
1747    @Override
1748    public boolean isPaused() {
1749        return mPaused;
1750    }
1751
1752    @Override
1753    public int getPreferredChildModeIndex(int modeIndex) {
1754        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
1755            boolean hdrPlusOn = mSettingsManager.isHdrPlusOn();
1756            if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1757                modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1758            }
1759        }
1760        return modeIndex;
1761    }
1762
1763    @Override
1764    public void onModeSelected(int modeIndex) {
1765        if (mCurrentModeIndex == modeIndex) {
1766            return;
1767        }
1768
1769        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
1770        // Record last used camera mode for quick switching
1771        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
1772                || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
1773            mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX,
1774                    modeIndex);
1775        }
1776
1777        closeModule(mCurrentModule);
1778        int oldModuleIndex = mCurrentModeIndex;
1779
1780        // Select the correct module index from the mode switcher index.
1781        modeIndex = getPreferredChildModeIndex(modeIndex);
1782        setModuleFromModeIndex(modeIndex);
1783
1784        mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
1785        mCameraAppUI.addShutterListener(mCurrentModule);
1786        openModule(mCurrentModule);
1787        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1788        // Store the module index so we can use it the next time the Camera
1789        // starts up.
1790        SharedPreferences prefs = PreferenceManager
1791                .getDefaultSharedPreferences(mAppContext);
1792        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply();
1793    }
1794
1795    /**
1796     * Shows the settings dialog.
1797     */
1798    @Override
1799    public void onSettingsSelected() {
1800        Intent intent = new Intent(this, CameraSettingsActivity.class);
1801        startActivity(intent);
1802    }
1803
1804    /**
1805     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1806     * index an sets it as mCurrentModule.
1807     */
1808    private void setModuleFromModeIndex(int modeIndex) {
1809        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1810        if (agent == null) {
1811            return;
1812        }
1813        if (!agent.requestAppForCamera()) {
1814            mCameraController.closeCamera();
1815        }
1816        mCurrentModeIndex = agent.getModuleId();
1817        mCurrentModule = (CameraModule) agent.createModule(this);
1818    }
1819
1820    @Override
1821    public SettingsManager getSettingsManager() {
1822        return mSettingsManager;
1823    }
1824
1825    @Override
1826    public CameraServices getServices() {
1827        return (CameraServices) getApplication();
1828    }
1829
1830    public List<String> getSupportedModeNames() {
1831        List<Integer> indices = mModuleManager.getSupportedModeIndexList();
1832        List<String> supported = new ArrayList<String>();
1833
1834        for (Integer modeIndex : indices) {
1835            String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
1836            if (name != null && !name.equals("")) {
1837                supported.add(name);
1838            }
1839        }
1840        return supported;
1841    }
1842
1843    @Override
1844    public ButtonManager getButtonManager() {
1845        if (mButtonManager == null) {
1846            mButtonManager = new ButtonManager(this);
1847        }
1848        return mButtonManager;
1849    }
1850
1851    /**
1852     * Creates an AlertDialog appropriate for choosing whether to enable
1853     * location on the first run of the app.
1854     */
1855    public AlertDialog getFirstTimeLocationAlert() {
1856        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1857        builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
1858            @Override
1859            public void onCallback(Boolean locationOn) {
1860                mSettingsManager.setLocation(locationOn, mLocationManager);
1861            }
1862        });
1863        if (builder != null) {
1864            return builder.create();
1865        } else {
1866            return null;
1867        }
1868    }
1869
1870    /**
1871     * Launches an ACTION_EDIT intent for the given local data item. If
1872     * 'withTinyPlanet' is set, this will show a disambig dialog first to let
1873     * the user start either the tiny planet editor or another photo edior.
1874     *
1875     * @param data The data item to edit.
1876     */
1877    public void launchEditor(LocalData data) {
1878        Intent intent = new Intent(Intent.ACTION_EDIT)
1879                .setDataAndType(data.getUri(), data.getMimeType())
1880                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1881        try {
1882            launchActivityByIntent(intent);
1883        } catch (ActivityNotFoundException e) {
1884            launchActivityByIntent(Intent.createChooser(intent, null));
1885        }
1886    }
1887
1888    @Override
1889    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1890        super.onCreateContextMenu(menu, v, menuInfo);
1891
1892        MenuInflater inflater = getMenuInflater();
1893        inflater.inflate(R.menu.filmstrip_context_menu, menu);
1894    }
1895
1896    @Override
1897    public boolean onContextItemSelected(MenuItem item) {
1898        switch (item.getItemId()) {
1899            case R.id.tiny_planet_editor:
1900                mMyFilmstripBottomControlListener.onTinyPlanet();
1901                return true;
1902            case R.id.photo_editor:
1903                mMyFilmstripBottomControlListener.onEdit();
1904                return true;
1905        }
1906        return false;
1907    }
1908
1909    /**
1910     * Launch the tiny planet editor.
1911     *
1912     * @param data The data must be a 360 degree stereographically mapped
1913     *            panoramic image. It will not be modified, instead a new item
1914     *            with the result will be added to the filmstrip.
1915     */
1916    public void launchTinyPlanetEditor(LocalData data) {
1917        TinyPlanetFragment fragment = new TinyPlanetFragment();
1918        Bundle bundle = new Bundle();
1919        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString());
1920        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1921        fragment.setArguments(bundle);
1922        fragment.show(getFragmentManager(), "tiny_planet");
1923    }
1924
1925    private void openModule(CameraModule module) {
1926        module.init(this, isSecureCamera(), isCaptureIntent());
1927        module.resume();
1928        updatePreviewVisibility();
1929    }
1930
1931    private void closeModule(CameraModule module) {
1932        module.pause();
1933        mCameraAppUI.clearModuleUI();
1934    }
1935
1936    private void performDeletion() {
1937        if (!mPendingDeletion) {
1938            return;
1939        }
1940        hideUndoDeletionBar(false);
1941        mDataAdapter.executeDeletion();
1942    }
1943
1944    public void showUndoDeletionBar() {
1945        if (mPendingDeletion) {
1946            performDeletion();
1947        }
1948        Log.v(TAG, "showing undo bar");
1949        mPendingDeletion = true;
1950        if (mUndoDeletionBar == null) {
1951            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1952                    mAboveFilmstripControlLayout, true);
1953            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1954            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1955            button.setOnClickListener(new View.OnClickListener() {
1956                @Override
1957                public void onClick(View view) {
1958                    mDataAdapter.undoDataRemoval();
1959                    hideUndoDeletionBar(true);
1960                }
1961            });
1962            // Setting undo bar clickable to avoid touch events going through
1963            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1964            mUndoDeletionBar.setClickable(true);
1965            // When there is user interaction going on with the undo button, we
1966            // do not want to hide the undo bar.
1967            button.setOnTouchListener(new View.OnTouchListener() {
1968                @Override
1969                public boolean onTouch(View v, MotionEvent event) {
1970                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1971                        mIsUndoingDeletion = true;
1972                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1973                        mIsUndoingDeletion = false;
1974                    }
1975                    return false;
1976                }
1977            });
1978        }
1979        mUndoDeletionBar.setAlpha(0f);
1980        mUndoDeletionBar.setVisibility(View.VISIBLE);
1981        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1982    }
1983
1984    private void hideUndoDeletionBar(boolean withAnimation) {
1985        Log.v(TAG, "Hiding undo deletion bar");
1986        mPendingDeletion = false;
1987        if (mUndoDeletionBar != null) {
1988            if (withAnimation) {
1989                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1990                        .setListener(new Animator.AnimatorListener() {
1991                            @Override
1992                            public void onAnimationStart(Animator animation) {
1993                                // Do nothing.
1994                            }
1995
1996                            @Override
1997                            public void onAnimationEnd(Animator animation) {
1998                                mUndoDeletionBar.setVisibility(View.GONE);
1999                            }
2000
2001                            @Override
2002                            public void onAnimationCancel(Animator animation) {
2003                                // Do nothing.
2004                            }
2005
2006                            @Override
2007                            public void onAnimationRepeat(Animator animation) {
2008                                // Do nothing.
2009                            }
2010                        }).start();
2011            } else {
2012                mUndoDeletionBar.setVisibility(View.GONE);
2013            }
2014        }
2015    }
2016
2017    @Override
2018    public void onOrientationChanged(int orientation) {
2019        // We keep the last known orientation. So if the user first orient
2020        // the camera then point the camera to floor or sky, we still have
2021        // the correct orientation.
2022        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
2023            return;
2024        }
2025        mLastRawOrientation = orientation;
2026        if (mCurrentModule != null) {
2027            mCurrentModule.onOrientationChanged(orientation);
2028        }
2029    }
2030
2031    /**
2032     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2033     * capture intent.
2034     *
2035     * @param enable {@code true} to enable swipe.
2036     */
2037    public void setSwipingEnabled(boolean enable) {
2038        // TODO: Bring back the functionality.
2039        if (isCaptureIntent()) {
2040            // lockPreview(true);
2041        } else {
2042            // lockPreview(!enable);
2043        }
2044    }
2045
2046    // Accessor methods for getting latency times used in performance testing
2047    public long getFirstPreviewTime() {
2048        if (mCurrentModule instanceof PhotoModule) {
2049            long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2050            if (coverHiddenTime != -1) {
2051                return coverHiddenTime - mOnCreateTime;
2052            }
2053        }
2054        return -1;
2055    }
2056
2057    public long getAutoFocusTime() {
2058        return (mCurrentModule instanceof PhotoModule) ?
2059                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2060    }
2061
2062    public long getShutterLag() {
2063        return (mCurrentModule instanceof PhotoModule) ?
2064                ((PhotoModule) mCurrentModule).mShutterLag : -1;
2065    }
2066
2067    public long getShutterToPictureDisplayedTime() {
2068        return (mCurrentModule instanceof PhotoModule) ?
2069                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2070    }
2071
2072    public long getPictureDisplayedToJpegCallbackTime() {
2073        return (mCurrentModule instanceof PhotoModule) ?
2074                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2075    }
2076
2077    public long getJpegCallbackFinishTime() {
2078        return (mCurrentModule instanceof PhotoModule) ?
2079                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2080    }
2081
2082    public long getCaptureStartTime() {
2083        return (mCurrentModule instanceof PhotoModule) ?
2084                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2085    }
2086
2087    public boolean isRecording() {
2088        return (mCurrentModule instanceof VideoModule) ?
2089                ((VideoModule) mCurrentModule).isRecording() : false;
2090    }
2091
2092    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
2093        return mCameraController;
2094    }
2095
2096    // For debugging purposes only.
2097    public CameraModule getCurrentModule() {
2098        return mCurrentModule;
2099    }
2100
2101    @Override
2102    public void showTutorial(AbstractTutorialOverlay tutorial) {
2103        mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2104    }
2105
2106    /**
2107     * Reads the current location recording settings and passes it on to the
2108     * location manager.
2109     */
2110    public void syncLocationManagerSetting() {
2111        mSettingsManager.syncLocationManager(mLocationManager);
2112    }
2113
2114    private void keepScreenOnForAWhile() {
2115        if (mKeepScreenOn) {
2116            return;
2117        }
2118        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2119        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2120        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2121    }
2122
2123    private void resetScreenOn() {
2124        mKeepScreenOn = false;
2125        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2126        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2127    }
2128
2129    /**
2130     * @return {@code true} if the Gallery is launched successfully.
2131     */
2132    private boolean startGallery() {
2133        if (mGalleryIntent == null) {
2134            return false;
2135        }
2136        try {
2137            UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY, InteractionCause.BUTTON);
2138            Intent startGalleryIntent = new Intent(mGalleryIntent);
2139            int currentDataId = mFilmstripController.getCurrentId();
2140            LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
2141            if (currentLocalData != null) {
2142                GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri());
2143            }
2144            launchActivityByIntent(startGalleryIntent);
2145        } catch (ActivityNotFoundException e) {
2146            Log.w(TAG, "Failed to launch gallery activity, closing");
2147        }
2148        return false;
2149    }
2150
2151    private void setNfcBeamPushUriFromData(LocalData data) {
2152        final Uri uri = data.getUri();
2153        if (uri != Uri.EMPTY) {
2154            mNfcPushUris[0] = uri;
2155        } else {
2156            mNfcPushUris[0] = null;
2157        }
2158    }
2159
2160    /**
2161     * Updates the visibility of the filmstrip bottom controls and action bar.
2162     */
2163    private void updateUiByData(final int dataId) {
2164        final LocalData currentData = mDataAdapter.getLocalData(dataId);
2165        if (currentData == null) {
2166            Log.w(TAG, "Current data ID not found.");
2167            hideSessionProgress();
2168            return;
2169        }
2170        updateActionBarMenu(currentData);
2171
2172        /* Bottom controls. */
2173        updateBottomControlsByData(currentData);
2174
2175        if (isSecureCamera()) {
2176            // We cannot show buttons in secure camera since go to other
2177            // activities might create a security hole.
2178            mCameraAppUI.getFilmstripBottomControls().hideControls();
2179            return;
2180        }
2181
2182
2183        setNfcBeamPushUriFromData(currentData);
2184
2185        if (!mDataAdapter.isMetadataUpdated(dataId)) {
2186            mDataAdapter.updateMetadata(dataId);
2187        }
2188    }
2189
2190    /**
2191     * Updates the bottom controls based on the data.
2192     */
2193    private void updateBottomControlsByData(final LocalData currentData) {
2194
2195        final CameraAppUI.BottomPanel filmstripBottomPanel =
2196                mCameraAppUI.getFilmstripBottomControls();
2197        filmstripBottomPanel.showControls();
2198        filmstripBottomPanel.setEditButtonVisibility(
2199                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2200        filmstripBottomPanel.setShareButtonVisibility(
2201                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2202        filmstripBottomPanel.setDeleteButtonVisibility(
2203                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2204
2205        /* Progress bar */
2206
2207        Uri contentUri = currentData.getUri();
2208        CaptureSessionManager sessionManager = getServices()
2209                .getCaptureSessionManager();
2210
2211        if (sessionManager.hasErrorMessage(contentUri)) {
2212            showProcessError(sessionManager.getErrorMesage(contentUri));
2213        } else {
2214            filmstripBottomPanel.hideProgressError();
2215            CaptureSession session = sessionManager.getSession(contentUri);
2216
2217            if (session != null) {
2218                int sessionProgress = session.getProgress();
2219
2220                if (sessionProgress < 0) {
2221                    hideSessionProgress();
2222                } else {
2223                    CharSequence progressMessage = session.getProgressMessage();
2224                    showSessionProgress(progressMessage);
2225                    updateSessionProgress(sessionProgress);
2226                }
2227            } else {
2228                hideSessionProgress();
2229            }
2230        }
2231
2232        /* View button */
2233
2234        // We need to add this to a separate DB.
2235        final int viewButtonVisibility;
2236        if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2237            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2238        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2239            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2240        } else {
2241            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2242        }
2243
2244        filmstripBottomPanel.setTinyPlanetEnabled(
2245                PanoramaMetadataLoader.isPanorama360(currentData));
2246        filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2247    }
2248
2249    private class PeekAnimationHandler extends Handler {
2250        private class DataAndCallback {
2251            LocalData mData;
2252            com.android.camera.util.Callback<Bitmap> mCallback;
2253
2254            public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2255                    callback) {
2256                mData = data;
2257                mCallback = callback;
2258            }
2259        }
2260
2261        public PeekAnimationHandler(Looper looper) {
2262            super(looper);
2263        }
2264
2265        /**
2266         * Starts the animation decoding job and posts a {@code Runnable} back
2267         * when when the decoding is done.
2268         *
2269         * @param data The data item to decode the thumbnail for.
2270         * @param callback {@link com.android.camera.util.Callback} after the
2271         *                 decoding is done.
2272         */
2273        public void startDecodingJob(final LocalData data,
2274                final com.android.camera.util.Callback<Bitmap> callback) {
2275            PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2276                    new DataAndCallback(data, callback)).sendToTarget();
2277        }
2278
2279        @Override
2280        public void handleMessage(Message msg) {
2281            final LocalData data = ((DataAndCallback) msg.obj).mData;
2282            final com.android.camera.util.Callback<Bitmap> callback =
2283                    ((DataAndCallback) msg.obj).mCallback;
2284            if (data == null || callback == null) {
2285                return;
2286            }
2287
2288            final Bitmap bitmap;
2289            switch (data.getLocalDataType()) {
2290                case LocalData.LOCAL_IN_PROGRESS_DATA:
2291                    byte[] jpegData = Storage.getJpegForSession(data.getUri());
2292                    if (jpegData != null) {
2293                        bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
2294                    } else {
2295                        bitmap = null;
2296                    }
2297                    break;
2298
2299                case LocalData.LOCAL_IMAGE:
2300                    FileInputStream stream;
2301                    try {
2302                        stream = new FileInputStream(data.getPath());
2303                    } catch (FileNotFoundException e) {
2304                        Log.e(TAG, "File not found:" + data.getPath());
2305                        return;
2306                    }
2307                    Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2308                            data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2309                            mAboveFilmstripControlLayout.getMeasuredHeight());
2310                    if (data.getRotation() % 180 != 0) {
2311                        int dummy = dim.x;
2312                        dim.x = dim.y;
2313                        dim.y = dummy;
2314                    }
2315                    bitmap = LocalDataUtil
2316                            .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2317                                    (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2318                                    data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2319                    break;
2320
2321                case LocalData.LOCAL_VIDEO:
2322                    bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2323                    break;
2324
2325                default:
2326                    bitmap = null;
2327                    break;
2328            }
2329
2330            if (bitmap == null) {
2331                return;
2332            }
2333
2334            mMainHandler.post(new Runnable() {
2335                @Override
2336                public void run() {
2337                    callback.onCallback(bitmap);
2338                }
2339            });
2340        }
2341    }
2342
2343    private void showDetailsDialog(int dataId) {
2344        final LocalData data = mDataAdapter.getLocalData(dataId);
2345        if (data == null) {
2346            return;
2347        }
2348        MediaDetails details = data.getMediaDetails(getAndroidContext());
2349        if (details == null) {
2350            return;
2351        }
2352        Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details);
2353        detailDialog.show();
2354
2355        UsageStatistics.photoInteraction(
2356                UsageStatistics.hashFileName(fileNameFromDataID(dataId)),
2357                eventprotos.CameraEvent.InteractionType.DETAILS,
2358                InteractionCause.BUTTON);
2359    }
2360
2361    /**
2362     * Show or hide action bar items depending on current data type.
2363     */
2364    private void updateActionBarMenu(LocalData data) {
2365        if (mActionBarMenu == null) {
2366            return;
2367        }
2368
2369        MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2370        if (detailsMenuItem == null) {
2371            return;
2372        }
2373
2374        int type = data.getLocalDataType();
2375        boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO);
2376        detailsMenuItem.setVisible(showDetails);
2377    }
2378}
2379