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