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