1 /* 2 * Copyright (C) 2008 ZXing authors 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.google.zxing.client.android; 18 19 import com.google.zxing.BarcodeFormat; 20 import com.google.zxing.DecodeHintType; 21 import com.google.zxing.Result; 22 import com.google.zxing.ResultMetadataType; 23 import com.google.zxing.ResultPoint; 24 import com.google.zxing.client.android.camera.CameraManager; 25 import com.google.zxing.client.android.clipboard.ClipboardInterface; 26 import com.google.zxing.client.android.history.HistoryActivity; 27 import com.google.zxing.client.android.history.HistoryItem; 28 import com.google.zxing.client.android.history.HistoryManager; 29 import com.google.zxing.client.android.result.ResultButtonListener; 30 import com.google.zxing.client.android.result.ResultHandler; 31 import com.google.zxing.client.android.result.ResultHandlerFactory; 32 import com.google.zxing.client.android.result.supplement.SupplementalInfoRetriever; 33 import com.google.zxing.client.android.share.ShareActivity; 34 35 import android.app.Activity; 36 import android.app.AlertDialog; 37 import android.content.Intent; 38 import android.content.SharedPreferences; 39 import android.content.pm.ActivityInfo; 40 import android.content.res.Configuration; 41 import android.graphics.Bitmap; 42 import android.graphics.BitmapFactory; 43 import android.graphics.Canvas; 44 import android.graphics.Paint; 45 import android.net.Uri; 46 import android.os.Bundle; 47 import android.os.Handler; 48 import android.os.Message; 49 import android.preference.PreferenceManager; 50 import android.util.Log; 51 import android.util.TypedValue; 52 import android.view.KeyEvent; 53 import android.view.Menu; 54 import android.view.MenuInflater; 55 import android.view.MenuItem; 56 import android.view.Surface; 57 import android.view.SurfaceHolder; 58 import android.view.SurfaceView; 59 import android.view.View; 60 import android.view.ViewGroup; 61 import android.view.Window; 62 import android.view.WindowManager; 63 import android.widget.ImageView; 64 import android.widget.TextView; 65 import android.widget.Toast; 66 67 import java.io.IOException; 68 import java.text.DateFormat; 69 import java.util.Collection; 70 import java.util.EnumSet; 71 import java.util.Map; 72 73 /** 74 * This activity opens the camera and does the actual scanning on a background thread. It draws a 75 * viewfinder to help the user place the barcode correctly, shows feedback as the image processing 76 * is happening, and then overlays the results when a scan is successful. 77 * 78 * @author dswitkin@google.com (Daniel Switkin) 79 * @author Sean Owen 80 */ 81 public final class CaptureActivity extends Activity implements SurfaceHolder.Callback { 82 83 private static final String TAG = CaptureActivity.class.getSimpleName(); 84 85 private static final long DEFAULT_INTENT_RESULT_DURATION_MS = 1500L; 86 private static final long BULK_MODE_SCAN_DELAY_MS = 1000L; 87 88 private static final String[] ZXING_URLS = { "http://zxing.appspot.com/scan", "zxing://scan/" }; 89 90 private static final int HISTORY_REQUEST_CODE = 0x0000bacc; 91 92 private static final Collection<ResultMetadataType> DISPLAYABLE_METADATA_TYPES = 93 EnumSet.of(ResultMetadataType.ISSUE_NUMBER, 94 ResultMetadataType.SUGGESTED_PRICE, 95 ResultMetadataType.ERROR_CORRECTION_LEVEL, 96 ResultMetadataType.POSSIBLE_COUNTRY); 97 98 private CameraManager cameraManager; 99 private CaptureActivityHandler handler; 100 private Result savedResultToShow; 101 private ViewfinderView viewfinderView; 102 private TextView statusView; 103 private View resultView; 104 private Result lastResult; 105 private boolean hasSurface; 106 private boolean copyToClipboard; 107 private IntentSource source; 108 private String sourceUrl; 109 private ScanFromWebPageManager scanFromWebPageManager; 110 private Collection<BarcodeFormat> decodeFormats; 111 private Map<DecodeHintType,?> decodeHints; 112 private String characterSet; 113 private HistoryManager historyManager; 114 private InactivityTimer inactivityTimer; 115 private BeepManager beepManager; 116 private AmbientLightManager ambientLightManager; 117 getViewfinderView()118 ViewfinderView getViewfinderView() { 119 return viewfinderView; 120 } 121 getHandler()122 public Handler getHandler() { 123 return handler; 124 } 125 getCameraManager()126 CameraManager getCameraManager() { 127 return cameraManager; 128 } 129 130 @Override onCreate(Bundle icicle)131 public void onCreate(Bundle icicle) { 132 super.onCreate(icicle); 133 134 Window window = getWindow(); 135 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 136 setContentView(R.layout.capture); 137 138 hasSurface = false; 139 inactivityTimer = new InactivityTimer(this); 140 beepManager = new BeepManager(this); 141 ambientLightManager = new AmbientLightManager(this); 142 143 PreferenceManager.setDefaultValues(this, R.xml.preferences, false); 144 } 145 146 @Override onResume()147 protected void onResume() { 148 super.onResume(); 149 150 // historyManager must be initialized here to update the history preference 151 historyManager = new HistoryManager(this); 152 historyManager.trimHistory(); 153 154 // CameraManager must be initialized here, not in onCreate(). This is necessary because we don't 155 // want to open the camera driver and measure the screen size if we're going to show the help on 156 // first launch. That led to bugs where the scanning rectangle was the wrong size and partially 157 // off screen. 158 cameraManager = new CameraManager(getApplication()); 159 160 viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view); 161 viewfinderView.setCameraManager(cameraManager); 162 163 resultView = findViewById(R.id.result_view); 164 statusView = (TextView) findViewById(R.id.status_view); 165 166 handler = null; 167 lastResult = null; 168 169 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 170 171 if (prefs.getBoolean(PreferencesActivity.KEY_DISABLE_AUTO_ORIENTATION, true)) { 172 setRequestedOrientation(getCurrentOrientation()); 173 } else { 174 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); 175 } 176 177 resetStatusView(); 178 179 180 beepManager.updatePrefs(); 181 ambientLightManager.start(cameraManager); 182 183 inactivityTimer.onResume(); 184 185 Intent intent = getIntent(); 186 187 copyToClipboard = prefs.getBoolean(PreferencesActivity.KEY_COPY_TO_CLIPBOARD, true) 188 && (intent == null || intent.getBooleanExtra(Intents.Scan.SAVE_HISTORY, true)); 189 190 source = IntentSource.NONE; 191 sourceUrl = null; 192 scanFromWebPageManager = null; 193 decodeFormats = null; 194 characterSet = null; 195 196 if (intent != null) { 197 198 String action = intent.getAction(); 199 String dataString = intent.getDataString(); 200 201 if (Intents.Scan.ACTION.equals(action)) { 202 203 // Scan the formats the intent requested, and return the result to the calling activity. 204 source = IntentSource.NATIVE_APP_INTENT; 205 decodeFormats = DecodeFormatManager.parseDecodeFormats(intent); 206 decodeHints = DecodeHintManager.parseDecodeHints(intent); 207 208 if (intent.hasExtra(Intents.Scan.WIDTH) && intent.hasExtra(Intents.Scan.HEIGHT)) { 209 int width = intent.getIntExtra(Intents.Scan.WIDTH, 0); 210 int height = intent.getIntExtra(Intents.Scan.HEIGHT, 0); 211 if (width > 0 && height > 0) { 212 cameraManager.setManualFramingRect(width, height); 213 } 214 } 215 216 if (intent.hasExtra(Intents.Scan.CAMERA_ID)) { 217 int cameraId = intent.getIntExtra(Intents.Scan.CAMERA_ID, -1); 218 if (cameraId >= 0) { 219 cameraManager.setManualCameraId(cameraId); 220 } 221 } 222 223 String customPromptMessage = intent.getStringExtra(Intents.Scan.PROMPT_MESSAGE); 224 if (customPromptMessage != null) { 225 statusView.setText(customPromptMessage); 226 } 227 228 } else if (dataString != null && 229 dataString.contains("http://www.google") && 230 dataString.contains("/m/products/scan")) { 231 232 // Scan only products and send the result to mobile Product Search. 233 source = IntentSource.PRODUCT_SEARCH_LINK; 234 sourceUrl = dataString; 235 decodeFormats = DecodeFormatManager.PRODUCT_FORMATS; 236 237 } else if (isZXingURL(dataString)) { 238 239 // Scan formats requested in query string (all formats if none specified). 240 // If a return URL is specified, send the results there. Otherwise, handle it ourselves. 241 source = IntentSource.ZXING_LINK; 242 sourceUrl = dataString; 243 Uri inputUri = Uri.parse(dataString); 244 scanFromWebPageManager = new ScanFromWebPageManager(inputUri); 245 decodeFormats = DecodeFormatManager.parseDecodeFormats(inputUri); 246 // Allow a sub-set of the hints to be specified by the caller. 247 decodeHints = DecodeHintManager.parseDecodeHints(inputUri); 248 249 } 250 251 characterSet = intent.getStringExtra(Intents.Scan.CHARACTER_SET); 252 253 } 254 255 SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view); 256 SurfaceHolder surfaceHolder = surfaceView.getHolder(); 257 if (hasSurface) { 258 // The activity was paused but not stopped, so the surface still exists. Therefore 259 // surfaceCreated() won't be called, so init the camera here. 260 initCamera(surfaceHolder); 261 } else { 262 // Install the callback and wait for surfaceCreated() to init the camera. 263 surfaceHolder.addCallback(this); 264 } 265 } 266 getCurrentOrientation()267 private int getCurrentOrientation() { 268 int rotation = getWindowManager().getDefaultDisplay().getRotation(); 269 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 270 switch (rotation) { 271 case Surface.ROTATION_0: 272 case Surface.ROTATION_90: 273 return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 274 default: 275 return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 276 } 277 } else { 278 switch (rotation) { 279 case Surface.ROTATION_0: 280 case Surface.ROTATION_270: 281 return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 282 default: 283 return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 284 } 285 } 286 } 287 isZXingURL(String dataString)288 private static boolean isZXingURL(String dataString) { 289 if (dataString == null) { 290 return false; 291 } 292 for (String url : ZXING_URLS) { 293 if (dataString.startsWith(url)) { 294 return true; 295 } 296 } 297 return false; 298 } 299 300 @Override onPause()301 protected void onPause() { 302 if (handler != null) { 303 handler.quitSynchronously(); 304 handler = null; 305 } 306 inactivityTimer.onPause(); 307 ambientLightManager.stop(); 308 beepManager.close(); 309 cameraManager.closeDriver(); 310 //historyManager = null; // Keep for onActivityResult 311 if (!hasSurface) { 312 SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view); 313 SurfaceHolder surfaceHolder = surfaceView.getHolder(); 314 surfaceHolder.removeCallback(this); 315 } 316 super.onPause(); 317 } 318 319 @Override onDestroy()320 protected void onDestroy() { 321 inactivityTimer.shutdown(); 322 super.onDestroy(); 323 } 324 325 @Override onKeyDown(int keyCode, KeyEvent event)326 public boolean onKeyDown(int keyCode, KeyEvent event) { 327 switch (keyCode) { 328 case KeyEvent.KEYCODE_BACK: 329 if (source == IntentSource.NATIVE_APP_INTENT) { 330 setResult(RESULT_CANCELED); 331 finish(); 332 return true; 333 } 334 if ((source == IntentSource.NONE || source == IntentSource.ZXING_LINK) && lastResult != null) { 335 restartPreviewAfterDelay(0L); 336 return true; 337 } 338 break; 339 case KeyEvent.KEYCODE_FOCUS: 340 case KeyEvent.KEYCODE_CAMERA: 341 // Handle these events so they don't launch the Camera app 342 return true; 343 // Use volume up/down to turn on light 344 case KeyEvent.KEYCODE_VOLUME_DOWN: 345 cameraManager.setTorch(false); 346 return true; 347 case KeyEvent.KEYCODE_VOLUME_UP: 348 cameraManager.setTorch(true); 349 return true; 350 } 351 return super.onKeyDown(keyCode, event); 352 } 353 354 @Override onCreateOptionsMenu(Menu menu)355 public boolean onCreateOptionsMenu(Menu menu) { 356 MenuInflater menuInflater = getMenuInflater(); 357 menuInflater.inflate(R.menu.capture, menu); 358 return super.onCreateOptionsMenu(menu); 359 } 360 361 @Override onOptionsItemSelected(MenuItem item)362 public boolean onOptionsItemSelected(MenuItem item) { 363 Intent intent = new Intent(Intent.ACTION_VIEW); 364 intent.addFlags(Intents.FLAG_NEW_DOC); 365 switch (item.getItemId()) { 366 case R.id.menu_share: 367 intent.setClassName(this, ShareActivity.class.getName()); 368 startActivity(intent); 369 break; 370 case R.id.menu_history: 371 intent.setClassName(this, HistoryActivity.class.getName()); 372 startActivityForResult(intent, HISTORY_REQUEST_CODE); 373 break; 374 case R.id.menu_settings: 375 intent.setClassName(this, PreferencesActivity.class.getName()); 376 startActivity(intent); 377 break; 378 case R.id.menu_help: 379 intent.setClassName(this, HelpActivity.class.getName()); 380 startActivity(intent); 381 break; 382 default: 383 return super.onOptionsItemSelected(item); 384 } 385 return true; 386 } 387 388 @Override onActivityResult(int requestCode, int resultCode, Intent intent)389 public void onActivityResult(int requestCode, int resultCode, Intent intent) { 390 if (resultCode == RESULT_OK && requestCode == HISTORY_REQUEST_CODE && historyManager != null) { 391 int itemNumber = intent.getIntExtra(Intents.History.ITEM_NUMBER, -1); 392 if (itemNumber >= 0) { 393 HistoryItem historyItem = historyManager.buildHistoryItem(itemNumber); 394 decodeOrStoreSavedBitmap(null, historyItem.getResult()); 395 } 396 } 397 } 398 decodeOrStoreSavedBitmap(Bitmap bitmap, Result result)399 private void decodeOrStoreSavedBitmap(Bitmap bitmap, Result result) { 400 // Bitmap isn't used yet -- will be used soon 401 if (handler == null) { 402 savedResultToShow = result; 403 } else { 404 if (result != null) { 405 savedResultToShow = result; 406 } 407 if (savedResultToShow != null) { 408 Message message = Message.obtain(handler, R.id.decode_succeeded, savedResultToShow); 409 handler.sendMessage(message); 410 } 411 savedResultToShow = null; 412 } 413 } 414 415 @Override surfaceCreated(SurfaceHolder holder)416 public void surfaceCreated(SurfaceHolder holder) { 417 if (holder == null) { 418 Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!"); 419 } 420 if (!hasSurface) { 421 hasSurface = true; 422 initCamera(holder); 423 } 424 } 425 426 @Override surfaceDestroyed(SurfaceHolder holder)427 public void surfaceDestroyed(SurfaceHolder holder) { 428 hasSurface = false; 429 } 430 431 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)432 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 433 // do nothing 434 } 435 436 /** 437 * A valid barcode has been found, so give an indication of success and show the results. 438 * 439 * @param rawResult The contents of the barcode. 440 * @param scaleFactor amount by which thumbnail was scaled 441 * @param barcode A greyscale bitmap of the camera data which was decoded. 442 */ handleDecode(Result rawResult, Bitmap barcode, float scaleFactor)443 public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor) { 444 inactivityTimer.onActivity(); 445 lastResult = rawResult; 446 ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(this, rawResult); 447 448 boolean fromLiveScan = barcode != null; 449 if (fromLiveScan) { 450 historyManager.addHistoryItem(rawResult, resultHandler); 451 // Then not from history, so beep/vibrate and we have an image to draw on 452 beepManager.playBeepSoundAndVibrate(); 453 drawResultPoints(barcode, scaleFactor, rawResult); 454 } 455 456 switch (source) { 457 case NATIVE_APP_INTENT: 458 case PRODUCT_SEARCH_LINK: 459 handleDecodeExternally(rawResult, resultHandler, barcode); 460 break; 461 case ZXING_LINK: 462 if (scanFromWebPageManager == null || !scanFromWebPageManager.isScanFromWebPage()) { 463 handleDecodeInternally(rawResult, resultHandler, barcode); 464 } else { 465 handleDecodeExternally(rawResult, resultHandler, barcode); 466 } 467 break; 468 case NONE: 469 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 470 if (fromLiveScan && prefs.getBoolean(PreferencesActivity.KEY_BULK_MODE, false)) { 471 Toast.makeText(getApplicationContext(), 472 getResources().getString(R.string.msg_bulk_mode_scanned) + " (" + rawResult.getText() + ')', 473 Toast.LENGTH_SHORT).show(); 474 maybeSetClipboard(resultHandler); 475 // Wait a moment or else it will scan the same barcode continuously about 3 times 476 restartPreviewAfterDelay(BULK_MODE_SCAN_DELAY_MS); 477 } else { 478 handleDecodeInternally(rawResult, resultHandler, barcode); 479 } 480 break; 481 } 482 } 483 484 /** 485 * Superimpose a line for 1D or dots for 2D to highlight the key features of the barcode. 486 * 487 * @param barcode A bitmap of the captured image. 488 * @param scaleFactor amount by which thumbnail was scaled 489 * @param rawResult The decoded results which contains the points to draw. 490 */ drawResultPoints(Bitmap barcode, float scaleFactor, Result rawResult)491 private void drawResultPoints(Bitmap barcode, float scaleFactor, Result rawResult) { 492 ResultPoint[] points = rawResult.getResultPoints(); 493 if (points != null && points.length > 0) { 494 Canvas canvas = new Canvas(barcode); 495 Paint paint = new Paint(); 496 paint.setColor(getResources().getColor(R.color.result_points)); 497 if (points.length == 2) { 498 paint.setStrokeWidth(4.0f); 499 drawLine(canvas, paint, points[0], points[1], scaleFactor); 500 } else if (points.length == 4 && 501 (rawResult.getBarcodeFormat() == BarcodeFormat.UPC_A || 502 rawResult.getBarcodeFormat() == BarcodeFormat.EAN_13)) { 503 // Hacky special case -- draw two lines, for the barcode and metadata 504 drawLine(canvas, paint, points[0], points[1], scaleFactor); 505 drawLine(canvas, paint, points[2], points[3], scaleFactor); 506 } else { 507 paint.setStrokeWidth(10.0f); 508 for (ResultPoint point : points) { 509 if (point != null) { 510 canvas.drawPoint(scaleFactor * point.getX(), scaleFactor * point.getY(), paint); 511 } 512 } 513 } 514 } 515 } 516 drawLine(Canvas canvas, Paint paint, ResultPoint a, ResultPoint b, float scaleFactor)517 private static void drawLine(Canvas canvas, Paint paint, ResultPoint a, ResultPoint b, float scaleFactor) { 518 if (a != null && b != null) { 519 canvas.drawLine(scaleFactor * a.getX(), 520 scaleFactor * a.getY(), 521 scaleFactor * b.getX(), 522 scaleFactor * b.getY(), 523 paint); 524 } 525 } 526 527 // Put up our own UI for how to handle the decoded contents. handleDecodeInternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode)528 private void handleDecodeInternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) { 529 530 maybeSetClipboard(resultHandler); 531 532 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 533 534 if (resultHandler.getDefaultButtonID() != null && prefs.getBoolean(PreferencesActivity.KEY_AUTO_OPEN_WEB, false)) { 535 resultHandler.handleButtonPress(resultHandler.getDefaultButtonID()); 536 return; 537 } 538 539 statusView.setVisibility(View.GONE); 540 viewfinderView.setVisibility(View.GONE); 541 resultView.setVisibility(View.VISIBLE); 542 543 ImageView barcodeImageView = (ImageView) findViewById(R.id.barcode_image_view); 544 if (barcode == null) { 545 barcodeImageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), 546 R.drawable.launcher_icon)); 547 } else { 548 barcodeImageView.setImageBitmap(barcode); 549 } 550 551 TextView formatTextView = (TextView) findViewById(R.id.format_text_view); 552 formatTextView.setText(rawResult.getBarcodeFormat().toString()); 553 554 TextView typeTextView = (TextView) findViewById(R.id.type_text_view); 555 typeTextView.setText(resultHandler.getType().toString()); 556 557 DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); 558 TextView timeTextView = (TextView) findViewById(R.id.time_text_view); 559 timeTextView.setText(formatter.format(rawResult.getTimestamp())); 560 561 562 TextView metaTextView = (TextView) findViewById(R.id.meta_text_view); 563 View metaTextViewLabel = findViewById(R.id.meta_text_view_label); 564 metaTextView.setVisibility(View.GONE); 565 metaTextViewLabel.setVisibility(View.GONE); 566 Map<ResultMetadataType,Object> metadata = rawResult.getResultMetadata(); 567 if (metadata != null) { 568 StringBuilder metadataText = new StringBuilder(20); 569 for (Map.Entry<ResultMetadataType,Object> entry : metadata.entrySet()) { 570 if (DISPLAYABLE_METADATA_TYPES.contains(entry.getKey())) { 571 metadataText.append(entry.getValue()).append('\n'); 572 } 573 } 574 if (metadataText.length() > 0) { 575 metadataText.setLength(metadataText.length() - 1); 576 metaTextView.setText(metadataText); 577 metaTextView.setVisibility(View.VISIBLE); 578 metaTextViewLabel.setVisibility(View.VISIBLE); 579 } 580 } 581 582 CharSequence displayContents = resultHandler.getDisplayContents(); 583 TextView contentsTextView = (TextView) findViewById(R.id.contents_text_view); 584 contentsTextView.setText(displayContents); 585 int scaledSize = Math.max(22, 32 - displayContents.length() / 4); 586 contentsTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, scaledSize); 587 588 TextView supplementTextView = (TextView) findViewById(R.id.contents_supplement_text_view); 589 supplementTextView.setText(""); 590 supplementTextView.setOnClickListener(null); 591 if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean( 592 PreferencesActivity.KEY_SUPPLEMENTAL, true)) { 593 SupplementalInfoRetriever.maybeInvokeRetrieval(supplementTextView, 594 resultHandler.getResult(), 595 historyManager, 596 this); 597 } 598 599 int buttonCount = resultHandler.getButtonCount(); 600 ViewGroup buttonView = (ViewGroup) findViewById(R.id.result_button_view); 601 buttonView.requestFocus(); 602 for (int x = 0; x < ResultHandler.MAX_BUTTON_COUNT; x++) { 603 TextView button = (TextView) buttonView.getChildAt(x); 604 if (x < buttonCount) { 605 button.setVisibility(View.VISIBLE); 606 button.setText(resultHandler.getButtonText(x)); 607 button.setOnClickListener(new ResultButtonListener(resultHandler, x)); 608 } else { 609 button.setVisibility(View.GONE); 610 } 611 } 612 613 } 614 615 // Briefly show the contents of the barcode, then handle the result outside Barcode Scanner. handleDecodeExternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode)616 private void handleDecodeExternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) { 617 618 if (barcode != null) { 619 viewfinderView.drawResultBitmap(barcode); 620 } 621 622 long resultDurationMS; 623 if (getIntent() == null) { 624 resultDurationMS = DEFAULT_INTENT_RESULT_DURATION_MS; 625 } else { 626 resultDurationMS = getIntent().getLongExtra(Intents.Scan.RESULT_DISPLAY_DURATION_MS, 627 DEFAULT_INTENT_RESULT_DURATION_MS); 628 } 629 630 if (resultDurationMS > 0) { 631 String rawResultString = String.valueOf(rawResult); 632 if (rawResultString.length() > 32) { 633 rawResultString = rawResultString.substring(0, 32) + " ..."; 634 } 635 statusView.setText(getString(resultHandler.getDisplayTitle()) + " : " + rawResultString); 636 } 637 638 maybeSetClipboard(resultHandler); 639 640 switch (source) { 641 case NATIVE_APP_INTENT: 642 // Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when 643 // the deprecated intent is retired. 644 Intent intent = new Intent(getIntent().getAction()); 645 intent.addFlags(Intents.FLAG_NEW_DOC); 646 intent.putExtra(Intents.Scan.RESULT, rawResult.toString()); 647 intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString()); 648 byte[] rawBytes = rawResult.getRawBytes(); 649 if (rawBytes != null && rawBytes.length > 0) { 650 intent.putExtra(Intents.Scan.RESULT_BYTES, rawBytes); 651 } 652 Map<ResultMetadataType, ?> metadata = rawResult.getResultMetadata(); 653 if (metadata != null) { 654 if (metadata.containsKey(ResultMetadataType.UPC_EAN_EXTENSION)) { 655 intent.putExtra(Intents.Scan.RESULT_UPC_EAN_EXTENSION, 656 metadata.get(ResultMetadataType.UPC_EAN_EXTENSION).toString()); 657 } 658 Number orientation = (Number) metadata.get(ResultMetadataType.ORIENTATION); 659 if (orientation != null) { 660 intent.putExtra(Intents.Scan.RESULT_ORIENTATION, orientation.intValue()); 661 } 662 String ecLevel = (String) metadata.get(ResultMetadataType.ERROR_CORRECTION_LEVEL); 663 if (ecLevel != null) { 664 intent.putExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL, ecLevel); 665 } 666 @SuppressWarnings("unchecked") 667 Iterable<byte[]> byteSegments = (Iterable<byte[]>) metadata.get(ResultMetadataType.BYTE_SEGMENTS); 668 if (byteSegments != null) { 669 int i = 0; 670 for (byte[] byteSegment : byteSegments) { 671 intent.putExtra(Intents.Scan.RESULT_BYTE_SEGMENTS_PREFIX + i, byteSegment); 672 i++; 673 } 674 } 675 } 676 sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS); 677 break; 678 679 case PRODUCT_SEARCH_LINK: 680 // Reformulate the URL which triggered us into a query, so that the request goes to the same 681 // TLD as the scan URL. 682 int end = sourceUrl.lastIndexOf("/scan"); 683 String productReplyURL = sourceUrl.substring(0, end) + "?q=" + 684 resultHandler.getDisplayContents() + "&source=zxing"; 685 sendReplyMessage(R.id.launch_product_query, productReplyURL, resultDurationMS); 686 break; 687 688 case ZXING_LINK: 689 if (scanFromWebPageManager != null && scanFromWebPageManager.isScanFromWebPage()) { 690 String linkReplyURL = scanFromWebPageManager.buildReplyURL(rawResult, resultHandler); 691 scanFromWebPageManager = null; 692 sendReplyMessage(R.id.launch_product_query, linkReplyURL, resultDurationMS); 693 } 694 break; 695 } 696 } 697 maybeSetClipboard(ResultHandler resultHandler)698 private void maybeSetClipboard(ResultHandler resultHandler) { 699 if (copyToClipboard && !resultHandler.areContentsSecure()) { 700 ClipboardInterface.setText(resultHandler.getDisplayContents(), this); 701 } 702 } 703 sendReplyMessage(int id, Object arg, long delayMS)704 private void sendReplyMessage(int id, Object arg, long delayMS) { 705 if (handler != null) { 706 Message message = Message.obtain(handler, id, arg); 707 if (delayMS > 0L) { 708 handler.sendMessageDelayed(message, delayMS); 709 } else { 710 handler.sendMessage(message); 711 } 712 } 713 } 714 initCamera(SurfaceHolder surfaceHolder)715 private void initCamera(SurfaceHolder surfaceHolder) { 716 if (surfaceHolder == null) { 717 throw new IllegalStateException("No SurfaceHolder provided"); 718 } 719 if (cameraManager.isOpen()) { 720 Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?"); 721 return; 722 } 723 try { 724 cameraManager.openDriver(surfaceHolder); 725 // Creating the handler starts the preview, which can also throw a RuntimeException. 726 if (handler == null) { 727 handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager); 728 } 729 decodeOrStoreSavedBitmap(null, null); 730 } catch (IOException ioe) { 731 Log.w(TAG, ioe); 732 displayFrameworkBugMessageAndExit(); 733 } catch (RuntimeException e) { 734 // Barcode Scanner has seen crashes in the wild of this variety: 735 // java.?lang.?RuntimeException: Fail to connect to camera service 736 Log.w(TAG, "Unexpected error initializing camera", e); 737 displayFrameworkBugMessageAndExit(); 738 } 739 } 740 displayFrameworkBugMessageAndExit()741 private void displayFrameworkBugMessageAndExit() { 742 AlertDialog.Builder builder = new AlertDialog.Builder(this); 743 builder.setTitle(getString(R.string.app_name)); 744 builder.setMessage(getString(R.string.msg_camera_framework_bug)); 745 builder.setPositiveButton(R.string.button_ok, new FinishListener(this)); 746 builder.setOnCancelListener(new FinishListener(this)); 747 builder.show(); 748 } 749 restartPreviewAfterDelay(long delayMS)750 public void restartPreviewAfterDelay(long delayMS) { 751 if (handler != null) { 752 handler.sendEmptyMessageDelayed(R.id.restart_preview, delayMS); 753 } 754 resetStatusView(); 755 } 756 resetStatusView()757 private void resetStatusView() { 758 resultView.setVisibility(View.GONE); 759 statusView.setText(R.string.msg_default_status); 760 statusView.setVisibility(View.VISIBLE); 761 viewfinderView.setVisibility(View.VISIBLE); 762 lastResult = null; 763 } 764 drawViewfinder()765 public void drawViewfinder() { 766 viewfinderView.drawViewfinder(); 767 } 768 } 769