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