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