ActivityBase.java revision 24e4e6cc25b7628ef15eff703f70b2872575a4cc
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.hardware.Camera.Parameters; 25import android.os.AsyncTask; 26import android.os.Bundle; 27import android.util.Log; 28import android.view.animation.DecelerateInterpolator; 29import android.view.KeyEvent; 30import android.view.Menu; 31import android.view.View; 32import android.view.Window; 33import android.view.WindowManager; 34 35import com.android.camera.ui.PopupManager; 36import com.android.camera.ui.RotateImageView; 37import com.android.gallery3d.app.AbstractGalleryActivity; 38import com.android.gallery3d.app.PhotoPage; 39import com.android.gallery3d.app.PhotoPage.PageTapListener; 40import com.android.gallery3d.app.GalleryActionBar; 41import com.android.gallery3d.app.StateManager; 42import com.android.gallery3d.util.MediaSetUtils; 43 44import java.io.File; 45 46/** 47 * Superclass of Camera and VideoCamera activities. 48 */ 49abstract public class ActivityBase extends AbstractGalleryActivity 50 implements CameraScreenNail.PositionChangedListener, 51 View.OnLayoutChangeListener, PageTapListener { 52 53 private static final String TAG = "ActivityBase"; 54 private static boolean LOGV = false; 55 private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100; // milliseconds 56 private int mResultCodeForTesting; 57 private Intent mResultDataForTesting; 58 private OnScreenHint mStorageHint; 59 private UpdateCameraAppView mUpdateCameraAppView; 60 private HideCameraAppView mHideCameraAppView; 61 private View mSingleTapArea; 62 63 // The bitmap of the last captured picture thumbnail and the URI of the 64 // original picture. 65 protected Thumbnail mThumbnail; 66 // An imageview showing showing the last captured picture thumbnail. 67 protected RotateImageView mThumbnailView; 68 protected int mThumbnailViewWidth; // layout width of the thumbnail 69 protected AsyncTask<Void, Void, Thumbnail> mLoadThumbnailTask; 70 protected boolean mOpenCameraFail; 71 protected boolean mCameraDisabled; 72 protected CameraManager.CameraProxy mCameraDevice; 73 protected Parameters mParameters; 74 // The activity is paused. The classes that extend this class should set 75 // mPaused the first thing in onResume/onPause. 76 protected boolean mPaused; 77 protected GalleryActionBar mActionBar; 78 79 // multiple cameras support 80 protected int mNumberOfCameras; 81 protected int mCameraId; 82 83 protected CameraScreenNail mCameraScreenNail; // This shows camera preview. 84 // The view containing only camera related widgets like control panel, 85 // indicator bar, focus indicator and etc. 86 protected View mCameraAppView; 87 protected boolean mShowCameraAppView = true; 88 89 protected class CameraOpenThread extends Thread { 90 @Override 91 public void run() { 92 try { 93 mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId); 94 mParameters = mCameraDevice.getParameters(); 95 } catch (CameraHardwareException e) { 96 mOpenCameraFail = true; 97 } catch (CameraDisabledException e) { 98 mCameraDisabled = true; 99 } 100 } 101 } 102 103 @Override 104 public void onCreate(Bundle icicle) { 105 if (Util.isTabletUI()) { 106 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 107 } else { 108 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 109 } 110 requestWindowFeature(Window.FEATURE_ACTION_BAR); 111 requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); 112 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 113 mActionBar = new GalleryActionBar(this); 114 mActionBar.hide(); 115 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); 116 super.disableToggleStatusBar(); 117 super.onCreate(icicle); 118 } 119 120 @Override 121 protected void onPause() { 122 super.onPause(); 123 if (LOGV) Log.v(TAG, "onPause"); 124 saveThumbnailToFile(); 125 126 if (mLoadThumbnailTask != null) { 127 mLoadThumbnailTask.cancel(true); 128 mLoadThumbnailTask = null; 129 } 130 131 if (mStorageHint != null) { 132 mStorageHint.cancel(); 133 mStorageHint = null; 134 } 135 } 136 137 @Override 138 public boolean onSearchRequested() { 139 return false; 140 } 141 142 @Override 143 public boolean onKeyDown(int keyCode, KeyEvent event) { 144 // Prevent software keyboard or voice search from showing up. 145 if (keyCode == KeyEvent.KEYCODE_SEARCH 146 || keyCode == KeyEvent.KEYCODE_MENU) { 147 if (event.isLongPress()) return true; 148 } 149 150 return super.onKeyDown(keyCode, event); 151 } 152 153 protected void setResultEx(int resultCode) { 154 mResultCodeForTesting = resultCode; 155 setResult(resultCode); 156 } 157 158 protected void setResultEx(int resultCode, Intent data) { 159 mResultCodeForTesting = resultCode; 160 mResultDataForTesting = data; 161 setResult(resultCode, data); 162 } 163 164 public int getResultCode() { 165 return mResultCodeForTesting; 166 } 167 168 public Intent getResultData() { 169 return mResultDataForTesting; 170 } 171 172 @Override 173 protected void onDestroy() { 174 PopupManager.removeInstance(this); 175 super.onDestroy(); 176 } 177 178 @Override 179 public boolean onCreateOptionsMenu(Menu menu) { 180 super.onCreateOptionsMenu(menu); 181 return getStateManager().createOptionsMenu(menu); 182 } 183 184 protected void updateStorageHint(long storageSpace) { 185 String message = null; 186 if (storageSpace == Storage.UNAVAILABLE) { 187 message = getString(R.string.no_storage); 188 } else if (storageSpace == Storage.PREPARING) { 189 message = getString(R.string.preparing_sd); 190 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 191 message = getString(R.string.access_sd_fail); 192 } else if (storageSpace < Storage.LOW_STORAGE_THRESHOLD) { 193 message = getString(R.string.spaceIsLow_content); 194 } 195 196 if (message != null) { 197 if (mStorageHint == null) { 198 mStorageHint = OnScreenHint.makeText(this, message); 199 } else { 200 mStorageHint.setText(message); 201 } 202 mStorageHint.show(); 203 } else if (mStorageHint != null) { 204 mStorageHint.cancel(); 205 mStorageHint = null; 206 } 207 } 208 209 private void updateThumbnailView() { 210 if (mThumbnail != null) { 211 mThumbnailView.setBitmap(mThumbnail.getBitmap()); 212 mThumbnailView.setVisibility(View.VISIBLE); 213 } else { 214 mThumbnailView.setBitmap(null); 215 mThumbnailView.setVisibility(View.GONE); 216 } 217 } 218 219 protected void getLastThumbnail() { 220 mThumbnail = ThumbnailHolder.getLastThumbnail(getContentResolver()); 221 // Suppose users tap the thumbnail view, go to the gallery, delete the 222 // image, and coming back to the camera. Thumbnail file will be invalid. 223 // Since the new thumbnail will be loaded in another thread later, the 224 // view should be set to gone to prevent from opening the invalid image. 225 updateThumbnailView(); 226 if (mThumbnail == null) { 227 mLoadThumbnailTask = new LoadThumbnailTask().execute(); 228 } 229 } 230 231 private class LoadThumbnailTask extends AsyncTask<Void, Void, Thumbnail> { 232 @Override 233 protected Thumbnail doInBackground(Void... params) { 234 // Load the thumbnail from the file. 235 ContentResolver resolver = getContentResolver(); 236 Thumbnail t = Thumbnail.getLastThumbnailFromFile(getFilesDir(), resolver); 237 238 if (isCancelled()) return null; 239 240 if (t == null) { 241 // Load the thumbnail from the media provider. 242 t = Thumbnail.getLastThumbnailFromContentResolver(resolver); 243 } 244 return t; 245 } 246 247 @Override 248 protected void onPostExecute(Thumbnail thumbnail) { 249 mThumbnail = thumbnail; 250 updateThumbnailView(); 251 } 252 } 253 254 protected void gotoGallery() { 255 // TODO: remove this after panorama has swipe UI. 256 if (getStateManager().getStateCount() > 0) { 257 PhotoPage photoPage = (PhotoPage) getStateManager().getTopState(); 258 // Move the next picture with capture animation. "1" means next. 259 photoPage.switchWithCaptureAnimation(1); 260 } else { 261 Util.viewUri(mThumbnail.getUri(), this); 262 } 263 } 264 265 protected void saveThumbnailToFile() { 266 if (mThumbnail != null && !mThumbnail.fromFile()) { 267 new SaveThumbnailTask().execute(mThumbnail); 268 } 269 } 270 271 private class SaveThumbnailTask extends AsyncTask<Thumbnail, Void, Void> { 272 @Override 273 protected Void doInBackground(Thumbnail... params) { 274 final int n = params.length; 275 final File filesDir = getFilesDir(); 276 for (int i = 0; i < n; i++) { 277 params[i].saveLastThumbnailToFile(filesDir); 278 } 279 return null; 280 } 281 } 282 283 // Call this after setContentView. 284 protected void createCameraScreenNail(boolean getPictures) { 285 mCameraAppView = findViewById(R.id.camera_app_root); 286 Bundle data = new Bundle(); 287 String path = "/local/all/"; 288 // Intent mode does not show camera roll. Use 0 as a work around for 289 // invalid bucket id. 290 // TODO: add support of empty media set in gallery. 291 path += (getPictures ? MediaSetUtils.CAMERA_BUCKET_ID : "0"); 292 data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path); 293 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path); 294 295 // Send a CameraScreenNail to gallery to enable the camera preview. 296 CameraScreenNailHolder holder = new CameraScreenNailHolder(this); 297 data.putParcelable(PhotoPage.KEY_SCREENNAIL_HOLDER, holder); 298 getStateManager().startState(PhotoPage.class, data); 299 mCameraScreenNail = holder.getCameraScreenNail(); 300 mCameraScreenNail.setPositionChangedListener(this); 301 } 302 303 private class HideCameraAppView implements Runnable { 304 @Override 305 public void run() { 306 mCameraAppView.setVisibility(View.GONE); 307 } 308 } 309 private class UpdateCameraAppView implements Runnable { 310 @Override 311 public void run() { 312 if (mShowCameraAppView) { 313 mCameraAppView.setVisibility(View.VISIBLE); 314 mCameraAppView.animate() 315 .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME) 316 .withLayer().alpha(1); 317 } else { 318 mCameraAppView.animate() 319 .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME) 320 .withLayer().alpha(0).withEndAction(mHideCameraAppView); 321 } 322 } 323 } 324 325 @Override 326 public void onPositionChanged(int x, int y, int width, int height, boolean visible) { 327 if (!mPaused && !isFinishing()) { 328 View rootView = (View) getGLRoot(); 329 int rootWidth = rootView.getWidth(); 330 int rootHeight = rootView.getHeight(); 331 boolean showCameraAppView; 332 // Check if the camera preview is in the center. 333 if (visible && (x == 0 && width == rootWidth) || 334 (y == 0 && height == rootHeight && Math.abs(x - (rootWidth - width) / 2) <= 1)) { 335 showCameraAppView = true; 336 } else { 337 showCameraAppView = false; 338 } 339 340 if (mShowCameraAppView != showCameraAppView) { 341 mShowCameraAppView = showCameraAppView; 342 // Initialize the animation. 343 if (mUpdateCameraAppView == null) { 344 mUpdateCameraAppView = new UpdateCameraAppView(); 345 mHideCameraAppView = new HideCameraAppView(); 346 mCameraAppView.animate() 347 .setInterpolator(new DecelerateInterpolator()); 348 } 349 runOnUiThread(mUpdateCameraAppView); 350 } 351 } 352 } 353 354 @Override 355 public GalleryActionBar getGalleryActionBar() { 356 return mActionBar; 357 } 358 359 // Preview frame layout has changed. Move the preview to the center of the 360 // layout. 361 @Override 362 public void onLayoutChange(View v, int left, int top, int right, int bottom, 363 int oldLeft, int oldTop, int oldRight, int oldBottom) { 364 // Find out the left and top of the preview frame layout relative to GL 365 // root view. 366 View root = (View) getGLRoot(); 367 int[] rootLocation = new int[2]; 368 int[] viewLocation = new int[2]; 369 root.getLocationInWindow(rootLocation); 370 v.getLocationInWindow(viewLocation); 371 int relativeLeft = viewLocation[0] - rootLocation[0]; 372 int relativeTop = viewLocation[1] - rootLocation[1]; 373 374 // Calculate the scale ratio between preview frame layout and GL root 375 // view. 376 int width = root.getWidth(); 377 int height = root.getHeight(); 378 float scale = Math.max((float) (right - left) / width, 379 (float) (bottom - top) / height); 380 float scalePx = width / 2f; 381 float scalePy = height / 2f; 382 383 // Calculate the translate distance. 384 float translateX = relativeLeft + (right - left - width) / 2f; 385 float translateY = relativeTop + (bottom - top - height) / 2f; 386 387 mCameraScreenNail.setMatrix(scale, scalePx, scalePy, translateX, translateY); 388 } 389 390 protected void setSingleTapUpListener(View singleTapArea) { 391 PhotoPage photoPage = (PhotoPage) getStateManager().getTopState(); 392 photoPage.setPageTapListener(this); 393 mSingleTapArea = singleTapArea; 394 } 395 396 // Single tap up from PhotoPage. 397 @Override 398 public boolean onSingleTapUp(int x, int y) { 399 // Camera control is invisible. Ignore. 400 if (!mShowCameraAppView) return false; 401 402 int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(), 403 mSingleTapArea); 404 x -= relativeLocation[0]; 405 y -= relativeLocation[1]; 406 if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0 407 && y < mSingleTapArea.getHeight()) { 408 onSingleTapUp(mSingleTapArea, x, y); 409 return true; 410 } 411 return false; 412 } 413 414 protected void onSingleTapUp(View view, int x, int y) { 415 } 416} 417