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