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