PageRenderTime 59ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/Magnifier/src/com/ideal/magnifier/MagnifierActivity.java

http://eyes-free.googlecode.com/
Java | 581 lines | 363 code | 51 blank | 167 comment | 48 complexity | 2c08e988b2f33077cf4fced0780d2992 MD5 | raw file
Possible License(s): GPL-3.0, Apache-2.0
  1. /*
  2. * Copyright (C) 2010 The IDEAL Group
  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. package com.ideal.magnifier;
  17. import android.app.Activity;
  18. import android.app.AlertDialog;
  19. import android.content.ActivityNotFoundException;
  20. import android.content.Context;
  21. import android.content.DialogInterface;
  22. import android.content.Intent;
  23. import android.content.SharedPreferences;
  24. import android.graphics.Bitmap;
  25. import android.graphics.BitmapFactory;
  26. import android.graphics.ImageFormat;
  27. import android.graphics.Rect;
  28. import android.graphics.YuvImage;
  29. import android.hardware.Camera;
  30. import android.hardware.Camera.AutoFocusCallback;
  31. import android.hardware.Camera.Parameters;
  32. import android.hardware.Camera.PreviewCallback;
  33. import android.net.Uri;
  34. import android.os.Bundle;
  35. import android.view.Display;
  36. import android.view.KeyEvent;
  37. import android.view.Menu;
  38. import android.view.MenuItem;
  39. import android.view.MotionEvent;
  40. import android.view.SurfaceHolder;
  41. import android.view.SurfaceHolder.Callback;
  42. import android.view.SurfaceView;
  43. import android.view.View;
  44. import android.view.View.OnTouchListener;
  45. import android.widget.ImageView;
  46. import android.widget.LinearLayout;
  47. import java.io.ByteArrayOutputStream;
  48. import java.io.IOException;
  49. import java.util.List;
  50. /**
  51. * Main activity for IDEAL Magnifier. Uses the phone's camera to turn Android
  52. * into a video magnifier. Volume buttons to zoom in/out Search button to turn
  53. * on the LED light Menu to bring up the color filter options If the image is
  54. * blurry, just tap the screen and it will refocus.
  55. */
  56. public class MagnifierActivity extends Activity implements Callback {
  57. /**
  58. * The user interaction cool down period in milliseconds. The camera's
  59. * autoFocus method is invoked after this delay if no further user
  60. * interactions occur.
  61. */
  62. public static final long FOCUS_INTERACTION_TIMEOUT_THRESHOLD = 750;
  63. /**
  64. * The threshold time for which a finger must remain on the
  65. * MagnifiedImageView before it is considered a long click and initiates a
  66. * pause toggle event.
  67. */
  68. public static final long LONG_PRESS_INTERACTION_THRESHOLD = 1500;
  69. /**
  70. * Runnable used to defer camera focusing until user interaction has stopped
  71. * for FOCUS_INTERACTION_TIMEOUT_THRESHOLD milliseconds.
  72. */
  73. private Runnable mFocuserLocked = null;
  74. /**
  75. * Runnable used to toggle the paused state of the MagnifiedImageView
  76. */
  77. private Runnable mPauser = null;
  78. /**
  79. * A flag indicating whether the camera is currently performing an
  80. * auto-focus operation.
  81. */
  82. private boolean mCameraFocusing = false;
  83. /**
  84. * A flag indicating whether or not a user interaction occurred during the
  85. * last camera focus event. Used to defer updating of camera parameters
  86. * until the focus operation completes.
  87. */
  88. private boolean mParameterSettingDeferred = false;
  89. /**
  90. * The last set of camera parameters that was set successfully. Used to
  91. * return the camera to its last known state when returning from a paused
  92. * state.
  93. */
  94. private Parameters mLastCameraParameters = null;
  95. /**
  96. * The set of camera parameters to be applied after a focus operation
  97. * completes.
  98. */
  99. private Parameters mDeferredParameters = null;
  100. /**
  101. * The Layout Manager holding either the MagnificationView or
  102. * MagnifiedImageView
  103. */
  104. private LinearLayout mRootView = null;
  105. /**
  106. * Surface used to project the camera preview.
  107. */
  108. private SurfaceHolder mHolder = null;
  109. /**
  110. * Abstracted SurfaceView used to further magnify the camera preview frames.
  111. */
  112. private MagnificationView mPreview = null;
  113. /**
  114. * Abstracted ImageView used to further magnify a single camera preview
  115. * frame.
  116. */
  117. private MagnifiedImageView mImagePreview = null;
  118. /**
  119. * The system's camera hardware
  120. */
  121. private Camera mCamera = null;
  122. /**
  123. * Handler for touch events. Used for focusing and pausing the magnifier.
  124. */
  125. private OnTouchListener mTouchListener = null;
  126. /**
  127. * Callback used to obtain camera data for pausing the magnifier.
  128. */
  129. private PreviewCallback mPreviewCallback = null;
  130. /**
  131. * The current zoom level
  132. */
  133. private int mZoom = 0;
  134. /**
  135. * The current camera mode
  136. */
  137. private String mCameraMode = null;
  138. /**
  139. * Flag indicating the current state of the camera flash LED.
  140. */
  141. private boolean mTorch = false;
  142. /**
  143. * Flag indicating the state of the surface, true indicating paused.
  144. */
  145. private boolean mMagnifierPaused = false;
  146. /**
  147. * The Preferences in which we store the last application state.
  148. */
  149. private SharedPreferences mPrefs = null;
  150. /**
  151. * These classes lie about our actual screen size, thus giving us a 2x
  152. * digital zoom to start with even before we invoke the hardware zoom
  153. * features of the camera.
  154. */
  155. public class MagnificationView extends SurfaceView {
  156. public MagnificationView(Context context) {
  157. super(context);
  158. }
  159. @Override
  160. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  161. // TODO: This method miscalculates the dimensions of the projected
  162. // screen, causing a horizontal stretch effect.
  163. Display display = getWindowManager().getDefaultDisplay();
  164. int width = display.getWidth();
  165. int height = display.getHeight();
  166. if (width * height > 643200) {
  167. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  168. } else {
  169. // TODO: We short-circuit the onMeasure if we have a screen size
  170. // above a certain threshold. This keeps the system from silently
  171. // killing our app for memory usage.
  172. widthMeasureSpec = MeasureSpec.makeMeasureSpec(width * 2, MeasureSpec.EXACTLY);
  173. heightMeasureSpec = MeasureSpec.makeMeasureSpec(height * 2, MeasureSpec.EXACTLY);
  174. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  175. }
  176. }
  177. }
  178. public class MagnifiedImageView extends ImageView {
  179. public MagnifiedImageView(Context context) {
  180. super(context);
  181. }
  182. @Override
  183. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  184. // TODO: This method miscalculates the dimensions of the projected
  185. // screen, causing a horizontal stretch effect.
  186. Display display = getWindowManager().getDefaultDisplay();
  187. int width = display.getWidth();
  188. int height = display.getHeight();
  189. if (width * height > 643200) {
  190. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  191. } else {
  192. // TODO: We short-circuit the onMeasure if we have a screen size
  193. // above a certain threshold. This keeps the system from silently
  194. // killing our app for memory usage.
  195. widthMeasureSpec = MeasureSpec.makeMeasureSpec(width * 2, MeasureSpec.EXACTLY);
  196. heightMeasureSpec = MeasureSpec.makeMeasureSpec(height * 2, MeasureSpec.EXACTLY);
  197. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  198. }
  199. }
  200. }
  201. /** Called when the activity is first created. */
  202. @Override
  203. public void onCreate(Bundle savedInstanceState) {
  204. super.onCreate(savedInstanceState);
  205. setContentView(R.layout.main);
  206. mPreview = new MagnificationView(this);
  207. mImagePreview = new MagnifiedImageView(this);
  208. mPauser = new Runnable() {
  209. @Override
  210. public void run() {
  211. togglePausePreview(true);
  212. }
  213. };
  214. mTouchListener = new OnTouchListener() {
  215. @Override
  216. public boolean onTouch(View view, MotionEvent event) {
  217. switch (event.getAction()) {
  218. case MotionEvent.ACTION_DOWN:
  219. mFocuserLocked.run();
  220. mPreview.postDelayed(mPauser, LONG_PRESS_INTERACTION_THRESHOLD);
  221. return true;
  222. case MotionEvent.ACTION_UP:
  223. case MotionEvent.ACTION_CANCEL:
  224. mPreview.removeCallbacks(mPauser);
  225. togglePausePreview(false);
  226. return true;
  227. }
  228. return false;
  229. }
  230. };
  231. mPreview.setOnTouchListener(mTouchListener);
  232. mImagePreview.setOnTouchListener(mTouchListener);
  233. mPreviewCallback = new PreviewCallback() {
  234. @Override
  235. /**
  236. * This callback will be used to capture a single frame from the camera
  237. * hardware. We process the image in YUV format, convert to JPEG, and
  238. * finally to Bitmap so it can be shown in an extended ImageView. This
  239. * process is sub-optimal as Android's graphics BitmapFactory does not
  240. * support direct YUV to Bitmap conversions.
  241. */
  242. public void onPreviewFrame(byte[] data, Camera camera) {
  243. Camera.Parameters parameters = camera.getParameters();
  244. Camera.Size size = parameters.getPreviewSize();
  245. if (size == null) {
  246. // We've failed to get preview frame data, so switch pack to
  247. // a live view.
  248. togglePausePreview(false);
  249. return;
  250. }
  251. // Generate a YuvImage from the camera data
  252. int w = parameters.getPreviewSize().width;
  253. int h = parameters.getPreviewSize().height;
  254. YuvImage pausedYuvImage = new YuvImage(data, parameters.getPreviewFormat(), w, h,
  255. null);
  256. // Compress the YuvImage to JPEG Output Stream
  257. ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
  258. pausedYuvImage.compressToJpeg(new Rect(0, 0, w, h), 100, jpegOutputStream);
  259. // Use BitmapFactory to create a Bitmap from the JPEG stream
  260. Bitmap pausedImage = BitmapFactory.decodeByteArray(jpegOutputStream.toByteArray(),
  261. 0, jpegOutputStream.size());
  262. // Scale the Bitmap to match the MagnifiedImageView's dimensions
  263. Display display = getWindowManager().getDefaultDisplay();
  264. int width = display.getWidth();
  265. int height = display.getHeight();
  266. mImagePreview.setImageBitmap(Bitmap.createScaledBitmap(pausedImage, width * 2,
  267. height * 2, false));
  268. mRootView.removeAllViews();
  269. mRootView.addView(mImagePreview);
  270. }
  271. };
  272. mRootView = (LinearLayout) findViewById(R.id.rootView);
  273. mRootView.addView(mPreview);
  274. mPreview.setKeepScreenOn(true);
  275. mHolder = mPreview.getHolder();
  276. mHolder.addCallback(this);
  277. mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  278. mFocuserLocked = new Runnable() {
  279. @Override
  280. public void run() {
  281. // This Runnable will focus the camera, and guarantee that calls
  282. // to Camera.autoFocus are synchronous.
  283. if (mCamera != null && !mCameraFocusing) {
  284. mCameraFocusing = true;
  285. mCamera.autoFocus(new AutoFocusCallback() {
  286. @Override
  287. public void onAutoFocus(boolean success, Camera camera) {
  288. mCameraFocusing = false;
  289. if (mParameterSettingDeferred) {
  290. // The user attempted to change a camera
  291. // parameter during the focus event, set the
  292. // parameters again so the user's change
  293. // takes effect.
  294. setParams(mDeferredParameters);
  295. mParameterSettingDeferred = false;
  296. }
  297. }
  298. });
  299. }
  300. }
  301. };
  302. }
  303. @Override
  304. public boolean onKeyDown(int keyCode, KeyEvent event) {
  305. if (mCamera != null) {
  306. Parameters params = mCamera.getParameters();
  307. if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
  308. mZoom = mZoom + 1;
  309. if (mZoom > params.getMaxZoom()) {
  310. mZoom = params.getMaxZoom();
  311. }
  312. params.setZoom(mZoom);
  313. setParams(params);
  314. return true;
  315. }
  316. if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
  317. mZoom = mZoom - 1;
  318. if (mZoom < 0) {
  319. mZoom = 0;
  320. }
  321. params.setZoom(mZoom);
  322. setParams(params);
  323. return true;
  324. }
  325. if (keyCode == KeyEvent.KEYCODE_SEARCH) {
  326. if (mTorch) {
  327. params.setFlashMode(Parameters.FLASH_MODE_OFF);
  328. mTorch = false;
  329. } else {
  330. params.setFlashMode(Parameters.FLASH_MODE_TORCH);
  331. mTorch = true;
  332. }
  333. setParams(params);
  334. return true;
  335. }
  336. }
  337. if (keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_ENTER
  338. || keyCode == KeyEvent.KEYCODE_DPAD_CENTER
  339. || (keyCode == KeyEvent.KEYCODE_BACK && mMagnifierPaused)) {
  340. togglePausePreview(!mMagnifierPaused);
  341. return true;
  342. }
  343. return super.onKeyDown(keyCode, event);
  344. }
  345. private void setParams(Parameters params) {
  346. // Setting the parameters of the camera is only a safe operation if the
  347. // camera is not presently focusing. Only focus the camera after the
  348. // user interaction has quieted down, verified by a delay period.
  349. mPreview.removeCallbacks(mFocuserLocked);
  350. if (!mCameraFocusing) {
  351. // On some phones such as the Motorola Droid, the preview needs to
  352. // be stopped and then restarted.
  353. // Failing to do so will result in an ANR (application not
  354. // responding) error.
  355. //
  356. // TODO: Check if it is possible to detect when a restart is needed
  357. // by checking isSmoothZoomSupported in the Camera Parameters.
  358. //
  359. // Nexus One: False (does not need a restart)
  360. // Motorola Droid: True (must have preview restarted)
  361. //
  362. // Log.e("smooth zoom?", params.isSmoothZoomSupported() + "");
  363. mCamera.stopPreview();
  364. mCamera.startPreview();
  365. mCamera.setParameters(params);
  366. mLastCameraParameters = params;
  367. } else {
  368. mParameterSettingDeferred = true;
  369. mDeferredParameters = params;
  370. }
  371. mPreview.postDelayed(mFocuserLocked, FOCUS_INTERACTION_TIMEOUT_THRESHOLD);
  372. }
  373. @Override
  374. public void surfaceCreated(SurfaceHolder holder) {
  375. if (mCamera == null) {
  376. mCamera = Camera.open();
  377. }
  378. mCameraFocusing = false;
  379. try {
  380. mCamera.setPreviewDisplay(holder);
  381. } catch (IOException e) {
  382. e.printStackTrace();
  383. }
  384. Parameters initParams;
  385. if (mLastCameraParameters != null) {
  386. initParams = mLastCameraParameters;
  387. } else {
  388. initParams = mCamera.getParameters();
  389. mLastCameraParameters = initParams;
  390. }
  391. initParams.setPreviewFormat(ImageFormat.NV21);
  392. setParams(initParams);
  393. }
  394. @Override
  395. public void surfaceDestroyed(SurfaceHolder holder) {
  396. mCamera.stopPreview();
  397. mCamera.release();
  398. mCamera = null;
  399. }
  400. @Override
  401. public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
  402. mHolder.setFixedSize(w, h);
  403. mCamera.startPreview();
  404. // Note: Parameters are not safe to set until AFTER the preview is up.
  405. // One some phones, it is OK (such as the Nexus One), but on others,
  406. // (such as the Motorola Droid), this will cause the parameters to not
  407. // actually change/may lead to an ANR on a subsequent set attempt.
  408. Parameters params = mCamera.getParameters();
  409. // Reload the previous state from the stored preferences.
  410. mPrefs = getSharedPreferences(getString(R.string.app_name), 0);
  411. mZoom = mPrefs.getInt(getString(R.string.zoom_level_pref), mCamera.getParameters()
  412. .getMaxZoom() / 2);
  413. mCameraMode = mPrefs.getString(getString(R.string.camera_mode_pref), mCamera
  414. .getParameters().getSupportedColorEffects().get(0));
  415. params.setZoom(mZoom);
  416. params.setColorEffect(mCameraMode);
  417. setParams(params);
  418. }
  419. public void showEffectsList() {
  420. if (mCamera == null) {
  421. return;
  422. }
  423. List<String> effectsList = mCamera.getParameters().getSupportedColorEffects();
  424. String[] effects = {
  425. ""
  426. };
  427. effects = effectsList.toArray(effects);
  428. final String[] items = effects;
  429. AlertDialog.Builder builder = new AlertDialog.Builder(this);
  430. builder.setTitle(getString(R.string.color_effect_dialog_title));
  431. builder.setItems(items, new DialogInterface.OnClickListener() {
  432. @Override
  433. public void onClick(DialogInterface dialog, int item) {
  434. Parameters params = mCamera.getParameters();
  435. mCameraMode = items[item];
  436. params.setColorEffect(items[item]);
  437. setParams(params);
  438. }
  439. });
  440. builder.create().show();
  441. }
  442. @Override
  443. public boolean onCreateOptionsMenu(Menu menu) {
  444. super.onCreateOptionsMenu(menu);
  445. // Parameters for menu.add are:
  446. // group -- Not used here.
  447. // id -- Used only when you want to handle and identify the click
  448. // yourself.
  449. // title
  450. menu.add(0, 0, 0, getString(R.string.color_effect_button_text)).setIcon(
  451. android.R.drawable.ic_menu_manage);
  452. menu.add(0, 1, 0, getString(R.string.toggle_freeze_frame_button_text)).setIcon(
  453. android.R.drawable.ic_menu_camera);
  454. menu.add(0, 2, 0, getString(R.string.toggle_light_button_text)).setIcon(
  455. android.R.drawable.ic_menu_view);
  456. menu.add(0, 3, 0, getString(R.string.more_apps_button_text)).setIcon(
  457. android.R.drawable.ic_menu_search);
  458. return true;
  459. }
  460. /**
  461. * Activity callback that lets your handle the selection in the class.
  462. * Return true to indicate that you've got it, false to indicate that it
  463. * should be handled by a declared handler object for that item (handler
  464. * objects are discouraged for reasons of efficiency).
  465. */
  466. @Override
  467. public boolean onOptionsItemSelected(MenuItem item) {
  468. switch (item.getItemId()) {
  469. case 0:
  470. showEffectsList();
  471. return true;
  472. case 1:
  473. togglePausePreview(!mMagnifierPaused);
  474. return true;
  475. case 2:
  476. Parameters params = mCamera.getParameters();
  477. if (mTorch) {
  478. params.setFlashMode(Parameters.FLASH_MODE_OFF);
  479. mTorch = false;
  480. } else {
  481. params.setFlashMode(Parameters.FLASH_MODE_TORCH);
  482. mTorch = true;
  483. }
  484. setParams(params);
  485. break;
  486. case 3:
  487. String marketUrl = "market://search?q=pub:\"IDEAL Group, Inc. Android Development Team\"";
  488. Intent i = new Intent(Intent.ACTION_VIEW);
  489. i.setData(Uri.parse(marketUrl));
  490. try {
  491. startActivity(i);
  492. } catch (ActivityNotFoundException anf) {
  493. new AlertDialog.Builder(this)
  494. .setTitle(getString(R.string.market_launch_error_title))
  495. .setMessage(getString(R.string.market_launch_error_text))
  496. .setNeutralButton(getString(R.string.market_launch_error_button), null)
  497. .show();
  498. }
  499. return true;
  500. }
  501. return false;
  502. }
  503. /**
  504. * Toggles the freeze frame feature by removing the MagnificationView and
  505. * adding a MagnifiedImageView with a single scaled preview frame.
  506. */
  507. public synchronized void togglePausePreview(boolean shouldPause) {
  508. if (mMagnifierPaused == shouldPause) {
  509. return;
  510. }
  511. mMagnifierPaused = shouldPause;
  512. if (!mMagnifierPaused) {
  513. mRootView.removeAllViews();
  514. mRootView.addView(mPreview);
  515. } else {
  516. mCamera.setOneShotPreviewCallback(mPreviewCallback);
  517. }
  518. }
  519. /**
  520. * Save the state of the application as it loses focus.
  521. */
  522. @Override
  523. public void onPause() {
  524. super.onPause();
  525. SharedPreferences.Editor editor = mPrefs.edit();
  526. editor.putInt(getString(R.string.zoom_level_pref), mZoom);
  527. editor.putString(getString(R.string.camera_mode_pref), mCameraMode);
  528. editor.commit();
  529. }
  530. }