ActivityBase.java revision a96d8603560c2131d1555df82b8f64c6eb06909c
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 requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); 177 178 super.onCreate(icicle); 179 } 180 181 public boolean isPanoramaActivity() { 182 return false; 183 } 184 185 @Override 186 protected void onResume() { 187 super.onResume(); 188 LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this); 189 manager.registerReceiver(mDeletePictureReceiver, mDeletePictureFilter); 190 191 installIntentFilter(); 192 if(updateStorageHintOnResume()) { 193 updateStorageSpace(); 194 mHandler.sendEmptyMessageDelayed(UPDATE_STORAGE_HINT, 200); 195 } 196 } 197 198 @Override 199 protected void onPause() { 200 super.onPause(); 201 LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this); 202 manager.unregisterReceiver(mDeletePictureReceiver); 203 204 if (LOGV) Log.v(TAG, "onPause"); 205 saveThumbnailToFile(); 206 207 if (mLoadThumbnailTask != null) { 208 mLoadThumbnailTask.cancel(true); 209 mLoadThumbnailTask = null; 210 } 211 212 if (mStorageHint != null) { 213 mStorageHint.cancel(); 214 mStorageHint = null; 215 } 216 217 unregisterReceiver(mReceiver); 218 } 219 220 @Override 221 public void setContentView(int layoutResID) { 222 super.setContentView(layoutResID); 223 // getActionBar() should be after setContentView 224 mActionBar = new GalleryActionBar(this); 225 mActionBar.hide(); 226 } 227 228 @Override 229 public boolean onSearchRequested() { 230 return false; 231 } 232 233 @Override 234 public boolean onKeyDown(int keyCode, KeyEvent event) { 235 // Prevent software keyboard or voice search from showing up. 236 if (keyCode == KeyEvent.KEYCODE_SEARCH 237 || keyCode == KeyEvent.KEYCODE_MENU) { 238 if (event.isLongPress()) return true; 239 } 240 if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) { 241 return true; 242 } 243 244 return super.onKeyDown(keyCode, event); 245 } 246 247 @Override 248 public boolean onKeyUp(int keyCode, KeyEvent event) { 249 if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) { 250 return true; 251 } 252 return super.onKeyUp(keyCode, event); 253 } 254 255 protected void setResultEx(int resultCode) { 256 mResultCodeForTesting = resultCode; 257 setResult(resultCode); 258 } 259 260 protected void setResultEx(int resultCode, Intent data) { 261 mResultCodeForTesting = resultCode; 262 mResultDataForTesting = data; 263 setResult(resultCode, data); 264 } 265 266 public int getResultCode() { 267 return mResultCodeForTesting; 268 } 269 270 public Intent getResultData() { 271 return mResultDataForTesting; 272 } 273 274 @Override 275 protected void onDestroy() { 276 PopupManager.removeInstance(this); 277 super.onDestroy(); 278 } 279 280 protected void installIntentFilter() { 281 // install an intent filter to receive SD card related events. 282 IntentFilter intentFilter = 283 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); 284 intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 285 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 286 intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING); 287 intentFilter.addDataScheme("file"); 288 registerReceiver(mReceiver, intentFilter); 289 } 290 291 protected void updateStorageSpace() { 292 mStorageSpace = Storage.getAvailableSpace(); 293 } 294 295 protected long getStorageSpace() { 296 return mStorageSpace; 297 } 298 299 protected void updateStorageSpaceAndHint() { 300 updateStorageSpace(); 301 updateStorageHint(mStorageSpace); 302 } 303 304 protected void updateStorageHint() { 305 updateStorageHint(mStorageSpace); 306 } 307 308 protected boolean updateStorageHintOnResume() { 309 return true; 310 } 311 312 protected void updateStorageHint(long storageSpace) { 313 String message = null; 314 if (storageSpace == Storage.UNAVAILABLE) { 315 message = getString(R.string.no_storage); 316 } else if (storageSpace == Storage.PREPARING) { 317 message = getString(R.string.preparing_sd); 318 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 319 message = getString(R.string.access_sd_fail); 320 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) { 321 message = getString(R.string.spaceIsLow_content); 322 } 323 324 if (message != null) { 325 if (mStorageHint == null) { 326 mStorageHint = OnScreenHint.makeText(this, message); 327 } else { 328 mStorageHint.setText(message); 329 } 330 mStorageHint.show(); 331 } else if (mStorageHint != null) { 332 mStorageHint.cancel(); 333 mStorageHint = null; 334 } 335 } 336 337 protected void updateThumbnailView() { 338 if (mThumbnail != null) { 339 mThumbnailView.setBitmap(mThumbnail.getBitmap()); 340 mThumbnailView.setVisibility(View.VISIBLE); 341 } else { 342 mThumbnailView.setBitmap(null); 343 mThumbnailView.setVisibility(View.GONE); 344 } 345 } 346 347 protected void getLastThumbnail() { 348 mThumbnail = ThumbnailHolder.getLastThumbnail(getContentResolver()); 349 // Suppose users tap the thumbnail view, go to the gallery, delete the 350 // image, and coming back to the camera. Thumbnail file will be invalid. 351 // Since the new thumbnail will be loaded in another thread later, the 352 // view should be set to gone to prevent from opening the invalid image. 353 updateThumbnailView(); 354 if (mThumbnail == null) { 355 mLoadThumbnailTask = new LoadThumbnailTask(true).execute(); 356 } 357 } 358 359 protected void getLastThumbnailUncached() { 360 if (mLoadThumbnailTask != null) mLoadThumbnailTask.cancel(true); 361 mLoadThumbnailTask = new LoadThumbnailTask(false).execute(); 362 } 363 364 private class LoadThumbnailTask extends AsyncTask<Void, Void, Thumbnail> { 365 private boolean mLookAtCache; 366 367 public LoadThumbnailTask(boolean lookAtCache) { 368 mLookAtCache = lookAtCache; 369 } 370 371 @Override 372 protected Thumbnail doInBackground(Void... params) { 373 // Load the thumbnail from the file. 374 ContentResolver resolver = getContentResolver(); 375 Thumbnail t = null; 376 if (mLookAtCache) { 377 t = Thumbnail.getLastThumbnailFromFile(getFilesDir(), resolver); 378 } 379 380 if (isCancelled()) return null; 381 382 if (t == null) { 383 Thumbnail result[] = new Thumbnail[1]; 384 // Load the thumbnail from the media provider. 385 int code = Thumbnail.getLastThumbnailFromContentResolver( 386 resolver, result); 387 switch (code) { 388 case Thumbnail.THUMBNAIL_FOUND: 389 return result[0]; 390 case Thumbnail.THUMBNAIL_NOT_FOUND: 391 return null; 392 case Thumbnail.THUMBNAIL_DELETED: 393 cancel(true); 394 return null; 395 } 396 } 397 return t; 398 } 399 400 @Override 401 protected void onPostExecute(Thumbnail thumbnail) { 402 if (isCancelled()) return; 403 mThumbnail = thumbnail; 404 updateThumbnailView(); 405 } 406 } 407 408 protected void gotoGallery() { 409 // Move the next picture with capture animation. "1" means next. 410 mAppBridge.switchWithCaptureAnimation(1); 411 } 412 413 protected void saveThumbnailToFile() { 414 if (mThumbnail != null && !mThumbnail.fromFile()) { 415 new SaveThumbnailTask().execute(mThumbnail); 416 } 417 } 418 419 private class SaveThumbnailTask extends AsyncTask<Thumbnail, Void, Void> { 420 @Override 421 protected Void doInBackground(Thumbnail... params) { 422 final int n = params.length; 423 final File filesDir = getFilesDir(); 424 for (int i = 0; i < n; i++) { 425 params[i].saveLastThumbnailToFile(filesDir); 426 } 427 return null; 428 } 429 } 430 431 // Call this after setContentView. 432 protected void createCameraScreenNail(boolean getPictures) { 433 mCameraAppView = findViewById(R.id.camera_app_root); 434 Bundle data = new Bundle(); 435 String path = "/local/all/"; 436 // Intent mode does not show camera roll. Use 0 as a work around for 437 // invalid bucket id. 438 // TODO: add support of empty media set in gallery. 439 path += (getPictures ? MediaSetUtils.CAMERA_BUCKET_ID : "0"); 440 data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path); 441 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path); 442 443 // Send an AppBridge to gallery to enable the camera preview. 444 mAppBridge = new MyAppBridge(); 445 data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge); 446 getStateManager().startState(PhotoPage.class, data); 447 mCameraScreenNail = mAppBridge.getCameraScreenNail(); 448 } 449 450 private class HideCameraAppView implements Animation.AnimationListener { 451 @Override 452 public void onAnimationEnd(Animation animation) { 453 // We cannot set this as GONE because we want to receive the 454 // onLayoutChange() callback even when we are invisible. 455 mCameraAppView.setVisibility(View.INVISIBLE); 456 } 457 458 @Override 459 public void onAnimationRepeat(Animation animation) { 460 } 461 462 @Override 463 public void onAnimationStart(Animation animation) { 464 } 465 } 466 467 protected void updateCameraAppView() { 468 // Initialize the animation. 469 if (mCameraAppViewFadeIn == null) { 470 mCameraAppViewFadeIn = new AlphaAnimation(0f, 1f); 471 mCameraAppViewFadeIn.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME); 472 mCameraAppViewFadeIn.setInterpolator(new DecelerateInterpolator()); 473 474 mCameraAppViewFadeOut = new AlphaAnimation(1f, 0f); 475 mCameraAppViewFadeOut.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME); 476 mCameraAppViewFadeOut.setInterpolator(new DecelerateInterpolator()); 477 mCameraAppViewFadeOut.setAnimationListener(new HideCameraAppView()); 478 } 479 480 if (mShowCameraAppView) { 481 mCameraAppView.setVisibility(View.VISIBLE); 482 // The "transparent region" is not recomputed when a sibling of 483 // SurfaceView changes visibility (unless it involves GONE). It's 484 // been broken since 1.0. Call requestLayout to work around it. 485 mCameraAppView.requestLayout(); 486 mCameraAppView.startAnimation(mCameraAppViewFadeIn); 487 } else { 488 mCameraAppView.startAnimation(mCameraAppViewFadeOut); 489 } 490 } 491 492 protected void onFullScreenChanged(boolean full) { 493 if (mShowCameraAppView == full) return; 494 mShowCameraAppView = full; 495 if (mPaused || isFinishing()) return; 496 updateCameraAppView(); 497 498 // If we received DELETE_PICTURE broadcasts while the Camera UI is 499 // hidden, we update the thumbnail now. 500 if (full && mUpdateThumbnailDelayed) { 501 getLastThumbnailUncached(); 502 mUpdateThumbnailDelayed = false; 503 } 504 } 505 506 @Override 507 public GalleryActionBar getGalleryActionBar() { 508 return mActionBar; 509 } 510 511 // Preview frame layout has changed. 512 @Override 513 public void onLayoutChange(View v, int left, int top, int right, int bottom) { 514 if (mAppBridge == null) return; 515 516 int width = right - left; 517 int height = bottom - top; 518 if (ApiHelper.HAS_SURFACE_TEXTURE) { 519 CameraScreenNail screenNail = (CameraScreenNail) mCameraScreenNail; 520 if (Util.getDisplayRotation(this) % 180 == 0) { 521 screenNail.setPreviewFrameLayoutSize(width, height); 522 } else { 523 // Swap the width and height. Camera screen nail draw() is based on 524 // natural orientation, not the view system orientation. 525 screenNail.setPreviewFrameLayoutSize(height, width); 526 } 527 } 528 529 // Find out the coordinates of the preview frame relative to GL 530 // root view. 531 View root = (View) getGLRoot(); 532 int[] rootLocation = new int[2]; 533 int[] viewLocation = new int[2]; 534 root.getLocationInWindow(rootLocation); 535 v.getLocationInWindow(viewLocation); 536 537 int l = viewLocation[0] - rootLocation[0]; 538 int t = viewLocation[1] - rootLocation[1]; 539 int r = l + width; 540 int b = t + height; 541 Rect frame = new Rect(l, t, r, b); 542 Log.d(TAG, "set CameraRelativeFrame as " + frame); 543 mAppBridge.setCameraRelativeFrame(frame); 544 } 545 546 protected void setSingleTapUpListener(View singleTapArea) { 547 mSingleTapArea = singleTapArea; 548 } 549 550 private boolean onSingleTapUp(int x, int y) { 551 // Ignore if listener is null or the camera control is invisible. 552 if (mSingleTapArea == null || !mShowCameraAppView) return false; 553 554 int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(), 555 mSingleTapArea); 556 x -= relativeLocation[0]; 557 y -= relativeLocation[1]; 558 if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0 559 && y < mSingleTapArea.getHeight()) { 560 onSingleTapUp(mSingleTapArea, x, y); 561 return true; 562 } 563 return false; 564 } 565 566 protected void onSingleTapUp(View view, int x, int y) { 567 } 568 569 protected void setSwipingEnabled(boolean enabled) { 570 mAppBridge.setSwipingEnabled(enabled); 571 } 572 573 protected void notifyScreenNailChanged() { 574 mAppBridge.notifyScreenNailChanged(); 575 } 576 577 protected void onPreviewTextureCopied() { 578 } 579 580 ////////////////////////////////////////////////////////////////////////// 581 // The is the communication interface between the Camera Application and 582 // the Gallery PhotoPage. 583 ////////////////////////////////////////////////////////////////////////// 584 585 class MyAppBridge extends AppBridge implements CameraScreenNail.Listener { 586 @SuppressWarnings("hiding") 587 private ScreenNail mCameraScreenNail; 588 private Server mServer; 589 590 @Override 591 public ScreenNail attachScreenNail() { 592 if (mCameraScreenNail == null) { 593 if (ApiHelper.HAS_SURFACE_TEXTURE) { 594 mCameraScreenNail = new CameraScreenNail(this); 595 } else { 596 Bitmap b = BitmapFactory.decodeResource(getResources(), 597 R.drawable.wallpaper_picker_preview); 598 mCameraScreenNail = new StaticBitmapScreenNail(b); 599 } 600 } 601 return mCameraScreenNail; 602 } 603 604 @Override 605 public void detachScreenNail() { 606 mCameraScreenNail = null; 607 } 608 609 public ScreenNail getCameraScreenNail() { 610 return mCameraScreenNail; 611 } 612 613 // Return true if the tap is consumed. 614 @Override 615 public boolean onSingleTapUp(int x, int y) { 616 return ActivityBase.this.onSingleTapUp(x, y); 617 } 618 619 // This is used to notify that the screen nail will be drawn in full screen 620 // or not in next draw() call. 621 @Override 622 public void onFullScreenChanged(boolean full) { 623 ActivityBase.this.onFullScreenChanged(full); 624 } 625 626 @Override 627 public void requestRender() { 628 getGLRoot().requestRender(); 629 } 630 631 @Override 632 public void onPreviewTextureCopied() { 633 ActivityBase.this.onPreviewTextureCopied(); 634 } 635 636 @Override 637 public void setServer(Server s) { 638 mServer = s; 639 } 640 641 @Override 642 public boolean isPanorama() { 643 return ActivityBase.this.isPanoramaActivity(); 644 } 645 646 @Override 647 public boolean isStaticCamera() { 648 return !ApiHelper.HAS_SURFACE_TEXTURE; 649 } 650 651 private void setCameraRelativeFrame(Rect frame) { 652 if (mServer != null) mServer.setCameraRelativeFrame(frame); 653 } 654 655 private void switchWithCaptureAnimation(int offset) { 656 if (mServer != null) mServer.switchWithCaptureAnimation(offset); 657 } 658 659 private void setSwipingEnabled(boolean enabled) { 660 if (mServer != null) mServer.setSwipingEnabled(enabled); 661 } 662 663 private void notifyScreenNailChanged() { 664 if (mServer != null) mServer.notifyScreenNailChanged(); 665 } 666 } 667} 668