ActivityBase.java revision 252da3c55a35c77fa4be4d51f79dac4aaf7cdb77
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 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 private 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 // Find out the coordinates of the preview frame relative to GL 370 // root view. 371 View root = (View) getGLRoot(); 372 int[] rootLocation = new int[2]; 373 int[] viewLocation = new int[2]; 374 root.getLocationInWindow(rootLocation); 375 v.getLocationInWindow(viewLocation); 376 377 int l = viewLocation[0] - rootLocation[0]; 378 int t = viewLocation[1] - rootLocation[1]; 379 int r = l + (right - left); 380 int b = t + (bottom - top); 381 Rect frame = new Rect(l, t, r, b); 382 Log.d(TAG, "set CameraRelativeFrame as " + frame); 383 mAppBridge.setCameraRelativeFrame(frame); 384 } 385 386 protected void setSingleTapUpListener(View singleTapArea) { 387 mSingleTapArea = singleTapArea; 388 } 389 390 private boolean onSingleTapUp(int x, int y) { 391 // Ignore if listener is null or the camera control is invisible. 392 if (mSingleTapArea == null || !mShowCameraAppView) return false; 393 394 int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(), 395 mSingleTapArea); 396 x -= relativeLocation[0]; 397 y -= relativeLocation[1]; 398 if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0 399 && y < mSingleTapArea.getHeight()) { 400 onSingleTapUp(mSingleTapArea, x, y); 401 return true; 402 } 403 return false; 404 } 405 406 protected void onSingleTapUp(View view, int x, int y) { 407 } 408 409 protected void setSwipingEnabled(boolean enabled) { 410 mAppBridge.setSwipingEnabled(enabled); 411 } 412 413 protected void notifyScreenNailChanged() { 414 mAppBridge.notifyScreenNailChanged(); 415 } 416 417 protected void onPreviewTextureCopied() { 418 } 419 420 ////////////////////////////////////////////////////////////////////////// 421 // The is the communication interface between the Camera Application and 422 // the Gallery PhotoPage. 423 ////////////////////////////////////////////////////////////////////////// 424 425 class MyAppBridge extends AppBridge implements CameraScreenNail.Listener { 426 private CameraScreenNail mCameraScreenNail; 427 private Server mServer; 428 429 @Override 430 public ScreenNail attachScreenNail() { 431 if (mCameraScreenNail == null) { 432 mCameraScreenNail = new CameraScreenNail(this); 433 } 434 return mCameraScreenNail; 435 } 436 437 @Override 438 public void detachScreenNail() { 439 mCameraScreenNail = null; 440 } 441 442 public CameraScreenNail getCameraScreenNail() { 443 return mCameraScreenNail; 444 } 445 446 // Return true if the tap is consumed. 447 @Override 448 public boolean onSingleTapUp(int x, int y) { 449 return ActivityBase.this.onSingleTapUp(x, y); 450 } 451 452 // This is used to notify that the screen nail will be drawn in full screen 453 // or not in next draw() call. 454 @Override 455 public void onFullScreenChanged(boolean full) { 456 ActivityBase.this.onFullScreenChanged(full); 457 } 458 459 @Override 460 public void requestRender() { 461 getGLRoot().requestRender(); 462 } 463 464 @Override 465 public void onPreviewTextureCopied() { 466 ActivityBase.this.onPreviewTextureCopied(); 467 } 468 469 @Override 470 public void setServer(Server s) { 471 mServer = s; 472 } 473 474 @Override 475 public boolean isPanorama() { 476 return ActivityBase.this.isPanoramaActivity(); 477 } 478 479 private void setCameraRelativeFrame(Rect frame) { 480 if (mServer != null) mServer.setCameraRelativeFrame(frame); 481 } 482 483 private void switchWithCaptureAnimation(int offset) { 484 if (mServer != null) mServer.switchWithCaptureAnimation(offset); 485 } 486 487 private void setSwipingEnabled(boolean enabled) { 488 if (mServer != null) mServer.setSwipingEnabled(enabled); 489 } 490 491 private void notifyScreenNailChanged() { 492 if (mServer != null) mServer.notifyScreenNailChanged(); 493 } 494 } 495} 496