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