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