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