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.Context; 21import android.content.Intent; 22import android.content.IntentFilter; 23import android.graphics.Bitmap; 24import android.graphics.BitmapFactory; 25import android.graphics.Rect; 26import android.hardware.Camera.Parameters; 27import android.net.Uri; 28import android.os.Bundle; 29import android.os.Handler; 30import android.os.Message; 31import android.view.KeyEvent; 32import android.view.View; 33import android.view.Window; 34import android.view.WindowManager; 35import android.view.animation.AlphaAnimation; 36import android.view.animation.Animation; 37import android.view.animation.DecelerateInterpolator; 38 39import com.android.camera.ui.LayoutChangeNotifier; 40import com.android.camera.ui.PopupManager; 41import com.android.gallery3d.app.AbstractGalleryActivity; 42import com.android.gallery3d.app.AppBridge; 43import com.android.gallery3d.app.GalleryActionBar; 44import com.android.gallery3d.app.PhotoPage; 45import com.android.gallery3d.common.ApiHelper; 46import com.android.gallery3d.ui.ScreenNail; 47import com.android.gallery3d.util.MediaSetUtils; 48 49/** 50 * Superclass of camera activity. 51 */ 52public abstract class ActivityBase extends AbstractGalleryActivity 53 implements LayoutChangeNotifier.Listener { 54 55 private static final String TAG = "ActivityBase"; 56 private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100; // milliseconds 57 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 58 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 59 public static final String ACTION_IMAGE_CAPTURE_SECURE = 60 "android.media.action.IMAGE_CAPTURE_SECURE"; 61 // The intent extra for camera from secure lock screen. True if the gallery 62 // should only show newly captured pictures. sSecureAlbumId does not 63 // increment. This is used when switching between camera, camcorder, and 64 // panorama. If the extra is not set, it is in the normal camera mode. 65 public static final String SECURE_CAMERA_EXTRA = "secure_camera"; 66 67 private int mResultCodeForTesting; 68 private Intent mResultDataForTesting; 69 private OnScreenHint mStorageHint; 70 private View mSingleTapArea; 71 72 protected boolean mOpenCameraFail; 73 protected boolean mCameraDisabled; 74 protected CameraManager.CameraProxy mCameraDevice; 75 protected Parameters mParameters; 76 // The activity is paused. The classes that extend this class should set 77 // mPaused the first thing in onResume/onPause. 78 protected boolean mPaused; 79 protected GalleryActionBar mActionBar; 80 81 // multiple cameras support 82 protected int mNumberOfCameras; 83 protected int mCameraId; 84 // The activity is going to switch to the specified camera id. This is 85 // needed because texture copy is done in GL thread. -1 means camera is not 86 // switching. 87 protected int mPendingSwitchCameraId = -1; 88 89 protected MyAppBridge mAppBridge; 90 protected ScreenNail mCameraScreenNail; // This shows camera preview. 91 // The view containing only camera related widgets like control panel, 92 // indicator bar, focus indicator and etc. 93 protected View mCameraAppView; 94 protected boolean mShowCameraAppView = true; 95 private Animation mCameraAppViewFadeIn; 96 private Animation mCameraAppViewFadeOut; 97 // Secure album id. This should be incremented every time the camera is 98 // launched from the secure lock screen. The id should be the same when 99 // switching between camera, camcorder, and panorama. 100 protected static int sSecureAlbumId; 101 // True if the camera is started from secure lock screen. 102 protected boolean mSecureCamera; 103 private static boolean sFirstStartAfterScreenOn = true; 104 105 private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD; 106 private static final int UPDATE_STORAGE_HINT = 0; 107 private final Handler mHandler = new Handler() { 108 @Override 109 public void handleMessage(Message msg) { 110 switch (msg.what) { 111 case UPDATE_STORAGE_HINT: 112 updateStorageHint(); 113 return; 114 } 115 } 116 }; 117 118 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 119 @Override 120 public void onReceive(Context context, Intent intent) { 121 String action = intent.getAction(); 122 if (action.equals(Intent.ACTION_MEDIA_MOUNTED) 123 || action.equals(Intent.ACTION_MEDIA_UNMOUNTED) 124 || action.equals(Intent.ACTION_MEDIA_CHECKING) 125 || action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { 126 updateStorageSpaceAndHint(); 127 } 128 } 129 }; 130 131 // close activity when screen turns off 132 private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 133 @Override 134 public void onReceive(Context context, Intent intent) { 135 finish(); 136 } 137 }; 138 139 private static BroadcastReceiver sScreenOffReceiver; 140 private static class ScreenOffReceiver extends BroadcastReceiver { 141 @Override 142 public void onReceive(Context context, Intent intent) { 143 sFirstStartAfterScreenOn = true; 144 } 145 } 146 147 public static boolean isFirstStartAfterScreenOn() { 148 return sFirstStartAfterScreenOn; 149 } 150 151 public static void resetFirstStartAfterScreenOn() { 152 sFirstStartAfterScreenOn = false; 153 } 154 155 protected class CameraOpenThread extends Thread { 156 @Override 157 public void run() { 158 try { 159 mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId); 160 mParameters = mCameraDevice.getParameters(); 161 } catch (CameraHardwareException e) { 162 mOpenCameraFail = true; 163 } catch (CameraDisabledException e) { 164 mCameraDisabled = true; 165 } 166 } 167 } 168 169 @Override 170 public void onCreate(Bundle icicle) { 171 super.disableToggleStatusBar(); 172 // Set a theme with action bar. It is not specified in manifest because 173 // we want to hide it by default. setTheme must happen before 174 // setContentView. 175 // 176 // This must be set before we call super.onCreate(), where the window's 177 // background is removed. 178 setTheme(R.style.Theme_Gallery); 179 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 180 if (ApiHelper.HAS_ACTION_BAR) { 181 requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); 182 } else { 183 requestWindowFeature(Window.FEATURE_NO_TITLE); 184 } 185 186 // Check if this is in the secure camera mode. 187 Intent intent = getIntent(); 188 String action = intent.getAction(); 189 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) { 190 mSecureCamera = true; 191 // Use a new album when this is started from the lock screen. 192 sSecureAlbumId++; 193 } else if (ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 194 mSecureCamera = true; 195 } else { 196 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 197 } 198 if (mSecureCamera) { 199 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); 200 registerReceiver(mScreenOffReceiver, filter); 201 if (sScreenOffReceiver == null) { 202 sScreenOffReceiver = new ScreenOffReceiver(); 203 getApplicationContext().registerReceiver(sScreenOffReceiver, filter); 204 } 205 } 206 super.onCreate(icicle); 207 } 208 209 public boolean isPanoramaActivity() { 210 return false; 211 } 212 213 @Override 214 protected void onResume() { 215 super.onResume(); 216 217 installIntentFilter(); 218 if (updateStorageHintOnResume()) { 219 updateStorageSpace(); 220 mHandler.sendEmptyMessageDelayed(UPDATE_STORAGE_HINT, 200); 221 } 222 } 223 224 @Override 225 protected void onPause() { 226 super.onPause(); 227 228 if (mStorageHint != null) { 229 mStorageHint.cancel(); 230 mStorageHint = null; 231 } 232 233 unregisterReceiver(mReceiver); 234 } 235 236 @Override 237 public void setContentView(int layoutResID) { 238 super.setContentView(layoutResID); 239 // getActionBar() should be after setContentView 240 mActionBar = new GalleryActionBar(this); 241 mActionBar.hide(); 242 } 243 244 @Override 245 public boolean onSearchRequested() { 246 return false; 247 } 248 249 @Override 250 public boolean onKeyDown(int keyCode, KeyEvent event) { 251 // Prevent software keyboard or voice search from showing up. 252 if (keyCode == KeyEvent.KEYCODE_SEARCH 253 || keyCode == KeyEvent.KEYCODE_MENU) { 254 if (event.isLongPress()) return true; 255 } 256 if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) { 257 return true; 258 } 259 260 return super.onKeyDown(keyCode, event); 261 } 262 263 @Override 264 public boolean onKeyUp(int keyCode, KeyEvent event) { 265 if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) { 266 return true; 267 } 268 return super.onKeyUp(keyCode, event); 269 } 270 271 protected void setResultEx(int resultCode) { 272 mResultCodeForTesting = resultCode; 273 setResult(resultCode); 274 } 275 276 protected void setResultEx(int resultCode, Intent data) { 277 mResultCodeForTesting = resultCode; 278 mResultDataForTesting = data; 279 setResult(resultCode, data); 280 } 281 282 public int getResultCode() { 283 return mResultCodeForTesting; 284 } 285 286 public Intent getResultData() { 287 return mResultDataForTesting; 288 } 289 290 @Override 291 protected void onDestroy() { 292 PopupManager.removeInstance(this); 293 if (mSecureCamera) unregisterReceiver(mScreenOffReceiver); 294 super.onDestroy(); 295 } 296 297 protected void installIntentFilter() { 298 // install an intent filter to receive SD card related events. 299 IntentFilter intentFilter = 300 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); 301 intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 302 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 303 intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING); 304 intentFilter.addDataScheme("file"); 305 registerReceiver(mReceiver, intentFilter); 306 } 307 308 protected void updateStorageSpace() { 309 mStorageSpace = Storage.getAvailableSpace(); 310 } 311 312 protected long getStorageSpace() { 313 return mStorageSpace; 314 } 315 316 protected void updateStorageSpaceAndHint() { 317 updateStorageSpace(); 318 updateStorageHint(mStorageSpace); 319 } 320 321 protected void updateStorageHint() { 322 updateStorageHint(mStorageSpace); 323 } 324 325 protected boolean updateStorageHintOnResume() { 326 return true; 327 } 328 329 protected void updateStorageHint(long storageSpace) { 330 String message = null; 331 if (storageSpace == Storage.UNAVAILABLE) { 332 message = getString(R.string.no_storage); 333 } else if (storageSpace == Storage.PREPARING) { 334 message = getString(R.string.preparing_sd); 335 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 336 message = getString(R.string.access_sd_fail); 337 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) { 338 message = getString(R.string.spaceIsLow_content); 339 } 340 341 if (message != null) { 342 if (mStorageHint == null) { 343 mStorageHint = OnScreenHint.makeText(this, message); 344 } else { 345 mStorageHint.setText(message); 346 } 347 mStorageHint.show(); 348 } else if (mStorageHint != null) { 349 mStorageHint.cancel(); 350 mStorageHint = null; 351 } 352 } 353 354 protected void gotoGallery() { 355 // Move the next picture with capture animation. "1" means next. 356 mAppBridge.switchWithCaptureAnimation(1); 357 } 358 359 // Call this after setContentView. 360 public ScreenNail createCameraScreenNail(boolean getPictures) { 361 mCameraAppView = findViewById(R.id.camera_app_root); 362 Bundle data = new Bundle(); 363 String path; 364 if (getPictures) { 365 if (mSecureCamera) { 366 path = "/secure/all/" + sSecureAlbumId; 367 } else { 368 path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID; 369 } 370 } else { 371 path = "/local/all/0"; // Use 0 so gallery does not show anything. 372 } 373 data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path); 374 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path); 375 data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera); 376 377 // Send an AppBridge to gallery to enable the camera preview. 378 if (mAppBridge != null) { 379 mCameraScreenNail.recycle(); 380 } 381 mAppBridge = new MyAppBridge(); 382 data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge); 383 if (getStateManager().getStateCount() == 0) { 384 getStateManager().startState(PhotoPage.class, data); 385 } else { 386 getStateManager().switchState(getStateManager().getTopState(), 387 PhotoPage.class, data); 388 } 389 mCameraScreenNail = mAppBridge.getCameraScreenNail(); 390 return mCameraScreenNail; 391 } 392 393 // Call this after setContentView. 394 protected ScreenNail reuseCameraScreenNail(boolean getPictures) { 395 mCameraAppView = findViewById(R.id.camera_app_root); 396 Bundle data = new Bundle(); 397 String path; 398 if (getPictures) { 399 if (mSecureCamera) { 400 path = "/secure/all/" + sSecureAlbumId; 401 } else { 402 path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID; 403 } 404 } else { 405 path = "/local/all/0"; // Use 0 so gallery does not show anything. 406 } 407 data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path); 408 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path); 409 data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera); 410 411 // Send an AppBridge to gallery to enable the camera preview. 412 if (mAppBridge == null) { 413 mAppBridge = new MyAppBridge(); 414 } 415 data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge); 416 if (getStateManager().getStateCount() == 0) { 417 getStateManager().startState(PhotoPage.class, data); 418 } 419 mCameraScreenNail = mAppBridge.getCameraScreenNail(); 420 return mCameraScreenNail; 421 } 422 423 private class HideCameraAppView implements Animation.AnimationListener { 424 @Override 425 public void onAnimationEnd(Animation animation) { 426 // We cannot set this as GONE because we want to receive the 427 // onLayoutChange() callback even when we are invisible. 428 mCameraAppView.setVisibility(View.INVISIBLE); 429 } 430 431 @Override 432 public void onAnimationRepeat(Animation animation) { 433 } 434 435 @Override 436 public void onAnimationStart(Animation animation) { 437 } 438 } 439 440 protected void updateCameraAppView() { 441 // Initialize the animation. 442 if (mCameraAppViewFadeIn == null) { 443 mCameraAppViewFadeIn = new AlphaAnimation(0f, 1f); 444 mCameraAppViewFadeIn.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME); 445 mCameraAppViewFadeIn.setInterpolator(new DecelerateInterpolator()); 446 447 mCameraAppViewFadeOut = new AlphaAnimation(1f, 0f); 448 mCameraAppViewFadeOut.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME); 449 mCameraAppViewFadeOut.setInterpolator(new DecelerateInterpolator()); 450 mCameraAppViewFadeOut.setAnimationListener(new HideCameraAppView()); 451 } 452 453 if (mShowCameraAppView) { 454 mCameraAppView.setVisibility(View.VISIBLE); 455 // The "transparent region" is not recomputed when a sibling of 456 // SurfaceView changes visibility (unless it involves GONE). It's 457 // been broken since 1.0. Call requestLayout to work around it. 458 mCameraAppView.requestLayout(); 459 mCameraAppView.startAnimation(mCameraAppViewFadeIn); 460 } else { 461 mCameraAppView.startAnimation(mCameraAppViewFadeOut); 462 } 463 } 464 465 protected void onFullScreenChanged(boolean full) { 466 if (mShowCameraAppView == full) return; 467 mShowCameraAppView = full; 468 if (mPaused || isFinishing()) return; 469 updateCameraAppView(); 470 } 471 472 @Override 473 public GalleryActionBar getGalleryActionBar() { 474 return mActionBar; 475 } 476 477 // Preview frame layout has changed. 478 @Override 479 public void onLayoutChange(View v, int left, int top, int right, int bottom) { 480 if (mAppBridge == null) return; 481 482 int width = right - left; 483 int height = bottom - top; 484 if (ApiHelper.HAS_SURFACE_TEXTURE) { 485 CameraScreenNail screenNail = (CameraScreenNail) mCameraScreenNail; 486 if (Util.getDisplayRotation(this) % 180 == 0) { 487 screenNail.setPreviewFrameLayoutSize(width, height); 488 } else { 489 // Swap the width and height. Camera screen nail draw() is based on 490 // natural orientation, not the view system orientation. 491 screenNail.setPreviewFrameLayoutSize(height, width); 492 } 493 notifyScreenNailChanged(); 494 } 495 } 496 497 protected void setSingleTapUpListener(View singleTapArea) { 498 mSingleTapArea = singleTapArea; 499 } 500 501 private boolean onSingleTapUp(int x, int y) { 502 // Ignore if listener is null or the camera control is invisible. 503 if (mSingleTapArea == null || !mShowCameraAppView) return false; 504 505 int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(), 506 mSingleTapArea); 507 x -= relativeLocation[0]; 508 y -= relativeLocation[1]; 509 if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0 510 && y < mSingleTapArea.getHeight()) { 511 onSingleTapUp(mSingleTapArea, x, y); 512 return true; 513 } 514 return false; 515 } 516 517 protected void onSingleTapUp(View view, int x, int y) { 518 } 519 520 public void setSwipingEnabled(boolean enabled) { 521 mAppBridge.setSwipingEnabled(enabled); 522 } 523 524 public void notifyScreenNailChanged() { 525 mAppBridge.notifyScreenNailChanged(); 526 } 527 528 protected void onPreviewTextureCopied() { 529 } 530 531 protected void onCaptureTextureCopied() { 532 } 533 534 protected void addSecureAlbumItemIfNeeded(boolean isVideo, Uri uri) { 535 if (mSecureCamera) { 536 int id = Integer.parseInt(uri.getLastPathSegment()); 537 mAppBridge.addSecureAlbumItem(isVideo, id); 538 } 539 } 540 541 public boolean isSecureCamera() { 542 return mSecureCamera; 543 } 544 545 ////////////////////////////////////////////////////////////////////////// 546 // The is the communication interface between the Camera Application and 547 // the Gallery PhotoPage. 548 ////////////////////////////////////////////////////////////////////////// 549 550 class MyAppBridge extends AppBridge implements CameraScreenNail.Listener { 551 @SuppressWarnings("hiding") 552 private ScreenNail mCameraScreenNail; 553 private Server mServer; 554 555 @Override 556 public ScreenNail attachScreenNail() { 557 if (mCameraScreenNail == null) { 558 if (ApiHelper.HAS_SURFACE_TEXTURE) { 559 mCameraScreenNail = new CameraScreenNail(this); 560 } else { 561 Bitmap b = BitmapFactory.decodeResource(getResources(), 562 R.drawable.wallpaper_picker_preview); 563 mCameraScreenNail = new StaticBitmapScreenNail(b); 564 } 565 } 566 return mCameraScreenNail; 567 } 568 569 @Override 570 public void detachScreenNail() { 571 mCameraScreenNail = null; 572 } 573 574 public ScreenNail getCameraScreenNail() { 575 return mCameraScreenNail; 576 } 577 578 // Return true if the tap is consumed. 579 @Override 580 public boolean onSingleTapUp(int x, int y) { 581 return ActivityBase.this.onSingleTapUp(x, y); 582 } 583 584 // This is used to notify that the screen nail will be drawn in full screen 585 // or not in next draw() call. 586 @Override 587 public void onFullScreenChanged(boolean full) { 588 ActivityBase.this.onFullScreenChanged(full); 589 } 590 591 @Override 592 public void requestRender() { 593 getGLRoot().requestRenderForced(); 594 } 595 596 @Override 597 public void onPreviewTextureCopied() { 598 ActivityBase.this.onPreviewTextureCopied(); 599 } 600 601 @Override 602 public void onCaptureTextureCopied() { 603 ActivityBase.this.onCaptureTextureCopied(); 604 } 605 606 @Override 607 public void setServer(Server s) { 608 mServer = s; 609 } 610 611 @Override 612 public boolean isPanorama() { 613 return ActivityBase.this.isPanoramaActivity(); 614 } 615 616 @Override 617 public boolean isStaticCamera() { 618 return !ApiHelper.HAS_SURFACE_TEXTURE; 619 } 620 621 public void addSecureAlbumItem(boolean isVideo, int id) { 622 if (mServer != null) mServer.addSecureAlbumItem(isVideo, id); 623 } 624 625 private void setCameraRelativeFrame(Rect frame) { 626 if (mServer != null) mServer.setCameraRelativeFrame(frame); 627 } 628 629 private void switchWithCaptureAnimation(int offset) { 630 if (mServer != null) mServer.switchWithCaptureAnimation(offset); 631 } 632 633 private void setSwipingEnabled(boolean enabled) { 634 if (mServer != null) mServer.setSwipingEnabled(enabled); 635 } 636 637 private void notifyScreenNailChanged() { 638 if (mServer != null) mServer.notifyScreenNailChanged(); 639 } 640 } 641} 642