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