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