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