ActivityBase.java revision a029bd9976ebb618f321e8740c86b0f5d681d241
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 @Override 125 protected void onPause() { 126 super.onPause(); 127 if (LOGV) Log.v(TAG, "onPause"); 128 saveThumbnailToFile(); 129 130 if (mLoadThumbnailTask != null) { 131 mLoadThumbnailTask.cancel(true); 132 mLoadThumbnailTask = null; 133 } 134 135 if (mStorageHint != null) { 136 mStorageHint.cancel(); 137 mStorageHint = null; 138 } 139 } 140 141 @Override 142 public void setContentView(int layoutResID) { 143 super.setContentView(layoutResID); 144 // getActionBar() should be after setContentView 145 mActionBar = new GalleryActionBar(this); 146 mActionBar.hide(); 147 } 148 149 @Override 150 public boolean onSearchRequested() { 151 return false; 152 } 153 154 @Override 155 public boolean onKeyDown(int keyCode, KeyEvent event) { 156 // Prevent software keyboard or voice search from showing up. 157 if (keyCode == KeyEvent.KEYCODE_SEARCH 158 || keyCode == KeyEvent.KEYCODE_MENU) { 159 if (event.isLongPress()) return true; 160 } 161 162 return super.onKeyDown(keyCode, event); 163 } 164 165 protected void setResultEx(int resultCode) { 166 mResultCodeForTesting = resultCode; 167 setResult(resultCode); 168 } 169 170 protected void setResultEx(int resultCode, Intent data) { 171 mResultCodeForTesting = resultCode; 172 mResultDataForTesting = data; 173 setResult(resultCode, data); 174 } 175 176 public int getResultCode() { 177 return mResultCodeForTesting; 178 } 179 180 public Intent getResultData() { 181 return mResultDataForTesting; 182 } 183 184 @Override 185 protected void onDestroy() { 186 PopupManager.removeInstance(this); 187 super.onDestroy(); 188 } 189 190 @Override 191 public boolean onCreateOptionsMenu(Menu menu) { 192 super.onCreateOptionsMenu(menu); 193 return getStateManager().createOptionsMenu(menu); 194 } 195 196 protected void updateStorageHint(long storageSpace) { 197 String message = null; 198 if (storageSpace == Storage.UNAVAILABLE) { 199 message = getString(R.string.no_storage); 200 } else if (storageSpace == Storage.PREPARING) { 201 message = getString(R.string.preparing_sd); 202 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 203 message = getString(R.string.access_sd_fail); 204 } else if (storageSpace < Storage.LOW_STORAGE_THRESHOLD) { 205 message = getString(R.string.spaceIsLow_content); 206 } 207 208 if (message != null) { 209 if (mStorageHint == null) { 210 mStorageHint = OnScreenHint.makeText(this, message); 211 } else { 212 mStorageHint.setText(message); 213 } 214 mStorageHint.show(); 215 } else if (mStorageHint != null) { 216 mStorageHint.cancel(); 217 mStorageHint = null; 218 } 219 } 220 221 protected void updateThumbnailView() { 222 if (mThumbnail != null) { 223 mThumbnailView.setBitmap(mThumbnail.getBitmap()); 224 mThumbnailView.setVisibility(View.VISIBLE); 225 } else { 226 mThumbnailView.setBitmap(null); 227 mThumbnailView.setVisibility(View.GONE); 228 } 229 } 230 231 protected void getLastThumbnail() { 232 mThumbnail = ThumbnailHolder.getLastThumbnail(getContentResolver()); 233 // Suppose users tap the thumbnail view, go to the gallery, delete the 234 // image, and coming back to the camera. Thumbnail file will be invalid. 235 // Since the new thumbnail will be loaded in another thread later, the 236 // view should be set to gone to prevent from opening the invalid image. 237 updateThumbnailView(); 238 if (mThumbnail == null) { 239 mLoadThumbnailTask = new LoadThumbnailTask().execute(); 240 } 241 } 242 243 private class LoadThumbnailTask extends AsyncTask<Void, Void, Thumbnail> { 244 @Override 245 protected Thumbnail doInBackground(Void... params) { 246 // Load the thumbnail from the file. 247 ContentResolver resolver = getContentResolver(); 248 Thumbnail t = Thumbnail.getLastThumbnailFromFile(getFilesDir(), resolver); 249 250 if (isCancelled()) return null; 251 252 if (t == null) { 253 // Load the thumbnail from the media provider. 254 t = Thumbnail.getLastThumbnailFromContentResolver(resolver); 255 } 256 return t; 257 } 258 259 @Override 260 protected void onPostExecute(Thumbnail thumbnail) { 261 mThumbnail = thumbnail; 262 updateThumbnailView(); 263 } 264 } 265 266 protected void gotoGallery() { 267 // TODO: remove this check after panorama has swipe UI. 268 if (mAppBridge != null) { 269 // Move the next picture with capture animation. "1" means next. 270 mAppBridge.switchWithCaptureAnimation(1); 271 } else { 272 Util.viewUri(mThumbnail.getUri(), this); 273 } 274 } 275 276 protected void saveThumbnailToFile() { 277 if (mThumbnail != null && !mThumbnail.fromFile()) { 278 new SaveThumbnailTask().execute(mThumbnail); 279 } 280 } 281 282 private class SaveThumbnailTask extends AsyncTask<Thumbnail, Void, Void> { 283 @Override 284 protected Void doInBackground(Thumbnail... params) { 285 final int n = params.length; 286 final File filesDir = getFilesDir(); 287 for (int i = 0; i < n; i++) { 288 params[i].saveLastThumbnailToFile(filesDir); 289 } 290 return null; 291 } 292 } 293 294 // Call this after setContentView. 295 protected void createCameraScreenNail(boolean getPictures) { 296 mCameraAppView = findViewById(R.id.camera_app_root); 297 Bundle data = new Bundle(); 298 String path = "/local/all/"; 299 // Intent mode does not show camera roll. Use 0 as a work around for 300 // invalid bucket id. 301 // TODO: add support of empty media set in gallery. 302 path += (getPictures ? MediaSetUtils.CAMERA_BUCKET_ID : "0"); 303 data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path); 304 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path); 305 306 // Send an AppBridge to gallery to enable the camera preview. 307 mAppBridge = new MyAppBridge(); 308 data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge); 309 getStateManager().startState(PhotoPage.class, data); 310 mCameraScreenNail = mAppBridge.getCameraScreenNail(); 311 } 312 313 private class HideCameraAppView implements Runnable { 314 @Override 315 public void run() { 316 // We cannot set this as GONE because we want to receive the 317 // onLayoutChange() callback even when we are invisible. 318 mCameraAppView.setVisibility(View.INVISIBLE); 319 } 320 } 321 322 private void updateCameraAppView() { 323 if (mShowCameraAppView) { 324 mCameraAppView.setVisibility(View.VISIBLE); 325 // The "transparent region" is not recomputed when a sibling of 326 // SurfaceView changes visibility (unless it involves GONE). It's 327 // been broken since 1.0. Call requestLayout to work around it. 328 mCameraAppView.requestLayout(); 329 // withEndAction(null) prevents the pending end action 330 // mHideCameraAppView from being executed. 331 mCameraAppView.animate() 332 .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME) 333 .withLayer().alpha(1).withEndAction(null); 334 } else { 335 mCameraAppView.animate() 336 .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME) 337 .withLayer().alpha(0).withEndAction(mHideCameraAppView); 338 } 339 } 340 341 private void onFullScreenChanged(boolean full) { 342 if (mShowCameraAppView == full) return; 343 mShowCameraAppView = full; 344 if (mPaused || isFinishing()) return; 345 // Initialize the animation. 346 if (mHideCameraAppView == null) { 347 mHideCameraAppView = new HideCameraAppView(); 348 mCameraAppView.animate() 349 .setInterpolator(new DecelerateInterpolator()); 350 } 351 updateCameraAppView(); 352 } 353 354 @Override 355 public GalleryActionBar getGalleryActionBar() { 356 return mActionBar; 357 } 358 359 // Preview frame layout has changed. 360 @Override 361 public void onLayoutChange(View v, int left, int top, int right, int bottom, 362 int oldLeft, int oldTop, int oldRight, int oldBottom) { 363 if (mAppBridge == null) return; 364 365 if (left == oldLeft && top == oldTop && right == oldRight 366 && bottom == oldBottom) { 367 return; 368 } 369 370 // Find out the coordinates of the preview frame relative to GL 371 // root view. 372 View root = (View) getGLRoot(); 373 int[] rootLocation = new int[2]; 374 int[] viewLocation = new int[2]; 375 root.getLocationInWindow(rootLocation); 376 v.getLocationInWindow(viewLocation); 377 378 int l = viewLocation[0] - rootLocation[0]; 379 int t = viewLocation[1] - rootLocation[1]; 380 int r = l + (right - left); 381 int b = t + (bottom - top); 382 Rect frame = new Rect(l, t, r, b); 383 Log.d(TAG, "set CameraRelativeFrame as " + frame); 384 mAppBridge.setCameraRelativeFrame(frame); 385 } 386 387 protected void setSingleTapUpListener(View singleTapArea) { 388 mSingleTapArea = singleTapArea; 389 } 390 391 private boolean onSingleTapUp(int x, int y) { 392 // Ignore if listener is null or the camera control is invisible. 393 if (mSingleTapArea == null || !mShowCameraAppView) return false; 394 395 int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(), 396 mSingleTapArea); 397 x -= relativeLocation[0]; 398 y -= relativeLocation[1]; 399 if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0 400 && y < mSingleTapArea.getHeight()) { 401 onSingleTapUp(mSingleTapArea, x, y); 402 return true; 403 } 404 return false; 405 } 406 407 protected void onSingleTapUp(View view, int x, int y) { 408 } 409 410 protected void setSwipingEnabled(boolean enabled) { 411 mAppBridge.setSwipingEnabled(enabled); 412 } 413 414 protected void notifyScreenNailChanged() { 415 mAppBridge.notifyScreenNailChanged(); 416 } 417 418 protected void onPreviewTextureCopied() { 419 } 420 421 ////////////////////////////////////////////////////////////////////////// 422 // The is the communication interface between the Camera Application and 423 // the Gallery PhotoPage. 424 ////////////////////////////////////////////////////////////////////////// 425 426 class MyAppBridge extends AppBridge implements CameraScreenNail.Listener { 427 private CameraScreenNail mCameraScreenNail; 428 private Server mServer; 429 430 @Override 431 public ScreenNail attachScreenNail() { 432 if (mCameraScreenNail == null) { 433 mCameraScreenNail = new CameraScreenNail(this); 434 } 435 return mCameraScreenNail; 436 } 437 438 @Override 439 public void detachScreenNail() { 440 mCameraScreenNail = null; 441 } 442 443 public CameraScreenNail getCameraScreenNail() { 444 return mCameraScreenNail; 445 } 446 447 // Return true if the tap is consumed. 448 @Override 449 public boolean onSingleTapUp(int x, int y) { 450 return ActivityBase.this.onSingleTapUp(x, y); 451 } 452 453 // This is used to notify that the screen nail will be drawn in full screen 454 // or not in next draw() call. 455 @Override 456 public void onFullScreenChanged(boolean full) { 457 ActivityBase.this.onFullScreenChanged(full); 458 } 459 460 @Override 461 public void requestRender() { 462 getGLRoot().requestRender(); 463 } 464 465 @Override 466 public void onPreviewTextureCopied() { 467 ActivityBase.this.onPreviewTextureCopied(); 468 } 469 470 @Override 471 public void setServer(Server s) { 472 mServer = s; 473 } 474 475 private void setCameraRelativeFrame(Rect frame) { 476 if (mServer != null) mServer.setCameraRelativeFrame(frame); 477 } 478 479 private void switchWithCaptureAnimation(int offset) { 480 if (mServer != null) mServer.switchWithCaptureAnimation(offset); 481 } 482 483 private void setSwipingEnabled(boolean enabled) { 484 if (mServer != null) mServer.setSwipingEnabled(enabled); 485 } 486 487 private void notifyScreenNailChanged() { 488 if (mServer != null) mServer.notifyScreenNailChanged(); 489 } 490 } 491} 492