2
* Copyright 2009 ZXing authors
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
8
* http://www.apache.org/licenses/LICENSE-2.0
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.
17
package com.google.zxing.integration.android;
19
import java.util.ArrayList;
20
import java.util.Arrays;
21
import java.util.Collection;
22
import java.util.Collections;
23
import java.util.HashMap;
24
import java.util.List;
27
import android.app.Activity;
28
import android.app.AlertDialog;
29
import android.content.ActivityNotFoundException;
30
import android.content.DialogInterface;
31
import android.content.Intent;
32
import android.content.pm.PackageManager;
33
import android.content.pm.ResolveInfo;
34
import android.net.Uri;
35
import android.os.Bundle;
36
import android.util.Log;
39
* <p>A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple
40
* way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the
41
* project's source code.</p>
43
* <h2>Initiating a barcode scan</h2>
45
* <p>To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait
46
* for the result in your app.</p>
48
* <p>It does require that the Barcode Scanner (or work-alike) application is installed. The
49
* {@link #initiateScan()} method will prompt the user to download the application, if needed.</p>
51
* <p>There are a few steps to using this integration. First, your {@link Activity} must implement
52
* the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:</p>
55
* public void onActivityResult(int requestCode, int resultCode, Intent intent) {
56
* IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
57
* if (scanResult != null) {
58
* // handle scan result
60
* // else continue with any other code you need in the method
65
* <p>This is where you will handle a scan result.</p>
67
* <p>Second, just call this in response to a user action somewhere to begin the scan process:</p>
70
* IntentIntegrator integrator = new IntentIntegrator(yourActivity);
71
* integrator.initiateScan();
74
* <p>Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the
75
* user was prompted to download the application. This lets the calling app potentially manage the dialog.
76
* In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()}
79
* <p>You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use
80
* {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and
81
* yes/no button labels can be changed.</p>
83
* <p>Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used
84
* to invoke the scanner. This can be used to set additional options not directly exposed by this
87
* <p>By default, this will only allow applications that are known to respond to this intent correctly
88
* do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}.
89
* For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.</p>
91
* <h2>Sharing text via barcode</h2>
93
* <p>To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.</p>
95
* <p>Some code, particularly download integration, was contributed from the Anobiit application.</p>
97
* <h2>Enabling experimental barcode formats</h2>
99
* <p>Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as
100
* {@link com.google.zxing.BarcodeFormat#PDF_417}. Use {@link #initiateScan(java.util.Collection)} with
101
* a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such
106
* @author Isaac Potoczny-Jones
107
* @author Brad Drehmer
110
public class IntentIntegrator {
112
public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
113
private static final String TAG = IntentIntegrator.class.getSimpleName();
115
public static final String DEFAULT_TITLE = "Install Barcode Scanner?";
116
public static final String DEFAULT_MESSAGE =
117
"This application requires Barcode Scanner. Would you like to install it?";
118
public static final String DEFAULT_YES = "Yes";
119
public static final String DEFAULT_NO = "No";
121
private static final String BS_PACKAGE = "com.google.zxing.client.android";
122
private static final String BSPLUS_PACKAGE = "com.srowen.bs.android";
124
// supported barcode formats
125
public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
126
public static final Collection<String> ONE_D_CODE_TYPES =
127
list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
128
"ITF", "RSS_14", "RSS_EXPANDED");
129
public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
130
public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");
132
public static final Collection<String> ALL_CODE_TYPES = null;
134
public static final List<String> TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE);
135
public static final List<String> TARGET_ALL_KNOWN = list(
136
BS_PACKAGE, // Barcode Scanner
137
BSPLUS_PACKAGE, // Barcode Scanner+
138
BSPLUS_PACKAGE + ".simple" // Barcode Scanner+ Simple
139
// What else supports this intent?
142
private final Activity activity;
143
private String title;
144
private String message;
145
private String buttonYes;
146
private String buttonNo;
147
private List<String> targetApplications;
148
private final Map<String,Object> moreExtras;
150
public IntentIntegrator(Activity activity) {
151
this.activity = activity;
152
title = DEFAULT_TITLE;
153
message = DEFAULT_MESSAGE;
154
buttonYes = DEFAULT_YES;
155
buttonNo = DEFAULT_NO;
156
targetApplications = TARGET_ALL_KNOWN;
157
moreExtras = new HashMap<String,Object>(3);
160
public String getTitle() {
164
public void setTitle(String title) {
168
public void setTitleByID(int titleID) {
169
title = activity.getString(titleID);
172
public String getMessage() {
176
public void setMessage(String message) {
177
this.message = message;
180
public void setMessageByID(int messageID) {
181
message = activity.getString(messageID);
184
public String getButtonYes() {
188
public void setButtonYes(String buttonYes) {
189
this.buttonYes = buttonYes;
192
public void setButtonYesByID(int buttonYesID) {
193
buttonYes = activity.getString(buttonYesID);
196
public String getButtonNo() {
200
public void setButtonNo(String buttonNo) {
201
this.buttonNo = buttonNo;
204
public void setButtonNoByID(int buttonNoID) {
205
buttonNo = activity.getString(buttonNoID);
208
public Collection<String> getTargetApplications() {
209
return targetApplications;
213
* @deprecated call {@link #setTargetApplications(List)}
216
public void setTargetApplications(Collection<String> targetApplications) {
217
List<String> list = new ArrayList<String>(targetApplications.size());
218
for (String app : targetApplications) {
221
setTargetApplications(list);
224
public final void setTargetApplications(List<String> targetApplications) {
225
if (targetApplications.isEmpty()) {
226
throw new IllegalArgumentException("No target applications");
228
this.targetApplications = targetApplications;
231
public void setSingleTargetApplication(String targetApplication) {
232
this.targetApplications = Collections.singletonList(targetApplication);
235
public Map<String,?> getMoreExtras() {
239
public final void addExtra(String key, Object value) {
240
moreExtras.put(key, value);
244
* Initiates a scan for all known barcode types.
246
public final AlertDialog initiateScan() {
247
return initiateScan(ALL_CODE_TYPES);
251
* Initiates a scan only for a certain set of barcode types, given as strings corresponding
252
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
253
* like {@link #PRODUCT_CODE_TYPES} for example.
255
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
256
* if a prompt was needed, or null otherwise
258
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) {
259
Intent intentScan = new Intent(BS_PACKAGE + ".SCAN");
260
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
262
// check which types of codes to scan for
263
if (desiredBarcodeFormats != null) {
264
// set the desired barcode types
265
StringBuilder joinedByComma = new StringBuilder();
266
for (String format : desiredBarcodeFormats) {
267
if (joinedByComma.length() > 0) {
268
joinedByComma.append(',');
270
joinedByComma.append(format);
272
intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString());
275
String targetAppPackage = findTargetAppPackage(intentScan);
276
if (targetAppPackage == null) {
277
return showDownloadDialog();
279
intentScan.setPackage(targetAppPackage);
280
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
281
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
282
attachMoreExtras(intentScan);
283
startActivityForResult(intentScan, REQUEST_CODE);
288
* Start an activity.<br>
289
* This method is defined to allow different methods of activity starting for
290
* newer versions of Android and for compatibility library.
292
* @param intent Intent to start.
293
* @param code Request code for the activity
294
* @see android.app.Activity#startActivityForResult(Intent, int)
295
* @see android.app.Fragment#startActivityForResult(Intent, int)
297
protected void startActivityForResult(Intent intent, int code) {
298
activity.startActivityForResult(intent, code);
301
private String findTargetAppPackage(Intent intent) {
302
PackageManager pm = activity.getPackageManager();
303
List<ResolveInfo> availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
304
if (availableApps != null) {
305
for (ResolveInfo availableApp : availableApps) {
306
String packageName = availableApp.activityInfo.packageName;
307
if (targetApplications.contains(packageName)) {
315
private AlertDialog showDownloadDialog() {
316
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
317
downloadDialog.setTitle(title);
318
downloadDialog.setMessage(message);
319
downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() {
321
public void onClick(DialogInterface dialogInterface, int i) {
322
String packageName = targetApplications.get(0);
323
Uri uri = Uri.parse("market://details?id=" + packageName);
324
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
326
activity.startActivity(intent);
327
} catch (ActivityNotFoundException anfe) {
328
// Hmm, market is not installed
329
Log.w(TAG, "Google Play is not installed; cannot install " + packageName);
333
downloadDialog.setNegativeButton(buttonNo, new DialogInterface.OnClickListener() {
335
public void onClick(DialogInterface dialogInterface, int i) {}
337
return downloadDialog.show();
342
* <p>Call this from your {@link Activity}'s
343
* {@link Activity#onActivityResult(int, int, Intent)} method.</p>
345
* @return null if the event handled here was not related to this class, or
346
* else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
347
* the fields will be null.
349
public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
350
if (requestCode == REQUEST_CODE) {
351
if (resultCode == Activity.RESULT_OK) {
352
String contents = intent.getStringExtra("SCAN_RESULT");
353
String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT");
354
byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES");
355
int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE);
356
Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
357
String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL");
358
return new IntentResult(contents,
362
errorCorrectionLevel);
364
return new IntentResult();
371
* Defaults to type "TEXT_TYPE".
372
* @see #shareText(CharSequence, CharSequence)
374
public final AlertDialog shareText(CharSequence text) {
375
return shareText(text, "TEXT_TYPE");
379
* Shares the given text by encoding it as a barcode, such that another user can
380
* scan the text off the screen of the device.
382
* @param text the text string to encode as a barcode
383
* @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants.
384
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
385
* if a prompt was needed, or null otherwise
387
public final AlertDialog shareText(CharSequence text, CharSequence type) {
388
Intent intent = new Intent();
389
intent.addCategory(Intent.CATEGORY_DEFAULT);
390
intent.setAction(BS_PACKAGE + ".ENCODE");
391
intent.putExtra("ENCODE_TYPE", type);
392
intent.putExtra("ENCODE_DATA", text);
393
String targetAppPackage = findTargetAppPackage(intent);
394
if (targetAppPackage == null) {
395
return showDownloadDialog();
397
intent.setPackage(targetAppPackage);
398
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
399
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
400
attachMoreExtras(intent);
401
activity.startActivity(intent);
405
private static List<String> list(String... values) {
406
return Collections.unmodifiableList(Arrays.asList(values));
409
private void attachMoreExtras(Intent intent) {
410
for (Map.Entry<String,Object> entry : moreExtras.entrySet()) {
411
String key = entry.getKey();
412
Object value = entry.getValue();
414
if (value instanceof Integer) {
415
intent.putExtra(key, (Integer) value);
416
} else if (value instanceof Long) {
417
intent.putExtra(key, (Long) value);
418
} else if (value instanceof Boolean) {
419
intent.putExtra(key, (Boolean) value);
420
} else if (value instanceof Double) {
421
intent.putExtra(key, (Double) value);
422
} else if (value instanceof Float) {
423
intent.putExtra(key, (Float) value);
424
} else if (value instanceof Bundle) {
425
intent.putExtra(key, (Bundle) value);
427
intent.putExtra(key, value.toString());