ActivityBase.java revision ae8b4eecd123aae8593b620b9e0783a03a907a98
1/* 2 * Copyright (C) 2009 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.content.BroadcastReceiver; 20import android.content.ContentResolver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.graphics.Bitmap; 25import android.graphics.BitmapFactory; 26import android.graphics.Rect; 27import android.hardware.Camera.Parameters; 28import android.os.AsyncTask; 29import android.os.Bundle; 30import android.os.Handler; 31import android.os.Message; 32import android.support.v4.content.LocalBroadcastManager; 33import android.util.Log; 34import android.view.KeyEvent; 35import android.view.View; 36import android.view.Window; 37import android.view.WindowManager; 38import android.view.animation.AlphaAnimation; 39import android.view.animation.Animation; 40import android.view.animation.DecelerateInterpolator; 41 42import com.android.camera.ui.CameraPicker; 43import com.android.camera.ui.LayoutChangeNotifier; 44import com.android.camera.ui.PopupManager; 45import com.android.camera.ui.RotateImageView; 46import com.android.gallery3d.app.AbstractGalleryActivity; 47import com.android.gallery3d.app.AppBridge; 48import com.android.gallery3d.app.GalleryActionBar; 49import com.android.gallery3d.app.PhotoPage; 50import com.android.gallery3d.common.ApiHelper; 51import com.android.gallery3d.ui.BitmapScreenNail; 52import com.android.gallery3d.ui.ScreenNail; 53import com.android.gallery3d.util.MediaSetUtils; 54 55import java.io.File; 56 57/** 58 * Superclass of Camera and VideoCamera activities. 59 */ 60public abstract class ActivityBase extends AbstractGalleryActivity 61 implements LayoutChangeNotifier.Listener { 62 63 private static final String TAG = "ActivityBase"; 64 private static final boolean LOGV = false; 65 private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100; // milliseconds 66 private static final String ACTION_DELETE_PICTURE = 67 "com.android.gallery3d.action.DELETE_PICTURE"; 68 69 private int mResultCodeForTesting; 70 private Intent mResultDataForTesting; 71 private OnScreenHint mStorageHint; 72 private View mSingleTapArea; 73 74 // The bitmap of the last captured picture thumbnail and the URI of the 75 // original picture. 76 protected Thumbnail mThumbnail; 77 protected int mThumbnailViewWidth; // layout width of the thumbnail 78 protected AsyncTask<Void, Void, Thumbnail> mLoadThumbnailTask; 79 // An imageview showing the last captured picture thumbnail. 80 protected RotateImageView mThumbnailView; 81 protected CameraPicker mCameraPicker; 82 83 protected boolean mOpenCameraFail; 84 protected boolean mCameraDisabled; 85 protected CameraManager.CameraProxy mCameraDevice; 86 protected Parameters mParameters; 87 // The activity is paused. The classes that extend this class should set 88 // mPaused the first thing in onResume/onPause. 89 protected boolean mPaused; 90 protected GalleryActionBar mActionBar; 91 92 // multiple cameras support 93 protected int mNumberOfCameras; 94 protected int mCameraId; 95 // The activity is going to switch to the specified camera id. This is 96 // needed because texture copy is done in GL thread. -1 means camera is not 97 // switching. 98 protected int mPendingSwitchCameraId = -1; 99 100 protected MyAppBridge mAppBridge; 101 protected ScreenNail mCameraScreenNail; // This shows camera preview. 102 // The view containing only camera related widgets like control panel, 103 // indicator bar, focus indicator and etc. 104 protected View mCameraAppView; 105 protected boolean mShowCameraAppView = true; 106 private Animation mCameraAppViewFadeIn; 107 private Animation mCameraAppViewFadeOut; 108 109 private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD; 110 private static final int UPDATE_STORAGE_HINT = 0; 111 private final Handler mHandler = new Handler() { 112 @Override 113 public void handleMessage(Message msg) { 114 switch (msg.what) { 115 case UPDATE_STORAGE_HINT: 116 updateStorageHint(); 117 return; 118 } 119 } 120 }; 121 122 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 123 @Override 124 public void onReceive(Context context, Intent intent) { 125 String action = intent.getAction(); 126 if (action.equals(Intent.ACTION_MEDIA_MOUNTED) 127 || action.equals(Intent.ACTION_MEDIA_UNMOUNTED) 128 || action.equals(Intent.ACTION_MEDIA_CHECKING) 129 || action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { 130 updateStorageSpaceAndHint(); 131 } 132 } 133 }; 134 135 private boolean mUpdateThumbnailDelayed; 136 private IntentFilter mDeletePictureFilter = 137 new IntentFilter(ACTION_DELETE_PICTURE); 138 private BroadcastReceiver mDeletePictureReceiver = 139 new BroadcastReceiver() { 140 @Override 141 public void onReceive(Context context, Intent intent) { 142 if (mShowCameraAppView) { 143 getLastThumbnailUncached(); 144 } else { 145 mUpdateThumbnailDelayed = true; 146 } 147 } 148 }; 149 150 protected class CameraOpenThread extends Thread { 151 @Override 152 public void run() { 153 try { 154 mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId); 155 mParameters = mCameraDevice.getParameters(); 156 } catch (CameraHardwareException e) { 157 mOpenCameraFail = true; 158 } catch (CameraDisabledException e) { 159 mCameraDisabled = true; 160 } 161 } 162 } 163 164 @Override 165 public void onCreate(Bundle icicle) { 166 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); 167 super.disableToggleStatusBar(); 168 // Set a theme with action bar. It is not specified in manifest because 169 // we want to hide it by default. setTheme must happen before 170 // setContentView. 171 // 172 // This must be set before we call super.onCreate(), where the window's 173 // background is removed. 174 setTheme(R.style.Theme_Gallery); 175 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 176 if (ApiHelper.HAS_ACTION_BAR) { 177 requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); 178 } else { 179 requestWindowFeature(Window.FEATURE_NO_TITLE); 180 } 181 182 super.onCreate(icicle); 183 } 184 185 public boolean isPanoramaActivity() { 186 return false; 187 } 188 189 @Override 190 protected void onResume() { 191 super.onResume(); 192 LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this); 193 manager.registerReceiver(mDeletePictureReceiver, mDeletePictureFilter); 194 195 installIntentFilter(); 196 if(updateStorageHintOnResume()) { 197 updateStorageSpace(); 198 mHandler.sendEmptyMessageDelayed(UPDATE_STORAGE_HINT, 200); 199 } 200 } 201 202 @Override 203 protected void onPause() { 204 super.onPause(); 205 LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this); 206 manager.unregisterReceiver(mDeletePictureReceiver); 207 208 if (LOGV) Log.v(TAG, "onPause"); 209 saveThumbnailToFile(); 210 211 if (mLoadThumbnailTask != null) { 212 mLoadThumbnailTask.cancel(true); 213 mLoadThumbnailTask = null; 214 } 215 216 if (mStorageHint != null) { 217 mStorageHint.cancel(); 218 mStorageHint = null; 219 } 220 221 unregisterReceiver(mReceiver); 222 } 223 224 @Override 225 public void setContentView(int layoutResID) { 226 super.setContentView(layoutResID); 227 // getActionBar() should be after setContentView 228 mActionBar = new GalleryActionBar(this); 229 mActionBar.hide(); 230 } 231 232 @Override 233 public boolean onSearchRequested() { 234 return false; 235 } 236 237 @Override 238 public boolean onKeyDown(int keyCode, KeyEvent event) { 239 // Prevent software keyboard or voice search from showing up. 240 if (keyCode == KeyEvent.KEYCODE_SEARCH 241 || keyCode == KeyEvent.KEYCODE_MENU) { 242 if (event.isLongPress()) return true; 243 } 244 if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) { 245 return true; 246 } 247 248 return super.onKeyDown(keyCode, event); 249 } 250 251 @Override 252 public boolean onKeyUp(int keyCode, KeyEvent event) { 253 if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) { 254 return true; 255 } 256 return super.onKeyUp(keyCode, event); 257 } 258 259 protected void setResultEx(int resultCode) { 260 mResultCodeForTesting = resultCode; 261 setResult(resultCode); 262 } 263 264 protected void setResultEx(int resultCode, Intent data) { 265 mResultCodeForTesting = resultCode; 266 mResultDataForTesting = data; 267 setResult(resultCode, data); 268 } 269 270 public int getResultCode() { 271 return mResultCodeForTesting; 272 } 273 274 public Intent getResultData() { 275 return mResultDataForTesting; 276 } 277 278 @Override 279 protected void onDestroy() { 280 PopupManager.removeInstance(this); 281 super.onDestroy(); 282 } 283 284 protected void installIntentFilter() { 285 // install an intent filter to receive SD card related events. 286 IntentFilter intentFilter = 287 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); 288 intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 289 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 290 intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING); 291 intentFilter.addDataScheme("file"); 292 registerReceiver(mReceiver, intentFilter); 293 } 294 295 protected void updateStorageSpace() { 296 mStorageSpace = Storage.getAvailableSpace(); 297 } 298 299 protected long getStorageSpace() { 300 return mStorageSpace; 301 } 302 303 protected void updateStorageSpaceAndHint() { 304 updateStorageSpace(); 305 updateStorageHint(mStorageSpace); 306 } 307 308 protected void updateStorageHint() { 309 updateStorageHint(mStorageSpace); 310 } 311 312 protected boolean updateStorageHintOnResume() { 313 return true; 314 } 315 316 protected void updateStorageHint(long storageSpace) { 317 String message = null; 318 if (storageSpace == Storage.UNAVAILABLE) { 319 message = getString(R.string.no_storage); 320 } else if (storageSpace == Storage.PREPARING) { 321 message = getString(R.string.preparing_sd); 322 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 323 message = getString(R.string.access_sd_fail); 324 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) { 325 message = getString(R.string.spaceIsLow_content); 326 } 327 328 if (message != null) { 329 if (mStorageHint == null) { 330 mStorageHint = OnScreenHint.makeText(this, message); 331 } else { 332 mStorageHint.setText(message); 333 } 334 mStorageHint.show(); 335 } else if (mStorageHint != null) { 336 mStorageHint.cancel(); 337 mStorageHint = null; 338 } 339 } 340 341 protected void updateThumbnailView() { 342 if (mThumbnail != null) { 343 mThumbnailView.setBitmap(mThumbnail.getBitmap()); 344 mThumbnailView.setVisibility(View.VISIBLE); 345 } else { 346 mThumbnailView.setBitmap(null); 347 mThumbnailView.setVisibility(View.GONE); 348 } 349 } 350 351 protected void getLastThumbnail() { 352 mThumbnail = ThumbnailHolder.getLastThumbnail(getContentResolver()); 353 // Suppose users tap the thumbnail view, go to the gallery, delete the 354 // image, and coming back to the camera. Thumbnail file will be invalid. 355 // Since the new thumbnail will be loaded in another thread later, the 356 // view should be set to gone to prevent from opening the invalid image. 357 updateThumbnailView(); 358 if (mThumbnail == null) { 359 mLoadThumbnailTask = new LoadThumbnailTask(true).execute(); 360 } 361 } 362 363 protected void getLastThumbnailUncached() { 364 if (mLoadThumbnailTask != null) mLoadThumbnailTask.cancel(true); 365 mLoadThumbnailTask = new LoadThumbnailTask(false).execute(); 366 } 367 368 private class LoadThumbnailTask extends AsyncTask<Void, Void, Thumbnail> { 369 private boolean mLookAtCache; 370 371 public LoadThumbnailTask(boolean lookAtCache) { 372 mLookAtCache = lookAtCache; 373 } 374 375 @Override 376 protected Thumbnail doInBackground(Void... params) { 377 // Load the thumbnail from the file. 378 ContentResolver resolver = getContentResolver(); 379 Thumbnail t = null; 380 if (mLookAtCache) { 381 t = Thumbnail.getLastThumbnailFromFile(getFilesDir(), resolver); 382 } 383 384 if (isCancelled()) return null; 385 386 if (t == null) { 387 Thumbnail result[] = new Thumbnail[1]; 388 // Load the thumbnail from the media provider. 389 int code = Thumbnail.getLastThumbnailFromContentResolver( 390 resolver, result); 391 switch (code) { 392 case Thumbnail.THUMBNAIL_FOUND: 393 return result[0]; 394 case Thumbnail.THUMBNAIL_NOT_FOUND: 395 return null; 396 case Thumbnail.THUMBNAIL_DELETED: 397 cancel(true); 398 return null; 399 } 400 } 401 return t; 402 } 403 404 @Override 405 protected void onPostExecute(Thumbnail thumbnail) { 406 if (isCancelled()) return; 407 mThumbnail = thumbnail; 408 updateThumbnailView(); 409 } 410 } 411 412 protected void gotoGallery() { 413 // Move the next picture with capture animation. "1" means next. 414 mAppBridge.switchWithCaptureAnimation(1); 415 } 416 417 protected void saveThumbnailToFile() { 418 if (mThumbnail != null && !mThumbnail.fromFile()) { 419 new SaveThumbnailTask().execute(mThumbnail); 420 } 421 } 422 423 private class SaveThumbnailTask extends AsyncTask<Thumbnail, Void, Void> { 424 @Override 425 protected Void doInBackground(Thumbnail... params) { 426 final int n = params.length; 427 final File filesDir = getFilesDir(); 428 for (int i = 0; i < n; i++) { 429 params[i].saveLastThumbnailToFile(filesDir); 430 } 431 return null; 432 } 433 } 434 435 // Call this after setContentView. 436 protected void createCameraScreenNail(boolean getPictures) { 437 mCameraAppView = findViewById(R.id.camera_app_root); 438 Bundle data = new Bundle(); 439 String path = "/local/all/"; 440 // Intent mode does not show camera roll. Use 0 as a work around for 441 // invalid bucket id. 442 // TODO: add support of empty media set in gallery. 443 path += (getPictures ? MediaSetUtils.CAMERA_BUCKET_ID : "0"); 444 data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path); 445 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path); 446 447 // Send an AppBridge to gallery to enable the camera preview. 448 mAppBridge = new MyAppBridge(); 449 data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge); 450 getStateManager().startState(PhotoPage.class, data); 451 mCameraScreenNail = mAppBridge.getCameraScreenNail(); 452 } 453 454 private class HideCameraAppView implements Animation.AnimationListener { 455 @Override 456 public void onAnimationEnd(Animation animation) { 457 // We cannot set this as GONE because we want to receive the 458 // onLayoutChange() callback even when we are invisible. 459 mCameraAppView.setVisibility(View.INVISIBLE); 460 } 461 462 @Override 463 public void onAnimationRepeat(Animation animation) { 464 } 465 466 @Override 467 public void onAnimationStart(Animation animation) { 468 } 469 } 470 471 protected void updateCameraAppView() { 472 // Initialize the animation. 473 if (mCameraAppViewFadeIn == null) { 474 mCameraAppViewFadeIn = new AlphaAnimation(0f, 1f); 475 mCameraAppViewFadeIn.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME); 476 mCameraAppViewFadeIn.setInterpolator(new DecelerateInterpolator()); 477 478 mCameraAppViewFadeOut = new AlphaAnimation(1f, 0f); 479 mCameraAppViewFadeOut.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME); 480 mCameraAppViewFadeOut.setInterpolator(new DecelerateInterpolator()); 481 mCameraAppViewFadeOut.setAnimationListener(new HideCameraAppView()); 482 } 483 484 if (mShowCameraAppView) { 485 mCameraAppView.setVisibility(View.VISIBLE); 486 // The "transparent region" is not recomputed when a sibling of 487 // SurfaceView changes visibility (unless it involves GONE). It's 488 // been broken since 1.0. Call requestLayout to work around it. 489 mCameraAppView.requestLayout(); 490 mCameraAppView.startAnimation(mCameraAppViewFadeIn); 491 } else { 492 mCameraAppView.startAnimation(mCameraAppViewFadeOut); 493 } 494 } 495 496 protected void onFullScreenChanged(boolean full) { 497 if (mShowCameraAppView == full) return; 498 mShowCameraAppView = full; 499 if (mPaused || isFinishing()) return; 500 updateCameraAppView(); 501 502 // If we received DELETE_PICTURE broadcasts while the Camera UI is 503 // hidden, we update the thumbnail now. 504 if (full && mUpdateThumbnailDelayed) { 505 getLastThumbnailUncached(); 506 mUpdateThumbnailDelayed = false; 507 } 508 } 509 510 @Override 511 public GalleryActionBar getGalleryActionBar() { 512 return mActionBar; 513 } 514 515 // Preview frame layout has changed. 516 @Override 517 public void onLayoutChange(View v, int left, int top, int right, int bottom) { 518 if (mAppBridge == null) return; 519 520 int width = right - left; 521 int height = bottom - top; 522 if (ApiHelper.HAS_SURFACE_TEXTURE) { 523 CameraScreenNail screenNail = (CameraScreenNail) mCameraScreenNail; 524 if (Util.getDisplayRotation(this) % 180 == 0) { 525 screenNail.setPreviewFrameLayoutSize(width, height); 526 } else { 527 // Swap the width and height. Camera screen nail draw() is based on 528 // natural orientation, not the view system orientation. 529 screenNail.setPreviewFrameLayoutSize(height, width); 530 } 531 } 532 533 // Find out the coordinates of the preview frame relative to GL 534 // root view. 535 View root = (View) getGLRoot(); 536 int[] rootLocation = new int[2]; 537 int[] viewLocation = new int[2]; 538 root.getLocationInWindow(rootLocation); 539 v.getLocationInWindow(viewLocation); 540 541 int l = viewLocation[0] - rootLocation[0]; 542 int t = viewLocation[1] - rootLocation[1]; 543 int r = l + width; 544 int b = t + height; 545 Rect frame = new Rect(l, t, r, b); 546 Log.d(TAG, "set CameraRelativeFrame as " + frame); 547 mAppBridge.setCameraRelativeFrame(frame); 548 } 549 550 protected void setSingleTapUpListener(View singleTapArea) { 551 mSingleTapArea = singleTapArea; 552 } 553 554 private boolean onSingleTapUp(int x, int y) { 555 // Ignore if listener is null or the camera control is invisible. 556 if (mSingleTapArea == null || !mShowCameraAppView) return false; 557 558 int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(), 559 mSingleTapArea); 560 x -= relativeLocation[0]; 561 y -= relativeLocation[1]; 562 if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0 563 && y < mSingleTapArea.getHeight()) { 564 onSingleTapUp(mSingleTapArea, x, y); 565 return true; 566 } 567 return false; 568 } 569 570 protected void onSingleTapUp(View view, int x, int y) { 571 } 572 573 protected void setSwipingEnabled(boolean enabled) { 574 mAppBridge.setSwipingEnabled(enabled); 575 } 576 577 protected void notifyScreenNailChanged() { 578 mAppBridge.notifyScreenNailChanged(); 579 } 580 581 protected void onPreviewTextureCopied() { 582 } 583 584 ////////////////////////////////////////////////////////////////////////// 585 // The is the communication interface between the Camera Application and 586 // the Gallery PhotoPage. 587 ////////////////////////////////////////////////////////////////////////// 588 589 class MyAppBridge extends AppBridge implements CameraScreenNail.Listener { 590 @SuppressWarnings("hiding") 591 private ScreenNail mCameraScreenNail; 592 private Server mServer; 593 594 @Override 595 public ScreenNail attachScreenNail() { 596 if (mCameraScreenNail == null) { 597 if (ApiHelper.HAS_SURFACE_TEXTURE) { 598 mCameraScreenNail = new CameraScreenNail(this); 599 } else { 600 Bitmap b = BitmapFactory.decodeResource(getResources(), 601 R.drawable.wallpaper_picker_preview); 602 mCameraScreenNail = new StaticBitmapScreenNail(b); 603 } 604 } 605 return mCameraScreenNail; 606 } 607 608 @Override 609 public void detachScreenNail() { 610 mCameraScreenNail = null; 611 } 612 613 public ScreenNail getCameraScreenNail() { 614 return mCameraScreenNail; 615 } 616 617 // Return true if the tap is consumed. 618 @Override 619 public boolean onSingleTapUp(int x, int y) { 620 return ActivityBase.this.onSingleTapUp(x, y); 621 } 622 623 // This is used to notify that the screen nail will be drawn in full screen 624 // or not in next draw() call. 625 @Override 626 public void onFullScreenChanged(boolean full) { 627 ActivityBase.this.onFullScreenChanged(full); 628 } 629 630 @Override 631 public void requestRender() { 632 getGLRoot().requestRender(); 633 } 634 635 @Override 636 public void onPreviewTextureCopied() { 637 ActivityBase.this.onPreviewTextureCopied(); 638 } 639 640 @Override 641 public void setServer(Server s) { 642 mServer = s; 643 } 644 645 @Override 646 public boolean isPanorama() { 647 return ActivityBase.this.isPanoramaActivity(); 648 } 649 650 @Override 651 public boolean isStaticCamera() { 652 return !ApiHelper.HAS_SURFACE_TEXTURE; 653 } 654 655 private void setCameraRelativeFrame(Rect frame) { 656 if (mServer != null) mServer.setCameraRelativeFrame(frame); 657 } 658 659 private void switchWithCaptureAnimation(int offset) { 660 if (mServer != null) mServer.switchWithCaptureAnimation(offset); 661 } 662 663 private void setSwipingEnabled(boolean enabled) { 664 if (mServer != null) mServer.setSwipingEnabled(enabled); 665 } 666 667 private void notifyScreenNailChanged() { 668 if (mServer != null) mServer.notifyScreenNailChanged(); 669 } 670 } 671} 672