CameraActivity.java revision 5c4bd6b5348a3464783270ea0bb10d4968d3b0cb
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        mCameraAppUI = null;
1861        mOrientationManager = null;
1862        mButtonManager = null;
1863        CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
1864        CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
1865        mSoundPlayer.release();
1866        super.onDestroy();
1867    }
1868
1869    @Override
1870    public void onConfigurationChanged(Configuration config) {
1871        super.onConfigurationChanged(config);
1872        Log.v(TAG, "onConfigurationChanged");
1873        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1874            return;
1875        }
1876
1877        if (mLastLayoutOrientation != config.orientation) {
1878            mLastLayoutOrientation = config.orientation;
1879            mCurrentModule.onLayoutOrientationChanged(
1880                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1881        }
1882    }
1883
1884    @Override
1885    public boolean onKeyDown(int keyCode, KeyEvent event) {
1886        if (!mFilmstripVisible) {
1887            if (mCurrentModule.onKeyDown(keyCode, event)) {
1888                return true;
1889            }
1890            // Prevent software keyboard or voice search from showing up.
1891            if (keyCode == KeyEvent.KEYCODE_SEARCH
1892                    || keyCode == KeyEvent.KEYCODE_MENU) {
1893                if (event.isLongPress()) {
1894                    return true;
1895                }
1896            }
1897        }
1898
1899        return super.onKeyDown(keyCode, event);
1900    }
1901
1902    @Override
1903    public boolean onKeyUp(int keyCode, KeyEvent event) {
1904        if (!mFilmstripVisible) {
1905            // If a module is in the middle of capture, it should
1906            // consume the key event.
1907            if (mCurrentModule.onKeyUp(keyCode, event)) {
1908                return true;
1909            } else if (keyCode == KeyEvent.KEYCODE_MENU
1910                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1911                // Let the mode list view consume the event.
1912                mCameraAppUI.openModeList();
1913                return true;
1914            } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1915                mCameraAppUI.showFilmstrip();
1916                return true;
1917            }
1918        } else {
1919            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1920                mFilmstripController.goToNextItem();
1921                return true;
1922            } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1923                boolean wentToPrevious = mFilmstripController.goToPreviousItem();
1924                if (!wentToPrevious) {
1925                  // at beginning of filmstrip, hide and go back to preview
1926                  mCameraAppUI.hideFilmstrip();
1927                }
1928                return true;
1929            }
1930        }
1931        return super.onKeyUp(keyCode, event);
1932    }
1933
1934    @Override
1935    public void onBackPressed() {
1936        if (!mCameraAppUI.onBackPressed()) {
1937            if (!mCurrentModule.onBackPressed()) {
1938                super.onBackPressed();
1939            }
1940        }
1941    }
1942
1943    @Override
1944    public boolean isAutoRotateScreen() {
1945        // TODO: Move to OrientationManager.
1946        return mAutoRotateScreen;
1947    }
1948
1949    @Override
1950    public boolean onCreateOptionsMenu(Menu menu) {
1951        MenuInflater inflater = getMenuInflater();
1952        inflater.inflate(R.menu.filmstrip_menu, menu);
1953        mActionBarMenu = menu;
1954
1955        // add a button for launching the gallery
1956        if (mGalleryIntent != null) {
1957            CharSequence appName =  IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
1958            if (appName != null) {
1959                MenuItem menuItem = menu.add(appName);
1960                menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
1961                menuItem.setIntent(mGalleryIntent);
1962
1963                Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1964                if (galleryLogo != null) {
1965                    menuItem.setIcon(galleryLogo);
1966                }
1967            }
1968        }
1969
1970        return super.onCreateOptionsMenu(menu);
1971    }
1972
1973    protected long getStorageSpaceBytes() {
1974        synchronized (mStorageSpaceLock) {
1975            return mStorageSpaceBytes;
1976        }
1977    }
1978
1979    protected interface OnStorageUpdateDoneListener {
1980        public void onStorageUpdateDone(long bytes);
1981    }
1982
1983    protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
1984        /*
1985         * We execute disk operations on a background thread in order to
1986         * free up the UI thread.  Synchronizing on the lock below ensures
1987         * that when getStorageSpaceBytes is called, the main thread waits
1988         * until this method has completed.
1989         *
1990         * However, .execute() does not ensure this execution block will be
1991         * run right away (.execute() schedules this AsyncTask for sometime
1992         * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
1993         * tries to execute the task in parellel with other AsyncTasks, but
1994         * there's still no guarantee).
1995         * e.g. don't call this then immediately call getStorageSpaceBytes().
1996         * Instead, pass in an OnStorageUpdateDoneListener.
1997         */
1998        (new AsyncTask<Void, Void, Long>() {
1999            @Override
2000            protected Long doInBackground(Void ... arg) {
2001                synchronized (mStorageSpaceLock) {
2002                    mStorageSpaceBytes = Storage.getAvailableSpace();
2003                    return mStorageSpaceBytes;
2004                }
2005            }
2006
2007            @Override
2008            protected void onPostExecute(Long bytes) {
2009                updateStorageHint(bytes);
2010                // This callback returns after I/O to check disk, so we could be
2011                // pausing and shutting down. If so, don't bother invoking.
2012                if (callback != null && !mPaused) {
2013                    callback.onStorageUpdateDone(bytes);
2014                } else {
2015                    Log.v(TAG, "ignoring storage callback after activity pause");
2016                }
2017            }
2018        }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2019    }
2020
2021    protected void updateStorageHint(long storageSpace) {
2022        if (!mIsActivityRunning) {
2023            return;
2024        }
2025
2026        String message = null;
2027        if (storageSpace == Storage.UNAVAILABLE) {
2028            message = getString(R.string.no_storage);
2029        } else if (storageSpace == Storage.PREPARING) {
2030            message = getString(R.string.preparing_sd);
2031        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2032            message = getString(R.string.access_sd_fail);
2033        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2034            message = getString(R.string.spaceIsLow_content);
2035        }
2036
2037        if (message != null) {
2038            Log.w(TAG, "Storage warning: " + message);
2039            if (mStorageHint == null) {
2040                mStorageHint = OnScreenHint.makeText(CameraActivity.this, message);
2041            } else {
2042                mStorageHint.setText(message);
2043            }
2044            mStorageHint.show();
2045            UsageStatistics.instance().storageWarning(storageSpace);
2046        } else if (mStorageHint != null) {
2047            mStorageHint.cancel();
2048            mStorageHint = null;
2049        }
2050    }
2051
2052    protected void setResultEx(int resultCode) {
2053        mResultCodeForTesting = resultCode;
2054        setResult(resultCode);
2055    }
2056
2057    protected void setResultEx(int resultCode, Intent data) {
2058        mResultCodeForTesting = resultCode;
2059        mResultDataForTesting = data;
2060        setResult(resultCode, data);
2061    }
2062
2063    public int getResultCode() {
2064        return mResultCodeForTesting;
2065    }
2066
2067    public Intent getResultData() {
2068        return mResultDataForTesting;
2069    }
2070
2071    public boolean isSecureCamera() {
2072        return mSecureCamera;
2073    }
2074
2075    @Override
2076    public boolean isPaused() {
2077        return mPaused;
2078    }
2079
2080    @Override
2081    public int getPreferredChildModeIndex(int modeIndex) {
2082        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2083            boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2084            if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule()) {
2085                modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2086            }
2087        }
2088        return modeIndex;
2089    }
2090
2091    @Override
2092    public void onModeSelected(int modeIndex) {
2093        if (mCurrentModeIndex == modeIndex) {
2094            return;
2095        }
2096
2097        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2098        // Record last used camera mode for quick switching
2099        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2100                || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2101            mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2102                                 Keys.KEY_CAMERA_MODULE_LAST_USED,
2103                                 modeIndex);
2104        }
2105
2106        closeModule(mCurrentModule);
2107
2108        // Select the correct module index from the mode switcher index.
2109        modeIndex = getPreferredChildModeIndex(modeIndex);
2110        setModuleFromModeIndex(modeIndex);
2111
2112        mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2113        mCameraAppUI.addShutterListener(mCurrentModule);
2114        mCameraAppUI.hideLetterboxing();
2115        openModule(mCurrentModule);
2116        mCurrentModule.onOrientationChanged(mLastRawOrientation);
2117        // Store the module index so we can use it the next time the Camera
2118        // starts up.
2119        mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2120                             Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2121    }
2122
2123    /**
2124     * Shows the settings dialog.
2125     */
2126    @Override
2127    public void onSettingsSelected() {
2128        UsageStatistics.instance().controlUsed(
2129                eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2130        Intent intent = new Intent(this, CameraSettingsActivity.class);
2131        startActivity(intent);
2132    }
2133
2134    @Override
2135    public void freezeScreenUntilPreviewReady() {
2136        mCameraAppUI.freezeScreenUntilPreviewReady();
2137    }
2138
2139    /**
2140     * Sets the mCurrentModuleIndex, creates a new module instance for the given
2141     * index an sets it as mCurrentModule.
2142     */
2143    private void setModuleFromModeIndex(int modeIndex) {
2144        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2145        if (agent == null) {
2146            return;
2147        }
2148        if (!agent.requestAppForCamera()) {
2149            mCameraController.closeCamera(true);
2150        }
2151        mCurrentModeIndex = agent.getModuleId();
2152        mCurrentModule = (CameraModule) agent.createModule(this);
2153    }
2154
2155    @Override
2156    public SettingsManager getSettingsManager() {
2157        return mSettingsManager;
2158    }
2159
2160    @Override
2161    public CameraServices getServices() {
2162        return (CameraServices) getApplication();
2163    }
2164
2165    public List<String> getSupportedModeNames() {
2166        List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2167        List<String> supported = new ArrayList<String>();
2168
2169        for (Integer modeIndex : indices) {
2170            String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2171            if (name != null && !name.equals("")) {
2172                supported.add(name);
2173            }
2174        }
2175        return supported;
2176    }
2177
2178    @Override
2179    public ButtonManager getButtonManager() {
2180        if (mButtonManager == null) {
2181            mButtonManager = new ButtonManager(this);
2182        }
2183        return mButtonManager;
2184    }
2185
2186    @Override
2187    public SoundPlayer getSoundPlayer() {
2188        return mSoundPlayer;
2189    }
2190
2191    /**
2192     * Creates an AlertDialog appropriate for choosing whether to enable
2193     * location on the first run of the app.
2194     */
2195    public AlertDialog getFirstTimeLocationAlert() {
2196        AlertDialog.Builder builder = new AlertDialog.Builder(this);
2197        builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
2198            @Override
2199            public void onCallback(Boolean locationOn) {
2200                Keys.setLocation(mSettingsManager, locationOn, mLocationManager);
2201            }
2202        });
2203        if (builder != null) {
2204            return builder.create();
2205        } else {
2206            return null;
2207        }
2208    }
2209
2210    /**
2211     * Launches an ACTION_EDIT intent for the given local data item. If
2212     * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2213     * the user start either the tiny planet editor or another photo edior.
2214     *
2215     * @param data The data item to edit.
2216     */
2217    public void launchEditor(LocalData data) {
2218        Intent intent = new Intent(Intent.ACTION_EDIT)
2219                .setDataAndType(data.getUri(), data.getMimeType())
2220                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2221        try {
2222            launchActivityByIntent(intent);
2223        } catch (ActivityNotFoundException e) {
2224            final String msgEditWith = getResources().getString(R.string.edit_with);
2225            launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2226        }
2227    }
2228
2229    @Override
2230    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2231        super.onCreateContextMenu(menu, v, menuInfo);
2232
2233        MenuInflater inflater = getMenuInflater();
2234        inflater.inflate(R.menu.filmstrip_context_menu, menu);
2235    }
2236
2237    @Override
2238    public boolean onContextItemSelected(MenuItem item) {
2239        switch (item.getItemId()) {
2240            case R.id.tiny_planet_editor:
2241                mMyFilmstripBottomControlListener.onTinyPlanet();
2242                return true;
2243            case R.id.photo_editor:
2244                mMyFilmstripBottomControlListener.onEdit();
2245                return true;
2246        }
2247        return false;
2248    }
2249
2250    /**
2251     * Launch the tiny planet editor.
2252     *
2253     * @param data The data must be a 360 degree stereographically mapped
2254     *            panoramic image. It will not be modified, instead a new item
2255     *            with the result will be added to the filmstrip.
2256     */
2257    public void launchTinyPlanetEditor(LocalData data) {
2258        TinyPlanetFragment fragment = new TinyPlanetFragment();
2259        Bundle bundle = new Bundle();
2260        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString());
2261        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
2262        fragment.setArguments(bundle);
2263        fragment.show(getFragmentManager(), "tiny_planet");
2264    }
2265
2266    /**
2267     * Returns what UI mode (capture mode or filmstrip) we are in.
2268     * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2269     */
2270    private int currentUserInterfaceMode() {
2271        int mode = NavigationChange.Mode.UNKNOWN_MODE;
2272        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2273            mode = NavigationChange.Mode.PHOTO_CAPTURE;
2274        }
2275        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2276            mode = NavigationChange.Mode.VIDEO_CAPTURE;
2277        }
2278        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2279            mode = NavigationChange.Mode.LENS_BLUR;
2280        }
2281        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2282            mode = NavigationChange.Mode.HDR_PLUS;
2283        }
2284        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2285            mode = NavigationChange.Mode.PHOTO_SPHERE;
2286        }
2287        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2288            mode = NavigationChange.Mode.PANORAMA;
2289        }
2290        if (mFilmstripVisible) {
2291            mode = NavigationChange.Mode.FILMSTRIP;
2292        }
2293        return mode;
2294    }
2295
2296    private void openModule(CameraModule module) {
2297        module.init(this, isSecureCamera(), isCaptureIntent());
2298        module.hardResetSettings(mSettingsManager);
2299        module.resume();
2300        UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2301                NavigationChange.InteractionCause.BUTTON);
2302        updatePreviewVisibility();
2303    }
2304
2305    private void closeModule(CameraModule module) {
2306        module.pause();
2307        mCameraAppUI.clearModuleUI();
2308    }
2309
2310    private void performDeletion() {
2311        if (!mPendingDeletion) {
2312            return;
2313        }
2314        hideUndoDeletionBar(false);
2315        mDataAdapter.executeDeletion();
2316    }
2317
2318    public void showUndoDeletionBar() {
2319        if (mPendingDeletion) {
2320            performDeletion();
2321        }
2322        Log.v(TAG, "showing undo bar");
2323        mPendingDeletion = true;
2324        if (mUndoDeletionBar == null) {
2325            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2326                    mAboveFilmstripControlLayout, true);
2327            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2328            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2329            button.setOnClickListener(new View.OnClickListener() {
2330                @Override
2331                public void onClick(View view) {
2332                    mDataAdapter.undoDataRemoval();
2333                    hideUndoDeletionBar(true);
2334                }
2335            });
2336            // Setting undo bar clickable to avoid touch events going through
2337            // the bar to the buttons (eg. edit button, etc) underneath the bar.
2338            mUndoDeletionBar.setClickable(true);
2339            // When there is user interaction going on with the undo button, we
2340            // do not want to hide the undo bar.
2341            button.setOnTouchListener(new View.OnTouchListener() {
2342                @Override
2343                public boolean onTouch(View v, MotionEvent event) {
2344                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2345                        mIsUndoingDeletion = true;
2346                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2347                        mIsUndoingDeletion = false;
2348                    }
2349                    return false;
2350                }
2351            });
2352        }
2353        mUndoDeletionBar.setAlpha(0f);
2354        mUndoDeletionBar.setVisibility(View.VISIBLE);
2355        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2356    }
2357
2358    private void hideUndoDeletionBar(boolean withAnimation) {
2359        Log.v(TAG, "Hiding undo deletion bar");
2360        mPendingDeletion = false;
2361        if (mUndoDeletionBar != null) {
2362            if (withAnimation) {
2363                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2364                        .setListener(new Animator.AnimatorListener() {
2365                            @Override
2366                            public void onAnimationStart(Animator animation) {
2367                                // Do nothing.
2368                            }
2369
2370                            @Override
2371                            public void onAnimationEnd(Animator animation) {
2372                                mUndoDeletionBar.setVisibility(View.GONE);
2373                            }
2374
2375                            @Override
2376                            public void onAnimationCancel(Animator animation) {
2377                                // Do nothing.
2378                            }
2379
2380                            @Override
2381                            public void onAnimationRepeat(Animator animation) {
2382                                // Do nothing.
2383                            }
2384                        }).start();
2385            } else {
2386                mUndoDeletionBar.setVisibility(View.GONE);
2387            }
2388        }
2389    }
2390
2391    @Override
2392    public void onOrientationChanged(int orientation) {
2393        // We keep the last known orientation. So if the user first orient
2394        // the camera then point the camera to floor or sky, we still have
2395        // the correct orientation.
2396        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
2397            return;
2398        }
2399        mLastRawOrientation = orientation;
2400        if (mCurrentModule != null) {
2401            mCurrentModule.onOrientationChanged(orientation);
2402        }
2403    }
2404
2405    /**
2406     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2407     * capture intent.
2408     *
2409     * @param enable {@code true} to enable swipe.
2410     */
2411    public void setSwipingEnabled(boolean enable) {
2412        // TODO: Bring back the functionality.
2413        if (isCaptureIntent()) {
2414            // lockPreview(true);
2415        } else {
2416            // lockPreview(!enable);
2417        }
2418    }
2419
2420    // Accessor methods for getting latency times used in performance testing
2421    public long getFirstPreviewTime() {
2422        if (mCurrentModule instanceof PhotoModule) {
2423            long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2424            if (coverHiddenTime != -1) {
2425                return coverHiddenTime - mOnCreateTime;
2426            }
2427        }
2428        return -1;
2429    }
2430
2431    public long getAutoFocusTime() {
2432        return (mCurrentModule instanceof PhotoModule) ?
2433                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2434    }
2435
2436    public long getShutterLag() {
2437        return (mCurrentModule instanceof PhotoModule) ?
2438                ((PhotoModule) mCurrentModule).mShutterLag : -1;
2439    }
2440
2441    public long getShutterToPictureDisplayedTime() {
2442        return (mCurrentModule instanceof PhotoModule) ?
2443                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2444    }
2445
2446    public long getPictureDisplayedToJpegCallbackTime() {
2447        return (mCurrentModule instanceof PhotoModule) ?
2448                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2449    }
2450
2451    public long getJpegCallbackFinishTime() {
2452        return (mCurrentModule instanceof PhotoModule) ?
2453                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2454    }
2455
2456    public long getCaptureStartTime() {
2457        return (mCurrentModule instanceof PhotoModule) ?
2458                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2459    }
2460
2461    public boolean isRecording() {
2462        return (mCurrentModule instanceof VideoModule) ?
2463                ((VideoModule) mCurrentModule).isRecording() : false;
2464    }
2465
2466    public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2467        return mCameraController;
2468    }
2469
2470    // For debugging purposes only.
2471    public CameraModule getCurrentModule() {
2472        return mCurrentModule;
2473    }
2474
2475    @Override
2476    public void showTutorial(AbstractTutorialOverlay tutorial) {
2477        mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2478    }
2479
2480    @Override
2481    public void showErrorAndFinish(int messageId) {
2482        CameraUtil.showErrorAndFinish(this, messageId);
2483    }
2484
2485    /**
2486     * Reads the current location recording settings and passes it on to the
2487     * location manager.
2488     */
2489    public void syncLocationManagerSetting() {
2490        Keys.syncLocationManager(mSettingsManager, mLocationManager);
2491    }
2492
2493    private void keepScreenOnForAWhile() {
2494        if (mKeepScreenOn) {
2495            return;
2496        }
2497        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2498        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2499        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2500    }
2501
2502    private void resetScreenOn() {
2503        mKeepScreenOn = false;
2504        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2505        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2506    }
2507
2508    /**
2509     * @return {@code true} if the Gallery is launched successfully.
2510     */
2511    private boolean startGallery() {
2512        if (mGalleryIntent == null) {
2513            return false;
2514        }
2515        try {
2516            UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2517                    NavigationChange.InteractionCause.BUTTON);
2518            Intent startGalleryIntent = new Intent(mGalleryIntent);
2519            int currentDataId = mFilmstripController.getCurrentId();
2520            LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
2521            if (currentLocalData != null) {
2522                GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri());
2523            }
2524            launchActivityByIntent(startGalleryIntent);
2525        } catch (ActivityNotFoundException e) {
2526            Log.w(TAG, "Failed to launch gallery activity, closing");
2527        }
2528        return false;
2529    }
2530
2531    private void setNfcBeamPushUriFromData(LocalData data) {
2532        final Uri uri = data.getUri();
2533        if (uri != Uri.EMPTY) {
2534            mNfcPushUris[0] = uri;
2535        } else {
2536            mNfcPushUris[0] = null;
2537        }
2538    }
2539
2540    /**
2541     * Updates the visibility of the filmstrip bottom controls and action bar.
2542     */
2543    private void updateUiByData(final int dataId) {
2544        final LocalData currentData = mDataAdapter.getLocalData(dataId);
2545        if (currentData == null) {
2546            Log.w(TAG, "Current data ID not found.");
2547            hideSessionProgress();
2548            return;
2549        }
2550        updateActionBarMenu(currentData);
2551
2552        /* Bottom controls. */
2553        updateBottomControlsByData(currentData);
2554
2555        if (isSecureCamera()) {
2556            // We cannot show buttons in secure camera since go to other
2557            // activities might create a security hole.
2558            mCameraAppUI.getFilmstripBottomControls().hideControls();
2559            return;
2560        }
2561
2562
2563        setNfcBeamPushUriFromData(currentData);
2564
2565        if (!mDataAdapter.isMetadataUpdated(dataId)) {
2566            mDataAdapter.updateMetadata(dataId);
2567        }
2568    }
2569
2570    /**
2571     * Updates the bottom controls based on the data.
2572     */
2573    private void updateBottomControlsByData(final LocalData currentData) {
2574
2575        final CameraAppUI.BottomPanel filmstripBottomPanel =
2576                mCameraAppUI.getFilmstripBottomControls();
2577        filmstripBottomPanel.showControls();
2578        filmstripBottomPanel.setEditButtonVisibility(
2579                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2580        filmstripBottomPanel.setShareButtonVisibility(
2581                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2582        filmstripBottomPanel.setDeleteButtonVisibility(
2583                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2584
2585        /* Progress bar */
2586
2587        Uri contentUri = currentData.getUri();
2588        CaptureSessionManager sessionManager = getServices()
2589                .getCaptureSessionManager();
2590
2591        if (sessionManager.hasErrorMessage(contentUri)) {
2592            showProcessError(sessionManager.getErrorMesage(contentUri));
2593        } else {
2594            filmstripBottomPanel.hideProgressError();
2595            CaptureSession session = sessionManager.getSession(contentUri);
2596
2597            if (session != null) {
2598                int sessionProgress = session.getProgress();
2599
2600                if (sessionProgress < 0) {
2601                    hideSessionProgress();
2602                } else {
2603                    CharSequence progressMessage = session.getProgressMessage();
2604                    showSessionProgress(progressMessage);
2605                    updateSessionProgress(sessionProgress);
2606                }
2607            } else {
2608                hideSessionProgress();
2609            }
2610        }
2611
2612        /* View button */
2613
2614        // We need to add this to a separate DB.
2615        final int viewButtonVisibility;
2616        if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2617            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2618        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2619            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2620        } else {
2621            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2622        }
2623
2624        filmstripBottomPanel.setTinyPlanetEnabled(
2625                PanoramaMetadataLoader.isPanorama360(currentData));
2626        filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2627    }
2628
2629    private class PeekAnimationHandler extends Handler {
2630        private class DataAndCallback {
2631            LocalData mData;
2632            com.android.camera.util.Callback<Bitmap> mCallback;
2633
2634            public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2635                    callback) {
2636                mData = data;
2637                mCallback = callback;
2638            }
2639        }
2640
2641        public PeekAnimationHandler(Looper looper) {
2642            super(looper);
2643        }
2644
2645        /**
2646         * Starts the animation decoding job and posts a {@code Runnable} back
2647         * when when the decoding is done.
2648         *
2649         * @param data The data item to decode the thumbnail for.
2650         * @param callback {@link com.android.camera.util.Callback} after the
2651         *                 decoding is done.
2652         */
2653        public void startDecodingJob(final LocalData data,
2654                final com.android.camera.util.Callback<Bitmap> callback) {
2655            PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2656                    new DataAndCallback(data, callback)).sendToTarget();
2657        }
2658
2659        @Override
2660        public void handleMessage(Message msg) {
2661            final LocalData data = ((DataAndCallback) msg.obj).mData;
2662            final com.android.camera.util.Callback<Bitmap> callback =
2663                    ((DataAndCallback) msg.obj).mCallback;
2664            if (data == null || callback == null) {
2665                return;
2666            }
2667
2668            final Bitmap bitmap;
2669            switch (data.getLocalDataType()) {
2670                case LocalData.LOCAL_IN_PROGRESS_DATA:
2671                    byte[] jpegData = Storage.getJpegForSession(data.getUri());
2672                    if (jpegData != null) {
2673                        bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
2674                    } else {
2675                        bitmap = null;
2676                    }
2677                    break;
2678
2679                case LocalData.LOCAL_IMAGE:
2680                    FileInputStream stream;
2681                    try {
2682                        stream = new FileInputStream(data.getPath());
2683                    } catch (FileNotFoundException e) {
2684                        Log.e(TAG, "File not found:" + data.getPath());
2685                        return;
2686                    }
2687                    Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2688                            data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2689                            mAboveFilmstripControlLayout.getMeasuredHeight());
2690                    if (data.getRotation() % 180 != 0) {
2691                        int dummy = dim.x;
2692                        dim.x = dim.y;
2693                        dim.y = dummy;
2694                    }
2695                    bitmap = LocalDataUtil
2696                            .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2697                                    (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2698                                    data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2699                    break;
2700
2701                case LocalData.LOCAL_VIDEO:
2702                    bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2703                    break;
2704
2705                default:
2706                    bitmap = null;
2707                    break;
2708            }
2709
2710            if (bitmap == null) {
2711                return;
2712            }
2713
2714            mMainHandler.post(new Runnable() {
2715                @Override
2716                public void run() {
2717                    callback.onCallback(bitmap);
2718                }
2719            });
2720        }
2721    }
2722
2723    private void showDetailsDialog(int dataId) {
2724        final LocalData data = mDataAdapter.getLocalData(dataId);
2725        if (data == null) {
2726            return;
2727        }
2728        MediaDetails details = data.getMediaDetails(getAndroidContext());
2729        if (details == null) {
2730            return;
2731        }
2732        Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details);
2733        detailDialog.show();
2734        UsageStatistics.instance().mediaInteraction(
2735                fileNameFromDataID(dataId), MediaInteraction.InteractionType.DETAILS,
2736                NavigationChange.InteractionCause.BUTTON, fileAgeFromDataID(dataId));
2737    }
2738
2739    /**
2740     * Show or hide action bar items depending on current data type.
2741     */
2742    private void updateActionBarMenu(LocalData data) {
2743        if (mActionBarMenu == null) {
2744            return;
2745        }
2746
2747        MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2748        if (detailsMenuItem == null) {
2749            return;
2750        }
2751
2752        int type = data.getLocalDataType();
2753        boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO);
2754        detailsMenuItem.setVisible(showDetails);
2755    }
2756}
2757