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