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