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 17 package com.android.camera; 18 19 import android.content.BroadcastReceiver; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.graphics.Rect; 25 import android.hardware.Camera.Parameters; 26 import android.os.AsyncTask; 27 import android.os.Bundle; 28 import android.support.v4.content.LocalBroadcastManager; 29 import android.util.Log; 30 import android.view.KeyEvent; 31 import android.view.Menu; 32 import android.view.View; 33 import android.view.Window; 34 import android.view.WindowManager; 35 import android.view.animation.DecelerateInterpolator; 36 37 import com.android.camera.ui.CameraPicker; 38 import com.android.camera.ui.PopupManager; 39 import com.android.camera.ui.RotateImageView; 40 import com.android.gallery3d.app.AbstractGalleryActivity; 41 import com.android.gallery3d.app.AppBridge; 42 import com.android.gallery3d.app.GalleryActionBar; 43 import com.android.gallery3d.app.PhotoPage; 44 import com.android.gallery3d.ui.ScreenNail; 45 import com.android.gallery3d.util.MediaSetUtils; 46 47 import java.io.File; 48 49 /** 50 * Superclass of Camera and VideoCamera activities. 51 */ 52 abstract 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 run()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 onCreate(Bundle icicle)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 isPanoramaActivity()144 public boolean isPanoramaActivity() { 145 return false; 146 } 147 148 @Override onResume()149 protected void onResume() { 150 super.onResume(); 151 LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this); 152 manager.registerReceiver(mDeletePictureReceiver, mDeletePictureFilter); 153 } 154 155 @Override onPause()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 setContentView(int layoutResID)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 onSearchRequested()184 public boolean onSearchRequested() { 185 return false; 186 } 187 188 @Override onKeyDown(int keyCode, KeyEvent event)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 setResultEx(int resultCode)199 protected void setResultEx(int resultCode) { 200 mResultCodeForTesting = resultCode; 201 setResult(resultCode); 202 } 203 setResultEx(int resultCode, Intent data)204 protected void setResultEx(int resultCode, Intent data) { 205 mResultCodeForTesting = resultCode; 206 mResultDataForTesting = data; 207 setResult(resultCode, data); 208 } 209 getResultCode()210 public int getResultCode() { 211 return mResultCodeForTesting; 212 } 213 getResultData()214 public Intent getResultData() { 215 return mResultDataForTesting; 216 } 217 218 @Override onDestroy()219 protected void onDestroy() { 220 PopupManager.removeInstance(this); 221 super.onDestroy(); 222 } 223 224 @Override onCreateOptionsMenu(Menu menu)225 public boolean onCreateOptionsMenu(Menu menu) { 226 super.onCreateOptionsMenu(menu); 227 return getStateManager().createOptionsMenu(menu); 228 } 229 updateStorageHint(long storageSpace)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 updateThumbnailView()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 getLastThumbnail()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 getLastThumbnailUncached()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 LoadThumbnailTask(boolean lookAtCache)285 public LoadThumbnailTask(boolean lookAtCache) { 286 mLookAtCache = lookAtCache; 287 } 288 289 @Override doInBackground(Void... params)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 onPostExecute(Thumbnail thumbnail)319 protected void onPostExecute(Thumbnail thumbnail) { 320 if (isCancelled()) return; 321 mThumbnail = thumbnail; 322 updateThumbnailView(); 323 } 324 } 325 gotoGallery()326 protected void gotoGallery() { 327 // Move the next picture with capture animation. "1" means next. 328 mAppBridge.switchWithCaptureAnimation(1); 329 } 330 saveThumbnailToFile()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 doInBackground(Thumbnail... params)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. createCameraScreenNail(boolean getPictures)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 run()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 updateCameraAppView()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 onFullScreenChanged(boolean full)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 getGalleryActionBar()417 public GalleryActionBar getGalleryActionBar() { 418 return mActionBar; 419 } 420 421 // Preview frame layout has changed. 422 @Override onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)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 setSingleTapUpListener(View singleTapArea)460 protected void setSingleTapUpListener(View singleTapArea) { 461 mSingleTapArea = singleTapArea; 462 } 463 onSingleTapUp(int x, int y)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 onSingleTapUp(View view, int x, int y)480 protected void onSingleTapUp(View view, int x, int y) { 481 } 482 setSwipingEnabled(boolean enabled)483 protected void setSwipingEnabled(boolean enabled) { 484 mAppBridge.setSwipingEnabled(enabled); 485 } 486 notifyScreenNailChanged()487 protected void notifyScreenNailChanged() { 488 mAppBridge.notifyScreenNailChanged(); 489 } 490 onPreviewTextureCopied()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 attachScreenNail()504 public ScreenNail attachScreenNail() { 505 if (mCameraScreenNail == null) { 506 mCameraScreenNail = new CameraScreenNail(this); 507 } 508 return mCameraScreenNail; 509 } 510 511 @Override detachScreenNail()512 public void detachScreenNail() { 513 mCameraScreenNail = null; 514 } 515 getCameraScreenNail()516 public CameraScreenNail getCameraScreenNail() { 517 return mCameraScreenNail; 518 } 519 520 // Return true if the tap is consumed. 521 @Override onSingleTapUp(int x, int y)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 onFullScreenChanged(boolean full)529 public void onFullScreenChanged(boolean full) { 530 ActivityBase.this.onFullScreenChanged(full); 531 } 532 533 @Override requestRender()534 public void requestRender() { 535 getGLRoot().requestRender(); 536 } 537 538 @Override onPreviewTextureCopied()539 public void onPreviewTextureCopied() { 540 ActivityBase.this.onPreviewTextureCopied(); 541 } 542 543 @Override setServer(Server s)544 public void setServer(Server s) { 545 mServer = s; 546 } 547 548 @Override isPanorama()549 public boolean isPanorama() { 550 return ActivityBase.this.isPanoramaActivity(); 551 } 552 setCameraRelativeFrame(Rect frame)553 private void setCameraRelativeFrame(Rect frame) { 554 if (mServer != null) mServer.setCameraRelativeFrame(frame); 555 } 556 switchWithCaptureAnimation(int offset)557 private void switchWithCaptureAnimation(int offset) { 558 if (mServer != null) mServer.switchWithCaptureAnimation(offset); 559 } 560 setSwipingEnabled(boolean enabled)561 private void setSwipingEnabled(boolean enabled) { 562 if (mServer != null) mServer.setSwipingEnabled(enabled); 563 } 564 notifyScreenNailChanged()565 private void notifyScreenNailChanged() { 566 if (mServer != null) mServer.notifyScreenNailChanged(); 567 } 568 } 569 } 570