• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
MyOrientationEventListener(Context context)197         public MyOrientationEventListener(Context context) {
198             super(context);
199         }
200 
201         @Override
onOrientationChanged(int orientation)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
onReceive(Context context, Intent intent)273         public void onReceive(Context context, Intent intent) {
274             sFirstStartAfterScreenOn = true;
275         }
276     }
277 
278     private class MainHandler extends Handler {
MainHandler(Looper looper)279         public MainHandler(Looper looper) {
280             super(looper);
281         }
282 
283         @Override
handleMessage(Message msg)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 {
onActionBarVisibilityChanged(boolean isVisible)293         public void onActionBarVisibilityChanged(boolean isVisible);
294     }
295 
setOnActionBarVisibilityListener(OnActionBarVisibilityListener listener)296     public void setOnActionBarVisibilityListener(OnActionBarVisibilityListener listener) {
297         mOnActionBarVisibilityListener = listener;
298     }
299 
isFirstStartAfterScreenOn()300     public static boolean isFirstStartAfterScreenOn() {
301         return sFirstStartAfterScreenOn;
302     }
303 
resetFirstStartAfterScreenOn()304     public static void resetFirstStartAfterScreenOn() {
305         sFirstStartAfterScreenOn = false;
306     }
307 
fileNameFromDataID(int dataID)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 
gotoGallery()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.
setSystemBarsVisibility(boolean visible)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      */
setSystemBarsVisibility(boolean visible, boolean hideLater)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 
hidePanoStitchingProgress()528     private void hidePanoStitchingProgress() {
529         mPanoStitchingPanel.setVisibility(View.GONE);
530     }
531 
showPanoStitchingProgress()532     private void showPanoStitchingProgress() {
533         mPanoStitchingPanel.setVisibility(View.VISIBLE);
534     }
535 
updateStitchingProgress(int progress)536     private void updateStitchingProgress(int progress) {
537         mBottomProgress.setProgress(progress);
538     }
539 
540     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
setupNfcBeamPush()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 
setNfcBeamPushUri(Uri uri)562     private void setNfcBeamPushUri(Uri uri) {
563         mNfcPushUris[0] = uri;
564     }
565 
setStandardShareIntent(Uri contentUri, String mimeType)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      */
getShareIntentFromType(String mimeType)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 
setPanoramaShareIntent(Uri contentUri)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
onMenuVisibilityChanged(boolean isVisible)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
onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent)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      */
updateActionBarMenu(int dataID)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 
setMenuItemVisible(Menu menu, int itemId, boolean visible)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 
getMediaSaveService()834     public MediaSaveService getMediaSaveService() {
835         return mMediaSaveService;
836     }
837 
notifyNewMedia(Uri uri)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 
removeData(int dataID)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 
bindMediaSaveService()869     private void bindMediaSaveService() {
870         Intent intent = new Intent(this, MediaSaveService.class);
871         bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
872     }
873 
unbindMediaSaveService()874     private void unbindMediaSaveService() {
875         if (mConnection != null) {
876             unbindService(mConnection);
877         }
878     }
879 
880     @Override
onCreateOptionsMenu(Menu menu)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
onOptionsItemSelected(MenuItem item)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 
isCaptureIntent()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
onCreate(Bundle state)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 
setRotationAnimation()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
onUserInteraction()1182     public void onUserInteraction() {
1183         super.onUserInteraction();
1184         mCurrentModule.onUserInteraction();
1185     }
1186 
1187     @Override
dispatchTouchEvent(MotionEvent ev)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
onPause()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
onActivityResult(int requestCode, int resultCode, Intent data)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
onResume()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
onStart()1269     public void onStart() {
1270         super.onStart();
1271         bindMediaSaveService();
1272         mPanoramaViewHelper.onStart();
1273     }
1274 
1275     @Override
onStop()1276     protected void onStop() {
1277         super.onStop();
1278         mPanoramaViewHelper.onStop();
1279         unbindMediaSaveService();
1280     }
1281 
1282     @Override
onDestroy()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
onConfigurationChanged(Configuration config)1294     public void onConfigurationChanged(Configuration config) {
1295         super.onConfigurationChanged(config);
1296         mCurrentModule.onConfigurationChanged(config);
1297     }
1298 
1299     @Override
onKeyDown(int keyCode, KeyEvent event)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
onKeyUp(int keyCode, KeyEvent event)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
onBackPressed()1326     public void onBackPressed() {
1327         if (!mFilmStripView.inCameraFullscreen()) {
1328             mFilmStripView.getController().goToFirstItem();
1329         } else if (!mCurrentModule.onBackPressed()) {
1330             super.onBackPressed();
1331         }
1332     }
1333 
isAutoRotateScreen()1334     public boolean isAutoRotateScreen() {
1335         return mAutoRotateScreen;
1336     }
1337 
updateStorageSpace()1338     protected void updateStorageSpace() {
1339         mStorageSpaceBytes = Storage.getAvailableSpace();
1340     }
1341 
getStorageSpaceBytes()1342     protected long getStorageSpaceBytes() {
1343         return mStorageSpaceBytes;
1344     }
1345 
updateStorageSpaceAndHint()1346     protected void updateStorageSpaceAndHint() {
1347         updateStorageSpace();
1348         updateStorageHint(mStorageSpaceBytes);
1349     }
1350 
updateStorageHint(long storageSpace)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 
setResultEx(int resultCode)1376     protected void setResultEx(int resultCode) {
1377         mResultCodeForTesting = resultCode;
1378         setResult(resultCode);
1379     }
1380 
setResultEx(int resultCode, Intent data)1381     protected void setResultEx(int resultCode, Intent data) {
1382         mResultCodeForTesting = resultCode;
1383         mResultDataForTesting = data;
1384         setResult(resultCode, data);
1385     }
1386 
getResultCode()1387     public int getResultCode() {
1388         return mResultCodeForTesting;
1389     }
1390 
getResultData()1391     public Intent getResultData() {
1392         return mResultDataForTesting;
1393     }
1394 
isSecureCamera()1395     public boolean isSecureCamera() {
1396         return mSecureCamera;
1397     }
1398 
1399     @Override
onModuleSelected(int moduleIndex)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      */
setModuleFromIndex(int moduleIndex)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      */
launchEditor(LocalData data)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      */
launchTinyPlanetEditor(LocalData data)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 
openModule(CameraModule module)1487     private void openModule(CameraModule module) {
1488         module.init(this, mCameraModuleRootView);
1489         module.onResumeBeforeSuper();
1490         module.onResumeAfterSuper();
1491     }
1492 
closeModule(CameraModule module)1493     private void closeModule(CameraModule module) {
1494         module.onPauseBeforeSuper();
1495         module.onPauseAfterSuper();
1496         ((ViewGroup) mCameraModuleRootView).removeAllViews();
1497     }
1498 
performDeletion()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 
showUndoDeletionBar()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 
hideUndoDeletionBar(boolean withAnimation)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
onShowSwitcherPopup()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      */
setSwipingEnabled(boolean enable)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      */
arePreviewControlsVisible()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      */
setPreviewControlsVisibility(boolean showControls)1622     private void setPreviewControlsVisibility(boolean showControls) {
1623         mCurrentModule.onPreviewFocusChanged(showControls);
1624     }
1625 
1626     // Accessor methods for getting latency times used in performance testing
getAutoFocusTime()1627     public long getAutoFocusTime() {
1628         return (mCurrentModule instanceof PhotoModule) ?
1629                 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1630     }
1631 
getShutterLag()1632     public long getShutterLag() {
1633         return (mCurrentModule instanceof PhotoModule) ?
1634                 ((PhotoModule) mCurrentModule).mShutterLag : -1;
1635     }
1636 
getShutterToPictureDisplayedTime()1637     public long getShutterToPictureDisplayedTime() {
1638         return (mCurrentModule instanceof PhotoModule) ?
1639                 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1640     }
1641 
getPictureDisplayedToJpegCallbackTime()1642     public long getPictureDisplayedToJpegCallbackTime() {
1643         return (mCurrentModule instanceof PhotoModule) ?
1644                 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1645     }
1646 
getJpegCallbackFinishTime()1647     public long getJpegCallbackFinishTime() {
1648         return (mCurrentModule instanceof PhotoModule) ?
1649                 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1650     }
1651 
getCaptureStartTime()1652     public long getCaptureStartTime() {
1653         return (mCurrentModule instanceof PhotoModule) ?
1654                 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1655     }
1656 
isRecording()1657     public boolean isRecording() {
1658         return (mCurrentModule instanceof VideoModule) ?
1659                 ((VideoModule) mCurrentModule).isRecording() : false;
1660     }
1661 
getCameraOpenErrorCallback()1662     public CameraOpenErrorCallback getCameraOpenErrorCallback() {
1663         return mCameraOpenErrorCallback;
1664     }
1665 
1666     // For debugging purposes only.
getCurrentModule()1667     public CameraModule getCurrentModule() {
1668         return mCurrentModule;
1669     }
1670 }
1671