CameraActivity.java revision a5a08d7642a1fdf961b057cc90e76c4c93103c15
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.camera; 18 19import android.animation.Animator; 20import android.app.ActionBar; 21import android.app.Activity; 22import android.content.BroadcastReceiver; 23import android.content.ComponentName; 24import android.content.ContentResolver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.ServiceConnection; 29import android.content.SharedPreferences; 30import android.content.pm.ActivityInfo; 31import android.content.res.Configuration; 32import android.graphics.drawable.ColorDrawable; 33import android.net.Uri; 34import android.os.AsyncTask; 35import android.os.Bundle; 36import android.os.Handler; 37import android.os.IBinder; 38import android.preference.PreferenceManager; 39import android.provider.MediaStore; 40import android.provider.Settings; 41import android.util.Log; 42import android.view.KeyEvent; 43import android.view.LayoutInflater; 44import android.view.Menu; 45import android.view.MenuInflater; 46import android.view.MenuItem; 47import android.view.MotionEvent; 48import android.view.OrientationEventListener; 49import android.view.View; 50import android.view.ViewGroup; 51import android.view.Window; 52import android.view.WindowManager; 53import android.widget.FrameLayout; 54import android.widget.ImageView; 55import android.widget.ProgressBar; 56import android.widget.ShareActionProvider; 57 58import com.android.camera.app.AppManagerFactory; 59import com.android.camera.app.PanoramaStitchingManager; 60import com.android.camera.data.CameraDataAdapter; 61import com.android.camera.data.CameraPreviewData; 62import com.android.camera.data.FixedFirstDataAdapter; 63import com.android.camera.data.FixedLastDataAdapter; 64import com.android.camera.data.LocalData; 65import com.android.camera.data.LocalDataAdapter; 66import com.android.camera.data.MediaDetails; 67import com.android.camera.data.SimpleViewData; 68import com.android.camera.tinyplanet.TinyPlanetFragment; 69import com.android.camera.ui.ModuleSwitcher; 70import com.android.camera.ui.DetailsDialog; 71import com.android.camera.ui.FilmStripView; 72import com.android.camera.util.ApiHelper; 73import com.android.camera.util.CameraUtil; 74import com.android.camera.util.PhotoSphereHelper; 75import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; 76import com.android.camera2.R; 77 78public class CameraActivity extends Activity 79 implements ModuleSwitcher.ModuleSwitchListener { 80 81 private static final String TAG = "CAM_Activity"; 82 83 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 84 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 85 public static final String ACTION_IMAGE_CAPTURE_SECURE = 86 "android.media.action.IMAGE_CAPTURE_SECURE"; 87 public static final String ACTION_TRIM_VIDEO = 88 "com.android.camera.action.TRIM"; 89 public static final String MEDIA_ITEM_PATH = "media-item-path"; 90 91 private static final String PREF_STARTUP_MODULE_INDEX = "camera.startup_module"; 92 93 // The intent extra for camera from secure lock screen. True if the gallery 94 // should only show newly captured pictures. sSecureAlbumId does not 95 // increment. This is used when switching between camera, camcorder, and 96 // panorama. If the extra is not set, it is in the normal camera mode. 97 public static final String SECURE_CAMERA_EXTRA = "secure_camera"; 98 99 /** 100 * Request code from an activity we started that indicated that we do not 101 * want to reset the view to the preview in onResume. 102 */ 103 public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142; 104 105 /** Request code for external image editor activities. */ 106 private static final int REQ_CODE_EDIT = 1; 107 108 109 /** Whether onResume should reset the view to the preview. */ 110 private boolean mResetToPreviewOnResume = true; 111 112 // Supported operations at FilmStripView. Different data has different 113 // set of supported operations. 114 private static final int SUPPORT_DELETE = 1 << 0; 115 private static final int SUPPORT_ROTATE = 1 << 1; 116 private static final int SUPPORT_INFO = 1 << 2; 117 private static final int SUPPORT_CROP = 1 << 3; 118 private static final int SUPPORT_SETAS = 1 << 4; 119 private static final int SUPPORT_EDIT = 1 << 5; 120 private static final int SUPPORT_TRIM = 1 << 6; 121 private static final int SUPPORT_SHARE = 1 << 7; 122 private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8; 123 private static final int SUPPORT_SHOW_ON_MAP = 1 << 9; 124 private static final int SUPPORT_ALL = 0xffffffff; 125 126 /** This data adapter is used by FilmStripView. */ 127 private LocalDataAdapter mDataAdapter; 128 /** This data adapter represents the real local camera data. */ 129 private LocalDataAdapter mWrappedDataAdapter; 130 131 private PanoramaStitchingManager mPanoramaManager; 132 private int mCurrentModuleIndex; 133 private CameraModule mCurrentModule; 134 private FrameLayout mAboveFilmstripControlLayout; 135 private View mCameraModuleRootView; 136 private FilmStripView mFilmStripView; 137 private ProgressBar mBottomProgress; 138 private View mPanoStitchingPanel; 139 private int mResultCodeForTesting; 140 private Intent mResultDataForTesting; 141 private OnScreenHint mStorageHint; 142 private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD; 143 private boolean mAutoRotateScreen; 144 private boolean mSecureCamera; 145 // This is a hack to speed up the start of SecureCamera. 146 private static boolean sFirstStartAfterScreenOn = true; 147 private int mLastRawOrientation; 148 private MyOrientationEventListener mOrientationListener; 149 private Handler mMainHandler; 150 private PanoramaViewHelper mPanoramaViewHelper; 151 private CameraPreviewData mCameraPreviewData; 152 private ActionBar mActionBar; 153 private Menu mActionBarMenu; 154 private ViewGroup mUndoDeletionBar; 155 private boolean mIsUndoingDeletion = false; 156 157 private ShareActionProvider mStandardShareActionProvider; 158 private Intent mStandardShareIntent; 159 private ShareActionProvider mPanoramaShareActionProvider; 160 private Intent mPanoramaShareIntent; 161 162 private final int DEFAULT_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 163 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; 164 private boolean mPendingDeletion = false; 165 166 public void gotoGallery() { 167 mFilmStripView.getController().goToNextItem(); 168 } 169 170 private class MyOrientationEventListener 171 extends OrientationEventListener { 172 public MyOrientationEventListener(Context context) { 173 super(context); 174 } 175 176 @Override 177 public void onOrientationChanged(int orientation) { 178 // We keep the last known orientation. So if the user first orient 179 // the camera then point the camera to floor or sky, we still have 180 // the correct orientation. 181 if (orientation == ORIENTATION_UNKNOWN) { 182 return; 183 } 184 mLastRawOrientation = orientation; 185 mCurrentModule.onOrientationChanged(orientation); 186 } 187 } 188 189 private MediaSaveService mMediaSaveService; 190 private ServiceConnection mConnection = new ServiceConnection() { 191 @Override 192 public void onServiceConnected(ComponentName className, IBinder b) { 193 mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService(); 194 mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); 195 } 196 197 @Override 198 public void onServiceDisconnected(ComponentName className) { 199 if (mMediaSaveService != null) { 200 mMediaSaveService.setListener(null); 201 mMediaSaveService = null; 202 } 203 } 204 }; 205 206 // close activity when screen turns off 207 private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 208 @Override 209 public void onReceive(Context context, Intent intent) { 210 finish(); 211 } 212 }; 213 214 private static BroadcastReceiver sScreenOffReceiver; 215 216 private static class ScreenOffReceiver extends BroadcastReceiver { 217 @Override 218 public void onReceive(Context context, Intent intent) { 219 sFirstStartAfterScreenOn = true; 220 } 221 } 222 223 public static boolean isFirstStartAfterScreenOn() { 224 return sFirstStartAfterScreenOn; 225 } 226 227 public static void resetFirstStartAfterScreenOn() { 228 sFirstStartAfterScreenOn = false; 229 } 230 231 private FilmStripView.Listener mFilmStripListener = 232 new FilmStripView.Listener() { 233 @Override 234 public void onDataPromoted(int dataID) { 235 removeData(dataID); 236 } 237 238 @Override 239 public void onDataDemoted(int dataID) { 240 removeData(dataID); 241 } 242 243 @Override 244 public void onDataFullScreenChange(int dataID, boolean full) { 245 boolean isCameraID = isCameraPreview(dataID); 246 if (!isCameraID) { 247 setActionBarVisibilityAndLightsOut(full); 248 } 249 } 250 251 /** 252 * Check if the local data corresponding to dataID is the camera 253 * preview. 254 * 255 * @param dataID the ID of the local data 256 * @return true if the local data is not null and it is the 257 * camera preview. 258 */ 259 private boolean isCameraPreview(int dataID) { 260 LocalData localData = mDataAdapter.getLocalData(dataID); 261 if (localData == null) { 262 Log.w(TAG, "Current data ID not found."); 263 return false; 264 } 265 return localData.getLocalDataType() == LocalData.LOCAL_CAMERA_PREVIEW; 266 } 267 268 @Override 269 public void onCurrentDataChanged(final int dataID, final boolean current) { 270 runOnUiThread(new Runnable() { 271 @Override 272 public void run() { 273 LocalData currentData = mDataAdapter.getLocalData(dataID); 274 if (currentData == null) { 275 Log.w(TAG, "Current data ID not found."); 276 hidePanoStitchingProgress(); 277 return; 278 } 279 boolean isCameraID = currentData.getLocalDataType() == 280 LocalData.LOCAL_CAMERA_PREVIEW; 281 if (!current) { 282 if (isCameraID) { 283 mCurrentModule.onPreviewFocusChanged(false); 284 setActionBarVisibilityAndLightsOut(false); 285 } 286 hidePanoStitchingProgress(); 287 } else { 288 if (isCameraID) { 289 mCurrentModule.onPreviewFocusChanged(true); 290 // Don't show the action bar in Camera 291 // preview. 292 setActionBarVisibilityAndLightsOut(true); 293 if (mPendingDeletion) { 294 performDeletion(); 295 } 296 } else { 297 updateActionBarMenu(dataID); 298 } 299 300 Uri contentUri = currentData.getContentUri(); 301 if (contentUri == null) { 302 hidePanoStitchingProgress(); 303 return; 304 } 305 int panoStitchingProgress = mPanoramaManager.getTaskProgress( 306 contentUri); 307 if (panoStitchingProgress < 0) { 308 hidePanoStitchingProgress(); 309 return; 310 } 311 showPanoStitchingProgress(); 312 updateStitchingProgress(panoStitchingProgress); 313 } 314 } 315 }); 316 } 317 318 @Override 319 public boolean onToggleActionBarVisibility(int dataID) { 320 if (mActionBar.isShowing()) { 321 setActionBarVisibilityAndLightsOut(true); 322 } else { 323 // Don't show the action bar if that is the camera preview. 324 boolean isCameraID = isCameraPreview(dataID); 325 if (!isCameraID) { 326 setActionBarVisibilityAndLightsOut(false); 327 } 328 } 329 return mActionBar.isShowing(); 330 } 331 }; 332 333 /** 334 * If enabled, this hides the action bar and switches the system UI to 335 * lights-out mode. 336 */ 337 private void setActionBarVisibilityAndLightsOut(boolean enabled) { 338 if (enabled) { 339 mActionBar.hide(); 340 } else { 341 mActionBar.show(); 342 } 343 int visibility = DEFAULT_SYSTEM_UI_VISIBILITY | (enabled ? View.SYSTEM_UI_FLAG_LOW_PROFILE 344 : View.SYSTEM_UI_FLAG_VISIBLE); 345 mAboveFilmstripControlLayout 346 .setSystemUiVisibility(visibility); 347 } 348 349 private void hidePanoStitchingProgress() { 350 mPanoStitchingPanel.setVisibility(View.GONE); 351 } 352 353 private void showPanoStitchingProgress() { 354 mPanoStitchingPanel.setVisibility(View.VISIBLE); 355 } 356 357 private void updateStitchingProgress(int progress) { 358 mBottomProgress.setProgress(progress); 359 } 360 361 private void setStandardShareIntent(Uri contentUri, String mimeType) { 362 if (mStandardShareIntent == null) { 363 mStandardShareIntent = new Intent(Intent.ACTION_SEND); 364 } 365 mStandardShareIntent.setType(mimeType); 366 mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); 367 if (mStandardShareActionProvider != null) { 368 mStandardShareActionProvider.setShareIntent(mStandardShareIntent); 369 } 370 } 371 372 private void setPanoramaShareIntent(Uri contentUri) { 373 if (mPanoramaShareIntent == null) { 374 mPanoramaShareIntent = new Intent(Intent.ACTION_SEND); 375 } 376 mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg"); 377 mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); 378 if (mPanoramaShareActionProvider != null) { 379 mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent); 380 } 381 } 382 383 /** 384 * According to the data type, make the menu items for supported operations 385 * visible. 386 * 387 * @param dataID the data ID of the current item. 388 */ 389 private void updateActionBarMenu(int dataID) { 390 LocalData currentData = mDataAdapter.getLocalData(dataID); 391 int type = currentData.getLocalDataType(); 392 393 if (mActionBarMenu == null) { 394 return; 395 } 396 397 int supported = 0; 398 switch (type) { 399 case LocalData.LOCAL_IMAGE: 400 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 401 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 402 | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP; 403 break; 404 case LocalData.LOCAL_VIDEO: 405 supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM 406 | SUPPORT_SHARE; 407 break; 408 case LocalData.LOCAL_PHOTO_SPHERE: 409 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 410 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 411 | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP; 412 break; 413 case LocalData.LOCAL_360_PHOTO_SPHERE: 414 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 415 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 416 | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360 417 | SUPPORT_SHOW_ON_MAP; 418 break; 419 default: 420 break; 421 } 422 423 setMenuItemVisible(mActionBarMenu, R.id.action_delete, 424 (supported & SUPPORT_DELETE) != 0); 425 setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw, 426 (supported & SUPPORT_ROTATE) != 0); 427 setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw, 428 (supported & SUPPORT_ROTATE) != 0); 429 setMenuItemVisible(mActionBarMenu, R.id.action_details, 430 (supported & SUPPORT_INFO) != 0); 431 setMenuItemVisible(mActionBarMenu, R.id.action_crop, 432 (supported & SUPPORT_CROP) != 0); 433 setMenuItemVisible(mActionBarMenu, R.id.action_setas, 434 (supported & SUPPORT_SETAS) != 0); 435 setMenuItemVisible(mActionBarMenu, R.id.action_edit, 436 (supported & SUPPORT_EDIT) != 0); 437 setMenuItemVisible(mActionBarMenu, R.id.action_trim, 438 (supported & SUPPORT_TRIM) != 0); 439 440 boolean standardShare = (supported & SUPPORT_SHARE) != 0; 441 boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0; 442 setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare); 443 setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare); 444 445 if (panoramaShare) { 446 // For 360 PhotoSphere, relegate standard share to the overflow menu 447 MenuItem item = mActionBarMenu.findItem(R.id.action_share); 448 if (item != null) { 449 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 450 item.setTitle(getResources().getString(R.string.share_as_photo)); 451 } 452 // And, promote "share as panorama" to action bar 453 item = mActionBarMenu.findItem(R.id.action_share_panorama); 454 if (item != null) { 455 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 456 } 457 setPanoramaShareIntent(currentData.getContentUri()); 458 } 459 if (standardShare) { 460 if (!panoramaShare) { 461 MenuItem item = mActionBarMenu.findItem(R.id.action_share); 462 if (item != null) { 463 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 464 item.setTitle(getResources().getString(R.string.share)); 465 } 466 } 467 setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType()); 468 } 469 470 boolean itemHasLocation = currentData.getLatLong() != null; 471 setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map, 472 itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0); 473 } 474 475 private void setMenuItemVisible(Menu menu, int itemId, boolean visible) { 476 MenuItem item = menu.findItem(itemId); 477 if (item != null) 478 item.setVisible(visible); 479 } 480 481 private ImageTaskManager.TaskListener mStitchingListener = 482 new ImageTaskManager.TaskListener() { 483 @Override 484 public void onTaskQueued(String filePath, final Uri imageUri) { 485 mMainHandler.post(new Runnable() { 486 @Override 487 public void run() { 488 notifyNewMedia(imageUri); 489 } 490 }); 491 } 492 493 @Override 494 public void onTaskDone(String filePath, final Uri imageUri) { 495 Log.v(TAG, "onTaskDone:" + filePath); 496 mMainHandler.post(new Runnable() { 497 @Override 498 public void run() { 499 int doneID = mDataAdapter.findDataByContentUri(imageUri); 500 int currentDataId = mFilmStripView.getCurrentId(); 501 502 if (currentDataId == doneID) { 503 hidePanoStitchingProgress(); 504 updateStitchingProgress(0); 505 } 506 507 mDataAdapter.refresh(getContentResolver(), imageUri); 508 } 509 }); 510 } 511 512 @Override 513 public void onTaskProgress( 514 String filePath, final Uri imageUri, final int progress) { 515 mMainHandler.post(new Runnable() { 516 @Override 517 public void run() { 518 int currentDataId = mFilmStripView.getCurrentId(); 519 if (currentDataId == -1) { 520 return; 521 } 522 if (imageUri.equals( 523 mDataAdapter.getLocalData(currentDataId).getContentUri())) { 524 updateStitchingProgress(progress); 525 } 526 } 527 }); 528 } 529 }; 530 531 public MediaSaveService getMediaSaveService() { 532 return mMediaSaveService; 533 } 534 535 public void notifyNewMedia(Uri uri) { 536 ContentResolver cr = getContentResolver(); 537 String mimeType = cr.getType(uri); 538 if (mimeType.startsWith("video/")) { 539 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri)); 540 mDataAdapter.addNewVideo(cr, uri); 541 } else if (mimeType.startsWith("image/")) { 542 CameraUtil.broadcastNewPicture(this, uri); 543 mDataAdapter.addNewPhoto(cr, uri); 544 } else if (mimeType.startsWith("application/stitching-preview")) { 545 mDataAdapter.addNewPhoto(cr, uri); 546 } else { 547 android.util.Log.w(TAG, "Unknown new media with MIME type:" 548 + mimeType + ", uri:" + uri); 549 } 550 } 551 552 private void removeData(int dataID) { 553 mDataAdapter.removeData(CameraActivity.this, dataID); 554 if (mDataAdapter.getTotalNumber() > 1) { 555 showUndoDeletionBar(); 556 } else { 557 // If camera preview is the only view left in filmstrip, 558 // no need to show undo bar. 559 performDeletion(); 560 } 561 } 562 563 private void bindMediaSaveService() { 564 Intent intent = new Intent(this, MediaSaveService.class); 565 bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 566 } 567 568 private void unbindMediaSaveService() { 569 if (mConnection != null) { 570 unbindService(mConnection); 571 } 572 } 573 574 @Override 575 public boolean onCreateOptionsMenu(Menu menu) { 576 // Inflate the menu items for use in the action bar 577 MenuInflater inflater = getMenuInflater(); 578 inflater.inflate(R.menu.operations, menu); 579 mActionBarMenu = menu; 580 581 // Configure the standard share action provider 582 MenuItem item = menu.findItem(R.id.action_share); 583 mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider(); 584 mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml"); 585 if (mStandardShareIntent != null) { 586 mStandardShareActionProvider.setShareIntent(mStandardShareIntent); 587 } 588 589 // Configure the panorama share action provider 590 item = menu.findItem(R.id.action_share_panorama); 591 mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider(); 592 mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml"); 593 if (mPanoramaShareIntent != null) { 594 mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent); 595 } 596 597 return super.onCreateOptionsMenu(menu); 598 } 599 600 @Override 601 public boolean onOptionsItemSelected(MenuItem item) { 602 int currentDataId = mFilmStripView.getCurrentId(); 603 if (currentDataId < 0) { 604 return false; 605 } 606 final LocalData localData = mDataAdapter.getLocalData(currentDataId); 607 608 // Handle presses on the action bar items 609 switch (item.getItemId()) { 610 case android.R.id.home: 611 // ActionBar's Up/Home button was clicked 612 mFilmStripView.getController().goToFirstItem(); 613 return true; 614 case R.id.action_delete: 615 removeData(currentDataId); 616 return true; 617 case R.id.action_edit: 618 launchEditor(localData); 619 return true; 620 case R.id.action_trim: { 621 // This is going to be handled by the Gallery app. 622 Intent intent = new Intent(ACTION_TRIM_VIDEO); 623 LocalData currentData = mDataAdapter.getLocalData( 624 mFilmStripView.getCurrentId()); 625 intent.setData(currentData.getContentUri()); 626 // We need the file path to wrap this into a RandomAccessFile. 627 intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath()); 628 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW); 629 return true; 630 } 631 case R.id.action_rotate_ccw: 632 localData.rotate90Degrees(this, mDataAdapter, currentDataId, false); 633 return true; 634 case R.id.action_rotate_cw: 635 localData.rotate90Degrees(this, mDataAdapter, currentDataId, true); 636 return true; 637 case R.id.action_crop: 638 // TODO: add the functionality. 639 return true; 640 case R.id.action_setas: { 641 Intent intent = new Intent(Intent.ACTION_ATTACH_DATA) 642 .setDataAndType(localData.getContentUri(), 643 localData.getMimeType()) 644 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 645 intent.putExtra("mimeType", intent.getType()); 646 startActivityForResult(Intent.createChooser( 647 intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW); 648 return true; 649 } 650 case R.id.action_details: 651 (new AsyncTask<Void, Void, MediaDetails>() { 652 @Override 653 protected MediaDetails doInBackground(Void... params) { 654 return localData.getMediaDetails(CameraActivity.this); 655 } 656 657 @Override 658 protected void onPostExecute(MediaDetails mediaDetails) { 659 DetailsDialog.create(CameraActivity.this, mediaDetails).show(); 660 } 661 }).execute(); 662 return true; 663 case R.id.action_show_on_map: 664 double[] latLong = localData.getLatLong(); 665 if (latLong != null) { 666 CameraUtil.showOnMap(this, latLong); 667 } 668 return true; 669 default: 670 return super.onOptionsItemSelected(item); 671 } 672 } 673 674 private boolean isCaptureIntent() { 675 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction()) 676 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 677 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 678 return true; 679 } else { 680 return false; 681 } 682 } 683 684 @Override 685 public void onCreate(Bundle state) { 686 super.onCreate(state); 687 getWindow().requestFeature(Window.FEATURE_ACTION_BAR); 688 setContentView(R.layout.camera_filmstrip); 689 mActionBar = getActionBar(); 690 691 if (ApiHelper.HAS_ROTATION_ANIMATION) { 692 setRotationAnimation(); 693 } 694 // Check if this is in the secure camera mode. 695 Intent intent = getIntent(); 696 String action = intent.getAction(); 697 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action) 698 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 699 mSecureCamera = true; 700 } else { 701 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 702 } 703 704 if (mSecureCamera) { 705 // Change the window flags so that secure camera can show when locked 706 Window win = getWindow(); 707 WindowManager.LayoutParams params = win.getAttributes(); 708 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 709 win.setAttributes(params); 710 711 // Filter for screen off so that we can finish secure camera activity 712 // when screen is off. 713 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); 714 registerReceiver(mScreenOffReceiver, filter); 715 // TODO: This static screen off event receiver is a workaround to the 716 // double onResume() invocation (onResume->onPause->onResume). We should 717 // find a better solution to this. 718 if (sScreenOffReceiver == null) { 719 sScreenOffReceiver = new ScreenOffReceiver(); 720 registerReceiver(sScreenOffReceiver, filter); 721 } 722 } 723 mAboveFilmstripControlLayout = 724 (FrameLayout) findViewById(R.id.camera_above_filmstrip_layout); 725 mAboveFilmstripControlLayout.setFitsSystemWindows(true); 726 // Hide action bar first since we are in full screen mode first, and 727 // switch the system UI to lights-out mode. 728 setActionBarVisibilityAndLightsOut(true); 729 mPanoramaManager = AppManagerFactory.getInstance(this) 730 .getPanoramaStitchingManager(); 731 mPanoramaManager.addTaskListener(mStitchingListener); 732 LayoutInflater inflater = getLayoutInflater(); 733 View rootLayout = inflater.inflate(R.layout.camera, null, false); 734 mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root); 735 mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel); 736 mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar); 737 mCameraPreviewData = new CameraPreviewData(rootLayout, 738 FilmStripView.ImageData.SIZE_FULL, 739 FilmStripView.ImageData.SIZE_FULL); 740 // Put a CameraPreviewData at the first position. 741 mWrappedDataAdapter = new FixedFirstDataAdapter( 742 new CameraDataAdapter(new ColorDrawable( 743 getResources().getColor(R.color.photo_placeholder))), 744 mCameraPreviewData); 745 mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view); 746 mFilmStripView.setViewGap( 747 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); 748 mPanoramaViewHelper = new PanoramaViewHelper(this); 749 mPanoramaViewHelper.onCreate(); 750 mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper); 751 // Set up the camera preview first so the preview shows up ASAP. 752 mFilmStripView.setListener(mFilmStripListener); 753 754 int moduleIndex = -1; 755 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction()) 756 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) { 757 moduleIndex = ModuleSwitcher.VIDEO_MODULE_INDEX; 758 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction()) 759 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent() 760 .getAction()) 761 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 762 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 763 moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX; 764 } else { 765 // If the activity has not been started using an explicit intent, 766 // read the module index from the last time the user changed modes 767 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 768 moduleIndex = prefs.getInt(PREF_STARTUP_MODULE_INDEX, -1); 769 if (moduleIndex < 0) { 770 moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX; 771 } 772 } 773 774 mOrientationListener = new MyOrientationEventListener(this); 775 setModuleFromIndex(moduleIndex); 776 mCurrentModule.init(this, mCameraModuleRootView); 777 mMainHandler = new Handler(getMainLooper()); 778 779 if (!mSecureCamera) { 780 mDataAdapter = mWrappedDataAdapter; 781 mFilmStripView.setDataAdapter(mDataAdapter); 782 if (!isCaptureIntent()) { 783 mDataAdapter.requestLoad(getContentResolver()); 784 } 785 } else { 786 // Put a lock placeholder as the last image by setting its date to 787 // 0. 788 ImageView v = (ImageView) getLayoutInflater().inflate( 789 R.layout.secure_album_placeholder, null); 790 mDataAdapter = new FixedLastDataAdapter( 791 mWrappedDataAdapter, 792 new SimpleViewData( 793 v, 794 v.getDrawable().getIntrinsicWidth(), 795 v.getDrawable().getIntrinsicHeight(), 796 0, 0)); 797 // Flush out all the original data. 798 mDataAdapter.flush(); 799 mFilmStripView.setDataAdapter(mDataAdapter); 800 } 801 } 802 803 private void setRotationAnimation() { 804 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 805 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 806 Window win = getWindow(); 807 WindowManager.LayoutParams winParams = win.getAttributes(); 808 winParams.rotationAnimation = rotationAnimation; 809 win.setAttributes(winParams); 810 } 811 812 @Override 813 public void onUserInteraction() { 814 super.onUserInteraction(); 815 mCurrentModule.onUserInteraction(); 816 } 817 818 @Override 819 public boolean dispatchTouchEvent(MotionEvent ev) { 820 boolean result = super.dispatchTouchEvent(ev); 821 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 822 // Real deletion is postponed until the next user interaction after 823 // the gesture that triggers deletion. Until real deletion is performed, 824 // users can click the undo button to bring back the image that they 825 // chose to delete. 826 if (mPendingDeletion && !mIsUndoingDeletion) { 827 performDeletion(); 828 } 829 } 830 return result; 831 } 832 833 @Override 834 public void onPause() { 835 mOrientationListener.disable(); 836 mCurrentModule.onPauseBeforeSuper(); 837 super.onPause(); 838 mCurrentModule.onPauseAfterSuper(); 839 } 840 841 @Override 842 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 843 if (requestCode == REQ_CODE_EDIT && resultCode == RESULT_OK) { 844 Uri uri = data.getData(); 845 ContentResolver contentResolver = getContentResolver(); 846 if (uri == null) { 847 // If we don't have a particular uri returned, then we have 848 // to refresh all, it is not optimal, but works best so far. 849 // Also don't requestLoad() when in secure camera mode. 850 if (!mSecureCamera) { 851 mDataAdapter.requestLoad(contentResolver); 852 } 853 } else { 854 mDataAdapter.refresh(contentResolver, uri); 855 } 856 } 857 858 if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW | requestCode == REQ_CODE_EDIT) { 859 mResetToPreviewOnResume = false; 860 } else { 861 super.onActivityResult(requestCode, resultCode, data); 862 } 863 } 864 865 @Override 866 public void onResume() { 867 // TODO: Handle this in OrientationManager. 868 // Auto-rotate off 869 if (Settings.System.getInt(getContentResolver(), 870 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) { 871 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 872 mAutoRotateScreen = false; 873 } else { 874 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 875 mAutoRotateScreen = true; 876 } 877 mOrientationListener.enable(); 878 mCurrentModule.onResumeBeforeSuper(); 879 super.onResume(); 880 mCurrentModule.onResumeAfterSuper(); 881 882 setSwipingEnabled(true); 883 884 if (mResetToPreviewOnResume) { 885 // Go to the preview on resume. 886 mFilmStripView.getController().goToFirstItem(); 887 } 888 // Default is showing the preview, unless disabled by explicitly 889 // starting an activity we want to return from to the filmstrip rather 890 // than the preview. 891 mResetToPreviewOnResume = true; 892 } 893 894 @Override 895 public void onStart() { 896 super.onStart(); 897 bindMediaSaveService(); 898 mPanoramaViewHelper.onStart(); 899 } 900 901 @Override 902 protected void onStop() { 903 super.onStop(); 904 mPanoramaViewHelper.onStop(); 905 unbindMediaSaveService(); 906 } 907 908 @Override 909 public void onDestroy() { 910 if (mSecureCamera) { 911 unregisterReceiver(mScreenOffReceiver); 912 } 913 super.onDestroy(); 914 } 915 916 @Override 917 public void onConfigurationChanged(Configuration config) { 918 super.onConfigurationChanged(config); 919 mCurrentModule.onConfigurationChanged(config); 920 } 921 922 @Override 923 public boolean onKeyDown(int keyCode, KeyEvent event) { 924 if (mCurrentModule.onKeyDown(keyCode, event)) { 925 return true; 926 } 927 // Prevent software keyboard or voice search from showing up. 928 if (keyCode == KeyEvent.KEYCODE_SEARCH 929 || keyCode == KeyEvent.KEYCODE_MENU) { 930 if (event.isLongPress()) { 931 return true; 932 } 933 } 934 935 return super.onKeyDown(keyCode, event); 936 } 937 938 @Override 939 public boolean onKeyUp(int keyCode, KeyEvent event) { 940 if (mCurrentModule.onKeyUp(keyCode, event)) { 941 return true; 942 } 943 return super.onKeyUp(keyCode, event); 944 } 945 946 @Override 947 public void onBackPressed() { 948 if (!mFilmStripView.inCameraFullscreen()) { 949 mFilmStripView.getController().goToFirstItem(); 950 } else if (!mCurrentModule.onBackPressed()) { 951 super.onBackPressed(); 952 } 953 } 954 955 public boolean isAutoRotateScreen() { 956 return mAutoRotateScreen; 957 } 958 959 protected void updateStorageSpace() { 960 mStorageSpace = Storage.getAvailableSpace(); 961 } 962 963 protected long getStorageSpace() { 964 return mStorageSpace; 965 } 966 967 protected void updateStorageSpaceAndHint() { 968 updateStorageSpace(); 969 updateStorageHint(mStorageSpace); 970 } 971 972 protected void updateStorageHint() { 973 updateStorageHint(mStorageSpace); 974 } 975 976 protected boolean updateStorageHintOnResume() { 977 return true; 978 } 979 980 protected void updateStorageHint(long storageSpace) { 981 String message = null; 982 if (storageSpace == Storage.UNAVAILABLE) { 983 message = getString(R.string.no_storage); 984 } else if (storageSpace == Storage.PREPARING) { 985 message = getString(R.string.preparing_sd); 986 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 987 message = getString(R.string.access_sd_fail); 988 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) { 989 message = getString(R.string.spaceIsLow_content); 990 } 991 992 if (message != null) { 993 if (mStorageHint == null) { 994 mStorageHint = OnScreenHint.makeText(this, message); 995 } else { 996 mStorageHint.setText(message); 997 } 998 mStorageHint.show(); 999 } else if (mStorageHint != null) { 1000 mStorageHint.cancel(); 1001 mStorageHint = null; 1002 } 1003 } 1004 1005 protected void setResultEx(int resultCode) { 1006 mResultCodeForTesting = resultCode; 1007 setResult(resultCode); 1008 } 1009 1010 protected void setResultEx(int resultCode, Intent data) { 1011 mResultCodeForTesting = resultCode; 1012 mResultDataForTesting = data; 1013 setResult(resultCode, data); 1014 } 1015 1016 public int getResultCode() { 1017 return mResultCodeForTesting; 1018 } 1019 1020 public Intent getResultData() { 1021 return mResultDataForTesting; 1022 } 1023 1024 public boolean isSecureCamera() { 1025 return mSecureCamera; 1026 } 1027 1028 @Override 1029 public void onModuleSelected(int moduleIndex) { 1030 if (mCurrentModuleIndex == moduleIndex) { 1031 return; 1032 } 1033 1034 CameraHolder.instance().keep(); 1035 closeModule(mCurrentModule); 1036 setModuleFromIndex(moduleIndex); 1037 1038 openModule(mCurrentModule); 1039 mCurrentModule.onOrientationChanged(mLastRawOrientation); 1040 if (mMediaSaveService != null) { 1041 mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); 1042 } 1043 1044 // Store the module index so we can use it the next time the Camera 1045 // starts up. 1046 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 1047 prefs.edit().putInt(PREF_STARTUP_MODULE_INDEX, moduleIndex).apply(); 1048 } 1049 1050 /** 1051 * Sets the mCurrentModuleIndex, creates a new module instance for the given 1052 * index an sets it as mCurrentModule. 1053 */ 1054 private void setModuleFromIndex(int moduleIndex) { 1055 mCurrentModuleIndex = moduleIndex; 1056 switch (moduleIndex) { 1057 case ModuleSwitcher.VIDEO_MODULE_INDEX: { 1058 mCurrentModule = new VideoModule(); 1059 break; 1060 } 1061 1062 case ModuleSwitcher.PHOTO_MODULE_INDEX: { 1063 mCurrentModule = new PhotoModule(); 1064 break; 1065 } 1066 1067 case ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX: { 1068 mCurrentModule = new WideAnglePanoramaModule(); 1069 break; 1070 } 1071 1072 case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX: { 1073 mCurrentModule = PhotoSphereHelper.createPanoramaModule(); 1074 break; 1075 } 1076 1077 default: 1078 break; 1079 } 1080 } 1081 1082 /** 1083 * Launches an ACTION_EDIT intent for the given local data item. 1084 */ 1085 public void launchEditor(LocalData data) { 1086 Intent intent = new Intent(Intent.ACTION_EDIT) 1087 .setDataAndType(data.getContentUri(), data.getMimeType()) 1088 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 1089 startActivityForResult(Intent.createChooser(intent, null), REQ_CODE_EDIT); 1090 } 1091 1092 /** 1093 * Launch the tiny planet editor. 1094 * 1095 * @param data the data must be a 360 degree stereographically mapped 1096 * panoramic image. It will not be modified, instead a new item 1097 * with the result will be added to the filmstrip. 1098 */ 1099 public void launchTinyPlanetEditor(LocalData data) { 1100 TinyPlanetFragment fragment = new TinyPlanetFragment(); 1101 Bundle bundle = new Bundle(); 1102 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString()); 1103 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle()); 1104 fragment.setArguments(bundle); 1105 fragment.show(getFragmentManager(), "tiny_planet"); 1106 } 1107 1108 private void openModule(CameraModule module) { 1109 module.init(this, mCameraModuleRootView); 1110 module.onResumeBeforeSuper(); 1111 module.onResumeAfterSuper(); 1112 } 1113 1114 private void closeModule(CameraModule module) { 1115 module.onPauseBeforeSuper(); 1116 module.onPauseAfterSuper(); 1117 ((ViewGroup) mCameraModuleRootView).removeAllViews(); 1118 } 1119 1120 private void performDeletion() { 1121 if (!mPendingDeletion) { 1122 return; 1123 } 1124 hideUndoDeletionBar(false); 1125 mDataAdapter.executeDeletion(CameraActivity.this); 1126 } 1127 1128 public void showUndoDeletionBar() { 1129 if (mPendingDeletion) { 1130 performDeletion(); 1131 } 1132 Log.v(TAG, "showing undo bar"); 1133 mPendingDeletion = true; 1134 if (mUndoDeletionBar == null) { 1135 ViewGroup v = (ViewGroup) getLayoutInflater().inflate( 1136 R.layout.undo_bar, mAboveFilmstripControlLayout, true); 1137 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 1138 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 1139 button.setOnClickListener(new View.OnClickListener() { 1140 @Override 1141 public void onClick(View view) { 1142 mDataAdapter.undoDataRemoval(); 1143 hideUndoDeletionBar(true); 1144 } 1145 }); 1146 // Setting undo bar clickable to avoid touch events going through 1147 // the bar to the buttons (eg. edit button, etc) underneath the bar. 1148 mUndoDeletionBar.setClickable(true); 1149 // When there is user interaction going on with the undo button, we 1150 // do not want to hide the undo bar. 1151 button.setOnTouchListener(new View.OnTouchListener() { 1152 @Override 1153 public boolean onTouch(View v, MotionEvent event) { 1154 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 1155 mIsUndoingDeletion = true; 1156 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 1157 mIsUndoingDeletion =false; 1158 } 1159 return false; 1160 } 1161 }); 1162 } 1163 mUndoDeletionBar.setAlpha(0f); 1164 mUndoDeletionBar.setVisibility(View.VISIBLE); 1165 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 1166 } 1167 1168 private void hideUndoDeletionBar(boolean withAnimation) { 1169 Log.v(TAG, "Hiding undo deletion bar"); 1170 mPendingDeletion = false; 1171 if (mUndoDeletionBar != null) { 1172 if (withAnimation) { 1173 mUndoDeletionBar.animate() 1174 .setDuration(200) 1175 .alpha(0f) 1176 .setListener(new Animator.AnimatorListener() { 1177 @Override 1178 public void onAnimationStart(Animator animation) { 1179 // Do nothing. 1180 } 1181 1182 @Override 1183 public void onAnimationEnd(Animator animation) { 1184 mUndoDeletionBar.setVisibility(View.GONE); 1185 } 1186 1187 @Override 1188 public void onAnimationCancel(Animator animation) { 1189 // Do nothing. 1190 } 1191 1192 @Override 1193 public void onAnimationRepeat(Animator animation) { 1194 // Do nothing. 1195 } 1196 }) 1197 .start(); 1198 } else { 1199 mUndoDeletionBar.setVisibility(View.GONE); 1200 } 1201 } 1202 } 1203 1204 @Override 1205 public void onShowSwitcherPopup() { 1206 } 1207 1208 /** 1209 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 1210 * capture intent. 1211 * 1212 * @param enable {@code true} to enable swipe. 1213 */ 1214 public void setSwipingEnabled(boolean enable) { 1215 if (isCaptureIntent()) { 1216 mCameraPreviewData.lockPreview(true); 1217 } else { 1218 mCameraPreviewData.lockPreview(!enable); 1219 } 1220 } 1221 1222 // Accessor methods for getting latency times used in performance testing 1223 public long getAutoFocusTime() { 1224 return (mCurrentModule instanceof PhotoModule) ? 1225 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 1226 } 1227 1228 public long getShutterLag() { 1229 return (mCurrentModule instanceof PhotoModule) ? 1230 ((PhotoModule) mCurrentModule).mShutterLag : -1; 1231 } 1232 1233 public long getShutterToPictureDisplayedTime() { 1234 return (mCurrentModule instanceof PhotoModule) ? 1235 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 1236 } 1237 1238 public long getPictureDisplayedToJpegCallbackTime() { 1239 return (mCurrentModule instanceof PhotoModule) ? 1240 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 1241 } 1242 1243 public long getJpegCallbackFinishTime() { 1244 return (mCurrentModule instanceof PhotoModule) ? 1245 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 1246 } 1247 1248 public long getCaptureStartTime() { 1249 return (mCurrentModule instanceof PhotoModule) ? 1250 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 1251 } 1252 1253 public boolean isRecording() { 1254 return (mCurrentModule instanceof VideoModule) ? 1255 ((VideoModule) mCurrentModule).isRecording() : false; 1256 } 1257} 1258