Home | History | Annotate | Download | only in camera
      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 
     18 package com.android.camera;
     19 
     20 import android.animation.Animator;
     21 import android.annotation.TargetApi;
     22 import android.app.ActionBar;
     23 import android.app.AlertDialog;
     24 import android.app.Dialog;
     25 import android.content.ActivityNotFoundException;
     26 import android.content.BroadcastReceiver;
     27 import android.content.ContentResolver;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.IntentFilter;
     31 import android.content.pm.ActivityInfo;
     32 import android.content.res.Configuration;
     33 import android.graphics.Bitmap;
     34 import android.graphics.BitmapFactory;
     35 import android.graphics.Matrix;
     36 import android.graphics.Point;
     37 import android.graphics.RectF;
     38 import android.graphics.SurfaceTexture;
     39 import android.graphics.drawable.ColorDrawable;
     40 import android.graphics.drawable.Drawable;
     41 import android.net.Uri;
     42 import android.nfc.NfcAdapter;
     43 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
     44 import android.nfc.NfcEvent;
     45 import android.os.AsyncTask;
     46 import android.os.Build;
     47 import android.os.Bundle;
     48 import android.os.Handler;
     49 import android.os.HandlerThread;
     50 import android.os.Looper;
     51 import android.os.Message;
     52 import android.provider.MediaStore;
     53 import android.provider.Settings;
     54 import android.text.TextUtils;
     55 import android.util.CameraPerformanceTracker;
     56 import android.view.ContextMenu;
     57 import android.view.ContextMenu.ContextMenuInfo;
     58 import android.view.KeyEvent;
     59 import android.view.Menu;
     60 import android.view.MenuInflater;
     61 import android.view.MenuItem;
     62 import android.view.MotionEvent;
     63 import android.view.View;
     64 import android.view.View.OnSystemUiVisibilityChangeListener;
     65 import android.view.ViewGroup;
     66 import android.view.Window;
     67 import android.view.WindowManager;
     68 import android.widget.FrameLayout;
     69 import android.widget.ImageView;
     70 import android.widget.ShareActionProvider;
     71 
     72 import com.android.camera.app.AppController;
     73 import com.android.camera.app.CameraAppUI;
     74 import com.android.camera.app.CameraController;
     75 import com.android.camera.app.CameraProvider;
     76 import com.android.camera.app.CameraServices;
     77 import com.android.camera.app.LocationManager;
     78 import com.android.camera.app.MemoryManager;
     79 import com.android.camera.app.MemoryQuery;
     80 import com.android.camera.app.ModuleManager;
     81 import com.android.camera.app.ModuleManagerImpl;
     82 import com.android.camera.app.MotionManager;
     83 import com.android.camera.app.OrientationManager;
     84 import com.android.camera.app.OrientationManagerImpl;
     85 import com.android.camera.data.CameraDataAdapter;
     86 import com.android.camera.data.FixedLastDataAdapter;
     87 import com.android.camera.data.LocalData;
     88 import com.android.camera.data.LocalDataAdapter;
     89 import com.android.camera.data.LocalDataUtil;
     90 import com.android.camera.data.LocalDataViewType;
     91 import com.android.camera.data.LocalMediaData;
     92 import com.android.camera.data.LocalMediaObserver;
     93 import com.android.camera.data.LocalSessionData;
     94 import com.android.camera.data.MediaDetails;
     95 import com.android.camera.data.MetadataLoader;
     96 import com.android.camera.data.PanoramaMetadataLoader;
     97 import com.android.camera.data.RgbzMetadataLoader;
     98 import com.android.camera.data.SimpleViewData;
     99 import com.android.camera.debug.Log;
    100 import com.android.camera.filmstrip.FilmstripContentPanel;
    101 import com.android.camera.filmstrip.FilmstripController;
    102 import com.android.camera.hardware.HardwareSpec;
    103 import com.android.camera.hardware.HardwareSpecImpl;
    104 import com.android.camera.module.ModuleController;
    105 import com.android.camera.module.ModulesInfo;
    106 import com.android.camera.one.OneCameraException;
    107 import com.android.camera.one.OneCameraManager;
    108 import com.android.camera.session.CaptureSession;
    109 import com.android.camera.session.CaptureSessionManager;
    110 import com.android.camera.session.CaptureSessionManager.SessionListener;
    111 import com.android.camera.settings.AppUpgrader;
    112 import com.android.camera.settings.CameraSettingsActivity;
    113 import com.android.camera.settings.Keys;
    114 import com.android.camera.settings.SettingsManager;
    115 import com.android.camera.settings.SettingsUtil;
    116 import com.android.camera.tinyplanet.TinyPlanetFragment;
    117 import com.android.camera.ui.AbstractTutorialOverlay;
    118 import com.android.camera.ui.DetailsDialog;
    119 import com.android.camera.ui.MainActivityLayout;
    120 import com.android.camera.ui.ModeListView;
    121 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
    122 import com.android.camera.ui.PreviewStatusListener;
    123 import com.android.camera.util.ApiHelper;
    124 import com.android.camera.util.Callback;
    125 import com.android.camera.util.CameraUtil;
    126 import com.android.camera.util.GalleryHelper;
    127 import com.android.camera.util.GcamHelper;
    128 import com.android.camera.util.GoogleHelpHelper;
    129 import com.android.camera.util.IntentHelper;
    130 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
    131 import com.android.camera.util.QuickActivity;
    132 import com.android.camera.util.ReleaseHelper;
    133 import com.android.camera.util.UsageStatistics;
    134 import com.android.camera.widget.FilmstripView;
    135 import com.android.camera.widget.Preloader;
    136 import com.android.camera2.R;
    137 import com.android.ex.camera2.portability.CameraAgent;
    138 import com.android.ex.camera2.portability.CameraAgentFactory;
    139 import com.android.ex.camera2.portability.CameraExceptionHandler;
    140 import com.android.ex.camera2.portability.CameraSettings;
    141 import com.bumptech.glide.Glide;
    142 import com.bumptech.glide.GlideBuilder;
    143 import com.bumptech.glide.MemoryCategory;
    144 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
    145 import com.google.common.logging.eventprotos;
    146 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
    147 import com.google.common.logging.eventprotos.MediaInteraction;
    148 import com.google.common.logging.eventprotos.NavigationChange;
    149 
    150 import java.io.File;
    151 import java.io.FileInputStream;
    152 import java.io.FileNotFoundException;
    153 import java.lang.ref.WeakReference;
    154 import java.util.ArrayList;
    155 import java.util.HashMap;
    156 import java.util.List;
    157 import java.util.concurrent.TimeUnit;
    158 
    159 public 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