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 package com.android.camera;
     18 
     19 import android.animation.Animator;
     20 import android.annotation.TargetApi;
     21 import android.app.ActionBar;
     22 import android.app.Activity;
     23 import android.content.ActivityNotFoundException;
     24 import android.content.BroadcastReceiver;
     25 import android.content.ComponentName;
     26 import android.content.ContentResolver;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.IntentFilter;
     30 import android.content.ServiceConnection;
     31 import android.content.SharedPreferences;
     32 import android.content.pm.ActivityInfo;
     33 import android.content.res.Configuration;
     34 import android.graphics.drawable.ColorDrawable;
     35 import android.net.Uri;
     36 import android.nfc.NfcAdapter;
     37 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
     38 import android.nfc.NfcEvent;
     39 import android.os.AsyncTask;
     40 import android.os.Build;
     41 import android.os.Bundle;
     42 import android.os.Handler;
     43 import android.os.IBinder;
     44 import android.os.Looper;
     45 import android.os.Message;
     46 import android.preference.PreferenceManager;
     47 import android.provider.MediaStore;
     48 import android.provider.Settings;
     49 import android.util.Log;
     50 import android.view.KeyEvent;
     51 import android.view.LayoutInflater;
     52 import android.view.Menu;
     53 import android.view.MenuInflater;
     54 import android.view.MenuItem;
     55 import android.view.MotionEvent;
     56 import android.view.OrientationEventListener;
     57 import android.view.View;
     58 import android.view.ViewGroup;
     59 import android.view.Window;
     60 import android.view.WindowManager;
     61 import android.widget.FrameLayout;
     62 import android.widget.ImageView;
     63 import android.widget.ProgressBar;
     64 import android.widget.ShareActionProvider;
     65 
     66 import com.android.camera.app.AppManagerFactory;
     67 import com.android.camera.app.PlaceholderManager;
     68 import com.android.camera.app.PanoramaStitchingManager;
     69 import com.android.camera.crop.CropActivity;
     70 import com.android.camera.data.CameraDataAdapter;
     71 import com.android.camera.data.CameraPreviewData;
     72 import com.android.camera.data.FixedFirstDataAdapter;
     73 import com.android.camera.data.FixedLastDataAdapter;
     74 import com.android.camera.data.InProgressDataWrapper;
     75 import com.android.camera.data.LocalData;
     76 import com.android.camera.data.LocalDataAdapter;
     77 import com.android.camera.data.LocalMediaObserver;
     78 import com.android.camera.data.MediaDetails;
     79 import com.android.camera.data.SimpleViewData;
     80 import com.android.camera.tinyplanet.TinyPlanetFragment;
     81 import com.android.camera.ui.ModuleSwitcher;
     82 import com.android.camera.ui.DetailsDialog;
     83 import com.android.camera.ui.FilmStripView;
     84 import com.android.camera.util.ApiHelper;
     85 import com.android.camera.util.CameraUtil;
     86 import com.android.camera.util.GcamHelper;
     87 import com.android.camera.util.IntentHelper;
     88 import com.android.camera.util.PhotoSphereHelper;
     89 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
     90 import com.android.camera.util.UsageStatistics;
     91 import com.android.camera2.R;
     92 
     93 import java.io.File;
     94 
     95 import static com.android.camera.CameraManager.CameraOpenErrorCallback;
     96 
     97 public class CameraActivity extends Activity
     98         implements ModuleSwitcher.ModuleSwitchListener,
     99         ActionBar.OnMenuVisibilityListener,
    100         ShareActionProvider.OnShareTargetSelectedListener {
    101 
    102     private static final String TAG = "CAM_Activity";
    103 
    104     private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
    105             "android.media.action.STILL_IMAGE_CAMERA_SECURE";
    106     public static final String ACTION_IMAGE_CAPTURE_SECURE =
    107             "android.media.action.IMAGE_CAPTURE_SECURE";
    108     public static final String ACTION_TRIM_VIDEO =
    109             "com.android.camera.action.TRIM";
    110     public static final String MEDIA_ITEM_PATH = "media-item-path";
    111 
    112     // The intent extra for camera from secure lock screen. True if the gallery
    113     // should only show newly captured pictures. sSecureAlbumId does not
    114     // increment. This is used when switching between camera, camcorder, and
    115     // panorama. If the extra is not set, it is in the normal camera mode.
    116     public static final String SECURE_CAMERA_EXTRA = "secure_camera";
    117 
    118     /**
    119      * Request code from an activity we started that indicated that we do not
    120      * want to reset the view to the preview in onResume.
    121      */
    122     public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
    123 
    124     public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
    125 
    126     private static final int HIDE_ACTION_BAR = 1;
    127     private static final long SHOW_ACTION_BAR_TIMEOUT_MS = 3000;
    128 
    129     /** Whether onResume should reset the view to the preview. */
    130     private boolean mResetToPreviewOnResume = true;
    131 
    132     // Supported operations at FilmStripView. Different data has different
    133     // set of supported operations.
    134     private static final int SUPPORT_DELETE = 1 << 0;
    135     private static final int SUPPORT_ROTATE = 1 << 1;
    136     private static final int SUPPORT_INFO = 1 << 2;
    137     private static final int SUPPORT_CROP = 1 << 3;
    138     private static final int SUPPORT_SETAS = 1 << 4;
    139     private static final int SUPPORT_EDIT = 1 << 5;
    140     private static final int SUPPORT_TRIM = 1 << 6;
    141     private static final int SUPPORT_SHARE = 1 << 7;
    142     private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8;
    143     private static final int SUPPORT_SHOW_ON_MAP = 1 << 9;
    144     private static final int SUPPORT_ALL = 0xffffffff;
    145 
    146     /** This data adapter is used by FilmStripView. */
    147     private LocalDataAdapter mDataAdapter;
    148     /** This data adapter represents the real local camera data. */
    149     private LocalDataAdapter mWrappedDataAdapter;
    150 
    151     private PanoramaStitchingManager mPanoramaManager;
    152     private PlaceholderManager mPlaceholderManager;
    153     private int mCurrentModuleIndex;
    154     private CameraModule mCurrentModule;
    155     private FrameLayout mAboveFilmstripControlLayout;
    156     private View mCameraModuleRootView;
    157     private FilmStripView mFilmStripView;
    158     private ProgressBar mBottomProgress;
    159     private View mPanoStitchingPanel;
    160     private int mResultCodeForTesting;
    161     private Intent mResultDataForTesting;
    162     private OnScreenHint mStorageHint;
    163     private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
    164     private boolean mAutoRotateScreen;
    165     private boolean mSecureCamera;
    166     // This is a hack to speed up the start of SecureCamera.
    167     private static boolean sFirstStartAfterScreenOn = true;
    168     private int mLastRawOrientation;
    169     private MyOrientationEventListener mOrientationListener;
    170     private Handler mMainHandler;
    171     private PanoramaViewHelper mPanoramaViewHelper;
    172     private CameraPreviewData mCameraPreviewData;
    173     private ActionBar mActionBar;
    174     private OnActionBarVisibilityListener mOnActionBarVisibilityListener = null;
    175     private Menu mActionBarMenu;
    176     private ViewGroup mUndoDeletionBar;
    177     private boolean mIsUndoingDeletion = false;
    178 
    179     private Uri[] mNfcPushUris = new Uri[1];
    180 
    181     private ShareActionProvider mStandardShareActionProvider;
    182     private Intent mStandardShareIntent;
    183     private ShareActionProvider mPanoramaShareActionProvider;
    184     private Intent mPanoramaShareIntent;
    185     private LocalMediaObserver mLocalImagesObserver;
    186     private LocalMediaObserver mLocalVideosObserver;
    187 
    188     private final int DEFAULT_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
    189             | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
    190     private boolean mPendingDeletion = false;
    191 
    192     private Intent mVideoShareIntent;
    193     private Intent mImageShareIntent;
    194 
    195     private class MyOrientationEventListener
    196             extends OrientationEventListener {
    197         public MyOrientationEventListener(Context context) {
    198             super(context);
    199         }
    200 
    201         @Override
    202         public void onOrientationChanged(int orientation) {
    203             // We keep the last known orientation. So if the user first orient
    204             // the camera then point the camera to floor or sky, we still have
    205             // the correct orientation.
    206             if (orientation == ORIENTATION_UNKNOWN) {
    207                 return;
    208             }
    209             mLastRawOrientation = orientation;
    210             mCurrentModule.onOrientationChanged(orientation);
    211         }
    212     }
    213 
    214     private MediaSaveService mMediaSaveService;
    215     private ServiceConnection mConnection = new ServiceConnection() {
    216         @Override
    217         public void onServiceConnected(ComponentName className, IBinder b) {
    218             mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService();
    219             mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
    220         }
    221 
    222         @Override
    223         public void onServiceDisconnected(ComponentName className) {
    224             if (mMediaSaveService != null) {
    225                 mMediaSaveService.setListener(null);
    226                 mMediaSaveService = null;
    227             }
    228         }
    229     };
    230 
    231     private CameraOpenErrorCallback mCameraOpenErrorCallback =
    232             new CameraOpenErrorCallback() {
    233                 @Override
    234                 public void onCameraDisabled(int cameraId) {
    235                     UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
    236                             UsageStatistics.ACTION_OPEN_FAIL, "security");
    237 
    238                     CameraUtil.showErrorAndFinish(CameraActivity.this,
    239                             R.string.camera_disabled);
    240                 }
    241 
    242                 @Override
    243                 public void onDeviceOpenFailure(int cameraId) {
    244                     UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
    245                             UsageStatistics.ACTION_OPEN_FAIL, "open");
    246 
    247                     CameraUtil.showErrorAndFinish(CameraActivity.this,
    248                             R.string.cannot_connect_camera);
    249                 }
    250 
    251                 @Override
    252                 public void onReconnectionFailure(CameraManager mgr) {
    253                     UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
    254                             UsageStatistics.ACTION_OPEN_FAIL, "reconnect");
    255 
    256                     CameraUtil.showErrorAndFinish(CameraActivity.this,
    257                             R.string.cannot_connect_camera);
    258                 }
    259             };
    260 
    261     // close activity when screen turns off
    262     private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
    263         @Override
    264         public void onReceive(Context context, Intent intent) {
    265             finish();
    266         }
    267     };
    268 
    269     private static BroadcastReceiver sScreenOffReceiver;
    270 
    271     private static class ScreenOffReceiver extends BroadcastReceiver {
    272         @Override
    273         public void onReceive(Context context, Intent intent) {
    274             sFirstStartAfterScreenOn = true;
    275         }
    276     }
    277 
    278     private class MainHandler extends Handler {
    279         public MainHandler(Looper looper) {
    280             super(looper);
    281         }
    282 
    283         @Override
    284         public void handleMessage(Message msg) {
    285             if (msg.what == HIDE_ACTION_BAR) {
    286                 removeMessages(HIDE_ACTION_BAR);
    287                 CameraActivity.this.setSystemBarsVisibility(false);
    288             }
    289         }
    290     }
    291 
    292     public interface OnActionBarVisibilityListener {
    293         public void onActionBarVisibilityChanged(boolean isVisible);
    294     }
    295 
    296     public void setOnActionBarVisibilityListener(OnActionBarVisibilityListener listener) {
    297         mOnActionBarVisibilityListener = listener;
    298     }
    299 
    300     public static boolean isFirstStartAfterScreenOn() {
    301         return sFirstStartAfterScreenOn;
    302     }
    303 
    304     public static void resetFirstStartAfterScreenOn() {
    305         sFirstStartAfterScreenOn = false;
    306     }
    307 
    308     private String fileNameFromDataID(int dataID) {
    309         final LocalData localData = mDataAdapter.getLocalData(dataID);
    310 
    311         File localFile = new File(localData.getPath());
    312         return localFile.getName();
    313     }
    314 
    315     private FilmStripView.Listener mFilmStripListener =
    316             new FilmStripView.Listener() {
    317                 @Override
    318                 public void onDataPromoted(int dataID) {
    319                     UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
    320                             UsageStatistics.ACTION_DELETE, "promoted", 0,
    321                             UsageStatistics.hashFileName(fileNameFromDataID(dataID)));
    322 
    323                     removeData(dataID);
    324                 }
    325 
    326                 @Override
    327                 public void onDataDemoted(int dataID) {
    328                     UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
    329                             UsageStatistics.ACTION_DELETE, "demoted", 0,
    330                             UsageStatistics.hashFileName(fileNameFromDataID(dataID)));
    331 
    332                     removeData(dataID);
    333                 }
    334 
    335                 @Override
    336                 public void onDataFullScreenChange(int dataID, boolean full) {
    337                     boolean isCameraID = isCameraPreview(dataID);
    338                     if (!isCameraID) {
    339                         if (!full) {
    340                             // Always show action bar in filmstrip mode
    341                             CameraActivity.this.setSystemBarsVisibility(true, false);
    342                         } else if (mActionBar.isShowing()) {
    343                             // Hide action bar after time out in full screen mode
    344                             mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR,
    345                                     SHOW_ACTION_BAR_TIMEOUT_MS);
    346                         }
    347                     }
    348                 }
    349 
    350                 /**
    351                  * Check if the local data corresponding to dataID is the camera
    352                  * preview.
    353                  *
    354                  * @param dataID the ID of the local data
    355                  * @return true if the local data is not null and it is the
    356                  *         camera preview.
    357                  */
    358                 private boolean isCameraPreview(int dataID) {
    359                     LocalData localData = mDataAdapter.getLocalData(dataID);
    360                     if (localData == null) {
    361                         Log.w(TAG, "Current data ID not found.");
    362                         return false;
    363                     }
    364                     return localData.getLocalDataType() == LocalData.LOCAL_CAMERA_PREVIEW;
    365                 }
    366 
    367                 @Override
    368                 public void onReload() {
    369                     setPreviewControlsVisibility(true);
    370                     CameraActivity.this.setSystemBarsVisibility(false);
    371                 }
    372 
    373                 @Override
    374                 public void onCurrentDataCentered(int dataID) {
    375                     if (dataID != 0 && !mFilmStripView.isCameraPreview()) {
    376                         // For now, We ignore all items that are not the camera preview.
    377                         return;
    378                     }
    379 
    380                     if(!arePreviewControlsVisible()) {
    381                         setPreviewControlsVisibility(true);
    382                         CameraActivity.this.setSystemBarsVisibility(false);
    383                     }
    384                 }
    385 
    386                 @Override
    387                 public void onCurrentDataOffCentered(int dataID) {
    388                     if (dataID != 0 && !mFilmStripView.isCameraPreview()) {
    389                         // For now, We ignore all items that are not the camera preview.
    390                         return;
    391                     }
    392 
    393                     if (arePreviewControlsVisible()) {
    394                         setPreviewControlsVisibility(false);
    395                     }
    396                 }
    397 
    398                 @Override
    399                 public void onDataFocusChanged(final int dataID, final boolean focused) {
    400                     // Delay hiding action bar if there is any user interaction
    401                     if (mMainHandler.hasMessages(HIDE_ACTION_BAR)) {
    402                         mMainHandler.removeMessages(HIDE_ACTION_BAR);
    403                         mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR,
    404                                 SHOW_ACTION_BAR_TIMEOUT_MS);
    405                     }
    406                     // TODO: This callback is UI event callback, should always
    407                     // happen on UI thread. Find the reason for this
    408                     // runOnUiThread() and fix it.
    409                     runOnUiThread(new Runnable() {
    410                         @Override
    411                         public void run() {
    412                             LocalData currentData = mDataAdapter.getLocalData(dataID);
    413                             if (currentData == null) {
    414                                 Log.w(TAG, "Current data ID not found.");
    415                                 hidePanoStitchingProgress();
    416                                 return;
    417                             }
    418                             boolean isCameraID = currentData.getLocalDataType() ==
    419                                     LocalData.LOCAL_CAMERA_PREVIEW;
    420                             if (!focused) {
    421                                 if (isCameraID) {
    422                                     mCurrentModule.onPreviewFocusChanged(false);
    423                                     CameraActivity.this.setSystemBarsVisibility(true);
    424                                 }
    425                                 hidePanoStitchingProgress();
    426                             } else {
    427                                 if (isCameraID) {
    428                                     // Don't show the action bar in Camera
    429                                     // preview.
    430                                     CameraActivity.this.setSystemBarsVisibility(false);
    431 
    432                                     if (mPendingDeletion) {
    433                                         performDeletion();
    434                                     }
    435                                 } else {
    436                                     updateActionBarMenu(dataID);
    437                                 }
    438 
    439                                 Uri contentUri = currentData.getContentUri();
    440                                 if (contentUri == null) {
    441                                     hidePanoStitchingProgress();
    442                                     return;
    443                                 }
    444                                 int panoStitchingProgress = mPanoramaManager.getTaskProgress(
    445                                         contentUri);
    446                                 if (panoStitchingProgress < 0) {
    447                                     hidePanoStitchingProgress();
    448                                     return;
    449                                 }
    450                                 showPanoStitchingProgress();
    451                                 updateStitchingProgress(panoStitchingProgress);
    452                             }
    453                         }
    454                     });
    455                 }
    456 
    457                 @Override
    458                 public void onToggleSystemDecorsVisibility(int dataID) {
    459                     // If action bar is showing, hide it immediately, otherwise
    460                     // show action bar and hide it later
    461                     if (mActionBar.isShowing()) {
    462                         CameraActivity.this.setSystemBarsVisibility(false);
    463                     } else {
    464                         // Don't show the action bar if that is the camera preview.
    465                         boolean isCameraID = isCameraPreview(dataID);
    466                         if (!isCameraID) {
    467                             CameraActivity.this.setSystemBarsVisibility(true, true);
    468                         }
    469                     }
    470                 }
    471 
    472                 @Override
    473                 public void setSystemDecorsVisibility(boolean visible) {
    474                     CameraActivity.this.setSystemBarsVisibility(visible);
    475                 }
    476             };
    477 
    478     public void gotoGallery() {
    479         UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_FILMSTRIP,
    480                 "thumbnailTap");
    481 
    482         mFilmStripView.getController().goToNextItem();
    483     }
    484 
    485     /**
    486      * If {@param visible} is false, this hides the action bar and switches the system UI
    487      * to lights-out mode.
    488      */
    489     // TODO: This should not be called outside of the activity.
    490     public void setSystemBarsVisibility(boolean visible) {
    491         setSystemBarsVisibility(visible, false);
    492     }
    493 
    494     /**
    495      * If {@param visible} is false, this hides the action bar and switches the
    496      * system UI to lights-out mode. If {@param hideLater} is true, a delayed message
    497      * will be sent after a timeout to hide the action bar.
    498      */
    499     private void setSystemBarsVisibility(boolean visible, boolean hideLater) {
    500         mMainHandler.removeMessages(HIDE_ACTION_BAR);
    501 
    502         int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
    503         int newSystemUIVisibility = DEFAULT_SYSTEM_UI_VISIBILITY |
    504                 (visible ? View.SYSTEM_UI_FLAG_VISIBLE :
    505                         View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN);
    506         if (newSystemUIVisibility != currentSystemUIVisibility) {
    507             mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
    508         }
    509 
    510         boolean currentActionBarVisibility = mActionBar.isShowing();
    511         if (visible != currentActionBarVisibility) {
    512             if (visible) {
    513                 mActionBar.show();
    514             } else {
    515                 mActionBar.hide();
    516             }
    517             if (mOnActionBarVisibilityListener != null) {
    518                 mOnActionBarVisibilityListener.onActionBarVisibilityChanged(visible);
    519             }
    520         }
    521 
    522         // Now delay hiding the bars
    523         if (visible && hideLater) {
    524             mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS);
    525         }
    526     }
    527 
    528     private void hidePanoStitchingProgress() {
    529         mPanoStitchingPanel.setVisibility(View.GONE);
    530     }
    531 
    532     private void showPanoStitchingProgress() {
    533         mPanoStitchingPanel.setVisibility(View.VISIBLE);
    534     }
    535 
    536     private void updateStitchingProgress(int progress) {
    537         mBottomProgress.setProgress(progress);
    538     }
    539 
    540     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    541     private void setupNfcBeamPush() {
    542         NfcAdapter adapter = NfcAdapter.getDefaultAdapter(CameraActivity.this);
    543         if (adapter == null) {
    544             return;
    545         }
    546 
    547         if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
    548             // Disable beaming
    549             adapter.setNdefPushMessage(null, CameraActivity.this);
    550             return;
    551         }
    552 
    553         adapter.setBeamPushUris(null, CameraActivity.this);
    554         adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
    555             @Override
    556             public Uri[] createBeamUris(NfcEvent event) {
    557                 return mNfcPushUris;
    558             }
    559         }, CameraActivity.this);
    560     }
    561 
    562     private void setNfcBeamPushUri(Uri uri) {
    563         mNfcPushUris[0] = uri;
    564     }
    565 
    566     private void setStandardShareIntent(Uri contentUri, String mimeType) {
    567         mStandardShareIntent = getShareIntentFromType(mimeType);
    568         if (mStandardShareIntent != null) {
    569             mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
    570             mStandardShareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    571             if (mStandardShareActionProvider != null) {
    572                 mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
    573             }
    574         }
    575     }
    576 
    577     /**
    578      * Get the share intent according to the mimeType
    579      *
    580      * @param mimeType The mimeType of current data.
    581      * @return the video/image's ShareIntent or null if mimeType is invalid.
    582      */
    583     private Intent getShareIntentFromType(String mimeType) {
    584         // Lazily create the intent object.
    585         if (mimeType.startsWith("video/")) {
    586             if (mVideoShareIntent == null) {
    587                 mVideoShareIntent = new Intent(Intent.ACTION_SEND);
    588                 mVideoShareIntent.setType("video/*");
    589             }
    590             return mVideoShareIntent;
    591         } else if (mimeType.startsWith("image/")) {
    592             if (mImageShareIntent == null) {
    593                 mImageShareIntent = new Intent(Intent.ACTION_SEND);
    594                 mImageShareIntent.setType("image/*");
    595             }
    596             return mImageShareIntent;
    597         }
    598         Log.w(TAG, "unsupported mimeType " + mimeType);
    599         return null;
    600     }
    601 
    602     private void setPanoramaShareIntent(Uri contentUri) {
    603         if (mPanoramaShareIntent == null) {
    604             mPanoramaShareIntent = new Intent(Intent.ACTION_SEND);
    605         }
    606         mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg");
    607         mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
    608         if (mPanoramaShareActionProvider != null) {
    609             mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
    610         }
    611     }
    612 
    613     @Override
    614     public void onMenuVisibilityChanged(boolean isVisible) {
    615         // If menu is showing, we need to make sure action bar does not go away.
    616         mMainHandler.removeMessages(HIDE_ACTION_BAR);
    617         if (!isVisible) {
    618             mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS);
    619         }
    620     }
    621 
    622     @Override
    623     public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
    624         int currentDataId = mFilmStripView.getCurrentId();
    625         if (currentDataId < 0) {
    626             return false;
    627         }
    628         UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_SHARE,
    629                 intent.getComponent().getPackageName(), 0,
    630                 UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
    631         return true;
    632     }
    633 
    634     /**
    635      * According to the data type, make the menu items for supported operations
    636      * visible.
    637      *
    638      * @param dataID the data ID of the current item.
    639      */
    640     private void updateActionBarMenu(int dataID) {
    641         LocalData currentData = mDataAdapter.getLocalData(dataID);
    642         if (currentData == null) {
    643             return;
    644         }
    645         int type = currentData.getLocalDataType();
    646 
    647         if (mActionBarMenu == null) {
    648             return;
    649         }
    650 
    651         int supported = 0;
    652 
    653         switch (type) {
    654             case LocalData.LOCAL_IMAGE:
    655                 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
    656                         | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
    657                         | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
    658                 break;
    659             case LocalData.LOCAL_VIDEO:
    660                 supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_SHARE;
    661                 break;
    662             case LocalData.LOCAL_PHOTO_SPHERE:
    663                 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
    664                         | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
    665                         | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
    666                 break;
    667             case LocalData.LOCAL_360_PHOTO_SPHERE:
    668                 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
    669                         | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
    670                         | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360
    671                         | SUPPORT_SHOW_ON_MAP;
    672                 break;
    673             default:
    674                 break;
    675         }
    676 
    677         // In secure camera mode, we only support delete operation.
    678         if (isSecureCamera()) {
    679             supported &= SUPPORT_DELETE;
    680         }
    681 
    682         setMenuItemVisible(mActionBarMenu, R.id.action_delete,
    683                 (supported & SUPPORT_DELETE) != 0);
    684         setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw,
    685                 (supported & SUPPORT_ROTATE) != 0);
    686         setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw,
    687                 (supported & SUPPORT_ROTATE) != 0);
    688         setMenuItemVisible(mActionBarMenu, R.id.action_details,
    689                 (supported & SUPPORT_INFO) != 0);
    690         setMenuItemVisible(mActionBarMenu, R.id.action_crop,
    691                 (supported & SUPPORT_CROP) != 0);
    692         setMenuItemVisible(mActionBarMenu, R.id.action_setas,
    693                 (supported & SUPPORT_SETAS) != 0);
    694         setMenuItemVisible(mActionBarMenu, R.id.action_edit,
    695                 (supported & SUPPORT_EDIT) != 0);
    696         setMenuItemVisible(mActionBarMenu, R.id.action_trim,
    697                 (supported & SUPPORT_TRIM) != 0);
    698 
    699         boolean standardShare = (supported & SUPPORT_SHARE) != 0;
    700         boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0;
    701         setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare);
    702         setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare);
    703 
    704         if (panoramaShare) {
    705             // For 360 PhotoSphere, relegate standard share to the overflow menu
    706             MenuItem item = mActionBarMenu.findItem(R.id.action_share);
    707             if (item != null) {
    708                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
    709                 item.setTitle(getResources().getString(R.string.share_as_photo));
    710             }
    711             // And, promote "share as panorama" to action bar
    712             item = mActionBarMenu.findItem(R.id.action_share_panorama);
    713             if (item != null) {
    714                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    715             }
    716             setPanoramaShareIntent(currentData.getContentUri());
    717         }
    718         if (standardShare) {
    719             if (!panoramaShare) {
    720                 MenuItem item = mActionBarMenu.findItem(R.id.action_share);
    721                 if (item != null) {
    722                     item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    723                     item.setTitle(getResources().getString(R.string.share));
    724                 }
    725             }
    726             setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType());
    727             setNfcBeamPushUri(currentData.getContentUri());
    728         }
    729 
    730         boolean itemHasLocation = currentData.getLatLong() != null;
    731         setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map,
    732                 itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0);
    733     }
    734 
    735     private void setMenuItemVisible(Menu menu, int itemId, boolean visible) {
    736         MenuItem item = menu.findItem(itemId);
    737         if (item != null)
    738             item.setVisible(visible);
    739     }
    740 
    741     private ImageTaskManager.TaskListener mPlaceholderListener =
    742             new ImageTaskManager.TaskListener() {
    743 
    744                 @Override
    745                 public void onTaskQueued(String filePath, final Uri imageUri) {
    746                     mMainHandler.post(new Runnable() {
    747                         @Override
    748                         public void run() {
    749                             notifyNewMedia(imageUri);
    750                             int dataID = mDataAdapter.findDataByContentUri(imageUri);
    751                             if (dataID != -1) {
    752                                 LocalData d = mDataAdapter.getLocalData(dataID);
    753                                 InProgressDataWrapper newData = new InProgressDataWrapper(d, true);
    754                                 mDataAdapter.updateData(dataID, newData);
    755                             }
    756                         }
    757                     });
    758                 }
    759 
    760                 @Override
    761                 public void onTaskDone(String filePath, final Uri imageUri) {
    762                     mMainHandler.post(new Runnable() {
    763                         @Override
    764                         public void run() {
    765                             mDataAdapter.refresh(getContentResolver(), imageUri);
    766                         }
    767                     });
    768                 }
    769 
    770                 @Override
    771                 public void onTaskProgress(String filePath, Uri imageUri, int progress) {
    772                     // Do nothing
    773                 }
    774     };
    775 
    776     private ImageTaskManager.TaskListener mStitchingListener =
    777             new ImageTaskManager.TaskListener() {
    778                 @Override
    779                 public void onTaskQueued(String filePath, final Uri imageUri) {
    780                     mMainHandler.post(new Runnable() {
    781                         @Override
    782                         public void run() {
    783                             notifyNewMedia(imageUri);
    784                             int dataID = mDataAdapter.findDataByContentUri(imageUri);
    785                             if (dataID != -1) {
    786                                 // Don't allow special UI actions (swipe to
    787                                 // delete, for example) on in-progress data.
    788                                 LocalData d = mDataAdapter.getLocalData(dataID);
    789                                 InProgressDataWrapper newData = new InProgressDataWrapper(d);
    790                                 mDataAdapter.updateData(dataID, newData);
    791                             }
    792                         }
    793                     });
    794                 }
    795 
    796                 @Override
    797                 public void onTaskDone(String filePath, final Uri imageUri) {
    798                     Log.v(TAG, "onTaskDone:" + filePath);
    799                     mMainHandler.post(new Runnable() {
    800                         @Override
    801                         public void run() {
    802                             int doneID = mDataAdapter.findDataByContentUri(imageUri);
    803                             int currentDataId = mFilmStripView.getCurrentId();
    804 
    805                             if (currentDataId == doneID) {
    806                                 hidePanoStitchingProgress();
    807                                 updateStitchingProgress(0);
    808                             }
    809 
    810                             mDataAdapter.refresh(getContentResolver(), imageUri);
    811                         }
    812                     });
    813                 }
    814 
    815                 @Override
    816                 public void onTaskProgress(
    817                         String filePath, final Uri imageUri, final int progress) {
    818                     mMainHandler.post(new Runnable() {
    819                         @Override
    820                         public void run() {
    821                             int currentDataId = mFilmStripView.getCurrentId();
    822                             if (currentDataId == -1) {
    823                                 return;
    824                             }
    825                             if (imageUri.equals(
    826                                     mDataAdapter.getLocalData(currentDataId).getContentUri())) {
    827                                 updateStitchingProgress(progress);
    828                             }
    829                         }
    830                     });
    831                 }
    832             };
    833 
    834     public MediaSaveService getMediaSaveService() {
    835         return mMediaSaveService;
    836     }
    837 
    838     public void notifyNewMedia(Uri uri) {
    839         ContentResolver cr = getContentResolver();
    840         String mimeType = cr.getType(uri);
    841         if (mimeType.startsWith("video/")) {
    842             sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
    843             mDataAdapter.addNewVideo(cr, uri);
    844         } else if (mimeType.startsWith("image/")) {
    845             CameraUtil.broadcastNewPicture(this, uri);
    846             mDataAdapter.addNewPhoto(cr, uri);
    847         } else if (mimeType.startsWith("application/stitching-preview")) {
    848             mDataAdapter.addNewPhoto(cr, uri);
    849         } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) {
    850             mDataAdapter.addNewPhoto(cr, uri);
    851         } else {
    852             android.util.Log.w(TAG, "Unknown new media with MIME type:"
    853                     + mimeType + ", uri:" + uri);
    854         }
    855     }
    856 
    857     private void removeData(int dataID) {
    858         mDataAdapter.removeData(CameraActivity.this, dataID);
    859         if (mDataAdapter.getTotalNumber() > 1) {
    860             showUndoDeletionBar();
    861         } else {
    862             // If camera preview is the only view left in filmstrip,
    863             // no need to show undo bar.
    864             mPendingDeletion = true;
    865             performDeletion();
    866         }
    867     }
    868 
    869     private void bindMediaSaveService() {
    870         Intent intent = new Intent(this, MediaSaveService.class);
    871         bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    872     }
    873 
    874     private void unbindMediaSaveService() {
    875         if (mConnection != null) {
    876             unbindService(mConnection);
    877         }
    878     }
    879 
    880     @Override
    881     public boolean onCreateOptionsMenu(Menu menu) {
    882         // Inflate the menu items for use in the action bar
    883         MenuInflater inflater = getMenuInflater();
    884         inflater.inflate(R.menu.operations, menu);
    885         mActionBarMenu = menu;
    886 
    887         // Configure the standard share action provider
    888         MenuItem item = menu.findItem(R.id.action_share);
    889         mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider();
    890         mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml");
    891         if (mStandardShareIntent != null) {
    892             mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
    893         }
    894 
    895         // Configure the panorama share action provider
    896         item = menu.findItem(R.id.action_share_panorama);
    897         mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider();
    898         mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml");
    899         if (mPanoramaShareIntent != null) {
    900             mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
    901         }
    902 
    903         mStandardShareActionProvider.setOnShareTargetSelectedListener(this);
    904         mPanoramaShareActionProvider.setOnShareTargetSelectedListener(this);
    905 
    906         return super.onCreateOptionsMenu(menu);
    907     }
    908 
    909     @Override
    910     public boolean onOptionsItemSelected(MenuItem item) {
    911         int currentDataId = mFilmStripView.getCurrentId();
    912         if (currentDataId < 0) {
    913             return false;
    914         }
    915         final LocalData localData = mDataAdapter.getLocalData(currentDataId);
    916 
    917         // Handle presses on the action bar items
    918         switch (item.getItemId()) {
    919             case android.R.id.home:
    920                 // ActionBar's Up/Home button was clicked
    921                 try {
    922                     startActivity(IntentHelper.getGalleryIntent(this));
    923                     return true;
    924                 } catch (ActivityNotFoundException e) {
    925                     Log.w(TAG, "Failed to launch gallery activity, closing");
    926                     finish();
    927                 }
    928             case R.id.action_delete:
    929                 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
    930                         UsageStatistics.ACTION_DELETE, null, 0,
    931                         UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
    932                 removeData(currentDataId);
    933                 return true;
    934             case R.id.action_edit:
    935                 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
    936                         UsageStatistics.ACTION_EDIT, null, 0,
    937                         UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
    938                 launchEditor(localData);
    939                 return true;
    940             case R.id.action_trim: {
    941                 // This is going to be handled by the Gallery app.
    942                 Intent intent = new Intent(ACTION_TRIM_VIDEO);
    943                 LocalData currentData = mDataAdapter.getLocalData(
    944                         mFilmStripView.getCurrentId());
    945                 intent.setData(currentData.getContentUri());
    946                 // We need the file path to wrap this into a RandomAccessFile.
    947                 intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath());
    948                 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
    949                 return true;
    950             }
    951             case R.id.action_rotate_ccw:
    952                 localData.rotate90Degrees(this, mDataAdapter, currentDataId, false);
    953                 return true;
    954             case R.id.action_rotate_cw:
    955                 localData.rotate90Degrees(this, mDataAdapter, currentDataId, true);
    956                 return true;
    957             case R.id.action_crop: {
    958                 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
    959                         UsageStatistics.ACTION_CROP, null, 0,
    960                         UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
    961                 Intent intent = new Intent(CropActivity.CROP_ACTION);
    962                 intent.setClass(this, CropActivity.class);
    963                 intent.setDataAndType(localData.getContentUri(), localData.getMimeType())
    964                         .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    965                 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
    966                 return true;
    967             }
    968             case R.id.action_setas: {
    969                 Intent intent = new Intent(Intent.ACTION_ATTACH_DATA)
    970                         .setDataAndType(localData.getContentUri(),
    971                                 localData.getMimeType())
    972                         .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    973                 intent.putExtra("mimeType", intent.getType());
    974                 startActivityForResult(Intent.createChooser(
    975                         intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW);
    976                 return true;
    977             }
    978             case R.id.action_details:
    979                 (new AsyncTask<Void, Void, MediaDetails>() {
    980                     @Override
    981                     protected MediaDetails doInBackground(Void... params) {
    982                         return localData.getMediaDetails(CameraActivity.this);
    983                     }
    984 
    985                     @Override
    986                     protected void onPostExecute(MediaDetails mediaDetails) {
    987                         if (mediaDetails != null) {
    988                             DetailsDialog.create(CameraActivity.this, mediaDetails).show();
    989                         }
    990                     }
    991                 }).execute();
    992                 return true;
    993             case R.id.action_show_on_map:
    994                 double[] latLong = localData.getLatLong();
    995                 if (latLong != null) {
    996                     CameraUtil.showOnMap(this, latLong);
    997                 }
    998                 return true;
    999             default:
   1000                 return super.onOptionsItemSelected(item);
   1001         }
   1002     }
   1003 
   1004     private boolean isCaptureIntent() {
   1005         if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
   1006                 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
   1007                 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
   1008             return true;
   1009         } else {
   1010             return false;
   1011         }
   1012     }
   1013 
   1014     @Override
   1015     public void onCreate(Bundle state) {
   1016         super.onCreate(state);
   1017         GcamHelper.init(getContentResolver());
   1018 
   1019         getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
   1020         setContentView(R.layout.camera_filmstrip);
   1021         mActionBar = getActionBar();
   1022         mActionBar.addOnMenuVisibilityListener(this);
   1023 
   1024         if (ApiHelper.HAS_ROTATION_ANIMATION) {
   1025             setRotationAnimation();
   1026         }
   1027 
   1028         mMainHandler = new MainHandler(getMainLooper());
   1029         // Check if this is in the secure camera mode.
   1030         Intent intent = getIntent();
   1031         String action = intent.getAction();
   1032         if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
   1033                 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
   1034             mSecureCamera = true;
   1035         } else {
   1036             mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
   1037         }
   1038 
   1039         if (mSecureCamera) {
   1040             // Change the window flags so that secure camera can show when locked
   1041             Window win = getWindow();
   1042             WindowManager.LayoutParams params = win.getAttributes();
   1043             params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
   1044             win.setAttributes(params);
   1045 
   1046             // Filter for screen off so that we can finish secure camera activity
   1047             // when screen is off.
   1048             IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
   1049             registerReceiver(mScreenOffReceiver, filter);
   1050             // TODO: This static screen off event receiver is a workaround to the
   1051             // double onResume() invocation (onResume->onPause->onResume). We should
   1052             // find a better solution to this.
   1053             if (sScreenOffReceiver == null) {
   1054                 sScreenOffReceiver = new ScreenOffReceiver();
   1055                 registerReceiver(sScreenOffReceiver, filter);
   1056             }
   1057         }
   1058         mAboveFilmstripControlLayout =
   1059                 (FrameLayout) findViewById(R.id.camera_above_filmstrip_layout);
   1060         mAboveFilmstripControlLayout.setFitsSystemWindows(true);
   1061         // Hide action bar first since we are in full screen mode first, and
   1062         // switch the system UI to lights-out mode.
   1063         this.setSystemBarsVisibility(false);
   1064         mPanoramaManager = AppManagerFactory.getInstance(this)
   1065                 .getPanoramaStitchingManager();
   1066         mPlaceholderManager = AppManagerFactory.getInstance(this)
   1067                 .getGcamProcessingManager();
   1068         mPanoramaManager.addTaskListener(mStitchingListener);
   1069         mPlaceholderManager.addTaskListener(mPlaceholderListener);
   1070         LayoutInflater inflater = getLayoutInflater();
   1071         View rootLayout = inflater.inflate(R.layout.camera, null, false);
   1072         mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root);
   1073         mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
   1074         mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
   1075         mCameraPreviewData = new CameraPreviewData(rootLayout,
   1076                 FilmStripView.ImageData.SIZE_FULL,
   1077                 FilmStripView.ImageData.SIZE_FULL);
   1078         // Put a CameraPreviewData at the first position.
   1079         mWrappedDataAdapter = new FixedFirstDataAdapter(
   1080                 new CameraDataAdapter(new ColorDrawable(
   1081                         getResources().getColor(R.color.photo_placeholder))),
   1082                 mCameraPreviewData);
   1083         mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view);
   1084         mFilmStripView.setViewGap(
   1085                 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
   1086         mPanoramaViewHelper = new PanoramaViewHelper(this);
   1087         mPanoramaViewHelper.onCreate();
   1088         mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper);
   1089         // Set up the camera preview first so the preview shows up ASAP.
   1090         mFilmStripView.setListener(mFilmStripListener);
   1091 
   1092         int moduleIndex = -1;
   1093         if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
   1094                 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
   1095             moduleIndex = ModuleSwitcher.VIDEO_MODULE_INDEX;
   1096         } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
   1097                 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
   1098                         .getAction())) {
   1099             moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
   1100             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
   1101             if (prefs.getInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, -1)
   1102                         == ModuleSwitcher.GCAM_MODULE_INDEX && GcamHelper.hasGcamCapture()) {
   1103                 moduleIndex = ModuleSwitcher.GCAM_MODULE_INDEX;
   1104             }
   1105         } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
   1106                 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
   1107             moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
   1108         } else {
   1109             // If the activity has not been started using an explicit intent,
   1110             // read the module index from the last time the user changed modes
   1111             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
   1112             moduleIndex = prefs.getInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, -1);
   1113             if ((moduleIndex == ModuleSwitcher.GCAM_MODULE_INDEX &&
   1114                     !GcamHelper.hasGcamCapture()) || moduleIndex < 0) {
   1115                 moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
   1116             }
   1117         }
   1118 
   1119         mOrientationListener = new MyOrientationEventListener(this);
   1120         setModuleFromIndex(moduleIndex);
   1121         mCurrentModule.init(this, mCameraModuleRootView);
   1122 
   1123         if (!mSecureCamera) {
   1124             mDataAdapter = mWrappedDataAdapter;
   1125             mFilmStripView.setDataAdapter(mDataAdapter);
   1126             if (!isCaptureIntent()) {
   1127                 mDataAdapter.requestLoad(getContentResolver());
   1128             }
   1129         } else {
   1130             // Put a lock placeholder as the last image by setting its date to
   1131             // 0.
   1132             ImageView v = (ImageView) getLayoutInflater().inflate(
   1133                     R.layout.secure_album_placeholder, null);
   1134             v.setOnClickListener(new View.OnClickListener() {
   1135                 @Override
   1136                 public void onClick(View view) {
   1137                     try {
   1138                         UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
   1139                                 UsageStatistics.ACTION_GALLERY, null);
   1140                         startActivity(IntentHelper.getGalleryIntent(CameraActivity.this));
   1141                     } catch (ActivityNotFoundException e) {
   1142                         Log.w(TAG, "Failed to launch gallery activity, closing");
   1143                     }
   1144                     finish();
   1145                 }
   1146             });
   1147             mDataAdapter = new FixedLastDataAdapter(
   1148                     mWrappedDataAdapter,
   1149                     new SimpleViewData(
   1150                             v,
   1151                             v.getDrawable().getIntrinsicWidth(),
   1152                             v.getDrawable().getIntrinsicHeight(),
   1153                             0, 0));
   1154             // Flush out all the original data.
   1155             mDataAdapter.flush();
   1156             mFilmStripView.setDataAdapter(mDataAdapter);
   1157         }
   1158 
   1159         setupNfcBeamPush();
   1160 
   1161         mLocalImagesObserver = new LocalMediaObserver();
   1162         mLocalVideosObserver = new LocalMediaObserver();
   1163 
   1164         getContentResolver().registerContentObserver(
   1165                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
   1166                 mLocalImagesObserver);
   1167         getContentResolver().registerContentObserver(
   1168                 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
   1169                 mLocalVideosObserver);
   1170     }
   1171 
   1172     private void setRotationAnimation() {
   1173         int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
   1174         rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
   1175         Window win = getWindow();
   1176         WindowManager.LayoutParams winParams = win.getAttributes();
   1177         winParams.rotationAnimation = rotationAnimation;
   1178         win.setAttributes(winParams);
   1179     }
   1180 
   1181     @Override
   1182     public void onUserInteraction() {
   1183         super.onUserInteraction();
   1184         mCurrentModule.onUserInteraction();
   1185     }
   1186 
   1187     @Override
   1188     public boolean dispatchTouchEvent(MotionEvent ev) {
   1189         boolean result = super.dispatchTouchEvent(ev);
   1190         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
   1191             // Real deletion is postponed until the next user interaction after
   1192             // the gesture that triggers deletion. Until real deletion is performed,
   1193             // users can click the undo button to bring back the image that they
   1194             // chose to delete.
   1195             if (mPendingDeletion && !mIsUndoingDeletion) {
   1196                  performDeletion();
   1197             }
   1198         }
   1199         return result;
   1200     }
   1201 
   1202     @Override
   1203     public void onPause() {
   1204         // Delete photos that are pending deletion
   1205         performDeletion();
   1206         mOrientationListener.disable();
   1207         mCurrentModule.onPauseBeforeSuper();
   1208         super.onPause();
   1209         mCurrentModule.onPauseAfterSuper();
   1210 
   1211         mLocalImagesObserver.setActivityPaused(true);
   1212         mLocalVideosObserver.setActivityPaused(true);
   1213     }
   1214 
   1215     @Override
   1216     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   1217         if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
   1218             mResetToPreviewOnResume = false;
   1219         } else {
   1220             super.onActivityResult(requestCode, resultCode, data);
   1221         }
   1222     }
   1223 
   1224     @Override
   1225     public void onResume() {
   1226         // TODO: Handle this in OrientationManager.
   1227         // Auto-rotate off
   1228         if (Settings.System.getInt(getContentResolver(),
   1229                 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
   1230             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
   1231             mAutoRotateScreen = false;
   1232         } else {
   1233             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
   1234             mAutoRotateScreen = true;
   1235         }
   1236 
   1237         UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
   1238                 UsageStatistics.ACTION_FOREGROUNDED, this.getClass().getSimpleName());
   1239 
   1240         mOrientationListener.enable();
   1241         mCurrentModule.onResumeBeforeSuper();
   1242         super.onResume();
   1243         mCurrentModule.onResumeAfterSuper();
   1244 
   1245         setSwipingEnabled(true);
   1246 
   1247         if (mResetToPreviewOnResume) {
   1248             // Go to the preview on resume.
   1249             mFilmStripView.getController().goToFirstItem();
   1250         }
   1251         // Default is showing the preview, unless disabled by explicitly
   1252         // starting an activity we want to return from to the filmstrip rather
   1253         // than the preview.
   1254         mResetToPreviewOnResume = true;
   1255 
   1256         if (mLocalVideosObserver.isMediaDataChangedDuringPause()
   1257                 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
   1258             if (!mSecureCamera) {
   1259                 // If it's secure camera, requestLoad() should not be called
   1260                 // as it will load all the data.
   1261                 mDataAdapter.requestLoad(getContentResolver());
   1262             }
   1263         }
   1264         mLocalImagesObserver.setActivityPaused(false);
   1265         mLocalVideosObserver.setActivityPaused(false);
   1266     }
   1267 
   1268     @Override
   1269     public void onStart() {
   1270         super.onStart();
   1271         bindMediaSaveService();
   1272         mPanoramaViewHelper.onStart();
   1273     }
   1274 
   1275     @Override
   1276     protected void onStop() {
   1277         super.onStop();
   1278         mPanoramaViewHelper.onStop();
   1279         unbindMediaSaveService();
   1280     }
   1281 
   1282     @Override
   1283     public void onDestroy() {
   1284         if (mSecureCamera) {
   1285             unregisterReceiver(mScreenOffReceiver);
   1286         }
   1287         getContentResolver().unregisterContentObserver(mLocalImagesObserver);
   1288         getContentResolver().unregisterContentObserver(mLocalVideosObserver);
   1289 
   1290         super.onDestroy();
   1291     }
   1292 
   1293     @Override
   1294     public void onConfigurationChanged(Configuration config) {
   1295         super.onConfigurationChanged(config);
   1296         mCurrentModule.onConfigurationChanged(config);
   1297     }
   1298 
   1299     @Override
   1300     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1301         if (mFilmStripView.inCameraFullscreen()) {
   1302             if (mCurrentModule.onKeyDown(keyCode, event)) {
   1303                 return true;
   1304             }
   1305             // Prevent software keyboard or voice search from showing up.
   1306             if (keyCode == KeyEvent.KEYCODE_SEARCH
   1307                     || keyCode == KeyEvent.KEYCODE_MENU) {
   1308                 if (event.isLongPress()) {
   1309                     return true;
   1310                 }
   1311             }
   1312         }
   1313 
   1314         return super.onKeyDown(keyCode, event);
   1315     }
   1316 
   1317     @Override
   1318     public boolean onKeyUp(int keyCode, KeyEvent event) {
   1319         if (mFilmStripView.inCameraFullscreen() && mCurrentModule.onKeyUp(keyCode, event)) {
   1320             return true;
   1321         }
   1322         return super.onKeyUp(keyCode, event);
   1323     }
   1324 
   1325     @Override
   1326     public void onBackPressed() {
   1327         if (!mFilmStripView.inCameraFullscreen()) {
   1328             mFilmStripView.getController().goToFirstItem();
   1329         } else if (!mCurrentModule.onBackPressed()) {
   1330             super.onBackPressed();
   1331         }
   1332     }
   1333 
   1334     public boolean isAutoRotateScreen() {
   1335         return mAutoRotateScreen;
   1336     }
   1337 
   1338     protected void updateStorageSpace() {
   1339         mStorageSpaceBytes = Storage.getAvailableSpace();
   1340     }
   1341 
   1342     protected long getStorageSpaceBytes() {
   1343         return mStorageSpaceBytes;
   1344     }
   1345 
   1346     protected void updateStorageSpaceAndHint() {
   1347         updateStorageSpace();
   1348         updateStorageHint(mStorageSpaceBytes);
   1349     }
   1350 
   1351     protected void updateStorageHint(long storageSpace) {
   1352         String message = null;
   1353         if (storageSpace == Storage.UNAVAILABLE) {
   1354             message = getString(R.string.no_storage);
   1355         } else if (storageSpace == Storage.PREPARING) {
   1356             message = getString(R.string.preparing_sd);
   1357         } else if (storageSpace == Storage.UNKNOWN_SIZE) {
   1358             message = getString(R.string.access_sd_fail);
   1359         } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
   1360             message = getString(R.string.spaceIsLow_content);
   1361         }
   1362 
   1363         if (message != null) {
   1364             if (mStorageHint == null) {
   1365                 mStorageHint = OnScreenHint.makeText(this, message);
   1366             } else {
   1367                 mStorageHint.setText(message);
   1368             }
   1369             mStorageHint.show();
   1370         } else if (mStorageHint != null) {
   1371             mStorageHint.cancel();
   1372             mStorageHint = null;
   1373         }
   1374     }
   1375 
   1376     protected void setResultEx(int resultCode) {
   1377         mResultCodeForTesting = resultCode;
   1378         setResult(resultCode);
   1379     }
   1380 
   1381     protected void setResultEx(int resultCode, Intent data) {
   1382         mResultCodeForTesting = resultCode;
   1383         mResultDataForTesting = data;
   1384         setResult(resultCode, data);
   1385     }
   1386 
   1387     public int getResultCode() {
   1388         return mResultCodeForTesting;
   1389     }
   1390 
   1391     public Intent getResultData() {
   1392         return mResultDataForTesting;
   1393     }
   1394 
   1395     public boolean isSecureCamera() {
   1396         return mSecureCamera;
   1397     }
   1398 
   1399     @Override
   1400     public void onModuleSelected(int moduleIndex) {
   1401         if (mCurrentModuleIndex == moduleIndex) {
   1402             return;
   1403         }
   1404 
   1405         CameraHolder.instance().keep();
   1406         closeModule(mCurrentModule);
   1407         setModuleFromIndex(moduleIndex);
   1408 
   1409         openModule(mCurrentModule);
   1410         mCurrentModule.onOrientationChanged(mLastRawOrientation);
   1411         if (mMediaSaveService != null) {
   1412             mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
   1413         }
   1414 
   1415         // Store the module index so we can use it the next time the Camera
   1416         // starts up.
   1417         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
   1418         prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, moduleIndex).apply();
   1419     }
   1420 
   1421     /**
   1422      * Sets the mCurrentModuleIndex, creates a new module instance for the given
   1423      * index an sets it as mCurrentModule.
   1424      */
   1425     private void setModuleFromIndex(int moduleIndex) {
   1426         mCurrentModuleIndex = moduleIndex;
   1427         switch (moduleIndex) {
   1428             case ModuleSwitcher.VIDEO_MODULE_INDEX:
   1429                 mCurrentModule = new VideoModule();
   1430                 break;
   1431 
   1432             case ModuleSwitcher.PHOTO_MODULE_INDEX:
   1433                 mCurrentModule = new PhotoModule();
   1434                 break;
   1435 
   1436             case ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX:
   1437                 mCurrentModule = new WideAnglePanoramaModule();
   1438                 break;
   1439 
   1440             case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX:
   1441                 mCurrentModule = PhotoSphereHelper.createPanoramaModule();
   1442                 break;
   1443             case ModuleSwitcher.GCAM_MODULE_INDEX:
   1444                 // Force immediate release of Camera instance
   1445                 CameraHolder.instance().strongRelease();
   1446                 mCurrentModule = GcamHelper.createGcamModule();
   1447                 break;
   1448             default:
   1449                 // Fall back to photo mode.
   1450                 mCurrentModule = new PhotoModule();
   1451                 mCurrentModuleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
   1452                 break;
   1453         }
   1454     }
   1455 
   1456     /**
   1457      * Launches an ACTION_EDIT intent for the given local data item.
   1458      */
   1459     public void launchEditor(LocalData data) {
   1460         Intent intent = new Intent(Intent.ACTION_EDIT)
   1461                 .setDataAndType(data.getContentUri(), data.getMimeType())
   1462                 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
   1463         try {
   1464             startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
   1465         } catch (ActivityNotFoundException e) {
   1466             startActivityForResult(Intent.createChooser(intent, null),
   1467                     REQ_CODE_DONT_SWITCH_TO_PREVIEW);
   1468         }
   1469     }
   1470 
   1471     /**
   1472      * Launch the tiny planet editor.
   1473      *
   1474      * @param data the data must be a 360 degree stereographically mapped
   1475      *            panoramic image. It will not be modified, instead a new item
   1476      *            with the result will be added to the filmstrip.
   1477      */
   1478     public void launchTinyPlanetEditor(LocalData data) {
   1479         TinyPlanetFragment fragment = new TinyPlanetFragment();
   1480         Bundle bundle = new Bundle();
   1481         bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
   1482         bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
   1483         fragment.setArguments(bundle);
   1484         fragment.show(getFragmentManager(), "tiny_planet");
   1485     }
   1486 
   1487     private void openModule(CameraModule module) {
   1488         module.init(this, mCameraModuleRootView);
   1489         module.onResumeBeforeSuper();
   1490         module.onResumeAfterSuper();
   1491     }
   1492 
   1493     private void closeModule(CameraModule module) {
   1494         module.onPauseBeforeSuper();
   1495         module.onPauseAfterSuper();
   1496         ((ViewGroup) mCameraModuleRootView).removeAllViews();
   1497     }
   1498 
   1499     private void performDeletion() {
   1500         if (!mPendingDeletion) {
   1501             return;
   1502         }
   1503         hideUndoDeletionBar(false);
   1504         mDataAdapter.executeDeletion(CameraActivity.this);
   1505 
   1506         int currentId = mFilmStripView.getCurrentId();
   1507         updateActionBarMenu(currentId);
   1508         mFilmStripListener.onCurrentDataCentered(currentId);
   1509     }
   1510 
   1511     public void showUndoDeletionBar() {
   1512         if (mPendingDeletion) {
   1513             performDeletion();
   1514         }
   1515         Log.v(TAG, "showing undo bar");
   1516         mPendingDeletion = true;
   1517         if (mUndoDeletionBar == null) {
   1518             ViewGroup v = (ViewGroup) getLayoutInflater().inflate(
   1519                     R.layout.undo_bar, mAboveFilmstripControlLayout, true);
   1520             mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
   1521             View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
   1522             button.setOnClickListener(new View.OnClickListener() {
   1523                 @Override
   1524                 public void onClick(View view) {
   1525                     mDataAdapter.undoDataRemoval();
   1526                     hideUndoDeletionBar(true);
   1527                 }
   1528             });
   1529             // Setting undo bar clickable to avoid touch events going through
   1530             // the bar to the buttons (eg. edit button, etc) underneath the bar.
   1531             mUndoDeletionBar.setClickable(true);
   1532             // When there is user interaction going on with the undo button, we
   1533             // do not want to hide the undo bar.
   1534             button.setOnTouchListener(new View.OnTouchListener() {
   1535                 @Override
   1536                 public boolean onTouch(View v, MotionEvent event) {
   1537                     if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
   1538                         mIsUndoingDeletion = true;
   1539                     } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
   1540                         mIsUndoingDeletion =false;
   1541                     }
   1542                     return false;
   1543                 }
   1544             });
   1545         }
   1546         mUndoDeletionBar.setAlpha(0f);
   1547         mUndoDeletionBar.setVisibility(View.VISIBLE);
   1548         mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
   1549     }
   1550 
   1551     private void hideUndoDeletionBar(boolean withAnimation) {
   1552         Log.v(TAG, "Hiding undo deletion bar");
   1553         mPendingDeletion = false;
   1554         if (mUndoDeletionBar != null) {
   1555             if (withAnimation) {
   1556                 mUndoDeletionBar.animate()
   1557                         .setDuration(200)
   1558                         .alpha(0f)
   1559                         .setListener(new Animator.AnimatorListener() {
   1560                             @Override
   1561                             public void onAnimationStart(Animator animation) {
   1562                                 // Do nothing.
   1563                             }
   1564 
   1565                             @Override
   1566                             public void onAnimationEnd(Animator animation) {
   1567                                 mUndoDeletionBar.setVisibility(View.GONE);
   1568                             }
   1569 
   1570                             @Override
   1571                             public void onAnimationCancel(Animator animation) {
   1572                                 // Do nothing.
   1573                             }
   1574 
   1575                             @Override
   1576                             public void onAnimationRepeat(Animator animation) {
   1577                                 // Do nothing.
   1578                             }
   1579                         })
   1580                         .start();
   1581             } else {
   1582                 mUndoDeletionBar.setVisibility(View.GONE);
   1583             }
   1584         }
   1585     }
   1586 
   1587     @Override
   1588     public void onShowSwitcherPopup() {
   1589         mCurrentModule.onShowSwitcherPopup();
   1590     }
   1591 
   1592     /**
   1593      * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
   1594      * capture intent.
   1595      *
   1596      * @param enable {@code true} to enable swipe.
   1597      */
   1598     public void setSwipingEnabled(boolean enable) {
   1599         if (isCaptureIntent()) {
   1600             mCameraPreviewData.lockPreview(true);
   1601         } else {
   1602             mCameraPreviewData.lockPreview(!enable);
   1603         }
   1604     }
   1605 
   1606 
   1607     /**
   1608      * Check whether camera controls are visible.
   1609      *
   1610      * @return whether controls are visible.
   1611      */
   1612     private boolean arePreviewControlsVisible() {
   1613         return mCurrentModule.arePreviewControlsVisible();
   1614     }
   1615 
   1616     /**
   1617      * Show or hide the {@link CameraControls} using the current module's
   1618      * implementation of {@link #onPreviewFocusChanged}.
   1619      *
   1620      * @param showControls whether to show camera controls.
   1621      */
   1622     private void setPreviewControlsVisibility(boolean showControls) {
   1623         mCurrentModule.onPreviewFocusChanged(showControls);
   1624     }
   1625 
   1626     // Accessor methods for getting latency times used in performance testing
   1627     public long getAutoFocusTime() {
   1628         return (mCurrentModule instanceof PhotoModule) ?
   1629                 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
   1630     }
   1631 
   1632     public long getShutterLag() {
   1633         return (mCurrentModule instanceof PhotoModule) ?
   1634                 ((PhotoModule) mCurrentModule).mShutterLag : -1;
   1635     }
   1636 
   1637     public long getShutterToPictureDisplayedTime() {
   1638         return (mCurrentModule instanceof PhotoModule) ?
   1639                 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
   1640     }
   1641 
   1642     public long getPictureDisplayedToJpegCallbackTime() {
   1643         return (mCurrentModule instanceof PhotoModule) ?
   1644                 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
   1645     }
   1646 
   1647     public long getJpegCallbackFinishTime() {
   1648         return (mCurrentModule instanceof PhotoModule) ?
   1649                 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
   1650     }
   1651 
   1652     public long getCaptureStartTime() {
   1653         return (mCurrentModule instanceof PhotoModule) ?
   1654                 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
   1655     }
   1656 
   1657     public boolean isRecording() {
   1658         return (mCurrentModule instanceof VideoModule) ?
   1659                 ((VideoModule) mCurrentModule).isRecording() : false;
   1660     }
   1661 
   1662     public CameraOpenErrorCallback getCameraOpenErrorCallback() {
   1663         return mCameraOpenErrorCallback;
   1664     }
   1665 
   1666     // For debugging purposes only.
   1667     public CameraModule getCurrentModule() {
   1668         return mCurrentModule;
   1669     }
   1670 }
   1671