~w-shackleton/droidpad-android/stable

« back to all changes in this revision

Viewing changes to src/com/google/zxing/integration/android/IntentIntegrator.java

  • Committer: William Shackleton
  • Date: 2013-04-03 20:21:22 UTC
  • mfrom: (51.2.10 secure)
  • mto: This revision was merged to the branch mainline in revision 52.
  • Revision ID: w.shackleton@gmail.com-20130403202122-w9xcr2y1qifvh45s
Merged support for TLS connections. Still a few bugs.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2009 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.integration.android;
 
18
 
 
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;
 
25
import java.util.Map;
 
26
 
 
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;
 
37
 
 
38
/**
 
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>
 
42
 *
 
43
 * <h2>Initiating a barcode scan</h2>
 
44
 *
 
45
 * <p>To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait
 
46
 * for the result in your app.</p>
 
47
 *
 
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>
 
50
 *
 
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>
 
53
 *
 
54
 * <pre>{@code
 
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
 
59
 *   }
 
60
 *   // else continue with any other code you need in the method
 
61
 *   ...
 
62
 * }
 
63
 * }</pre>
 
64
 *
 
65
 * <p>This is where you will handle a scan result.</p>
 
66
 *
 
67
 * <p>Second, just call this in response to a user action somewhere to begin the scan process:</p>
 
68
 *
 
69
 * <pre>{@code
 
70
 * IntentIntegrator integrator = new IntentIntegrator(yourActivity);
 
71
 * integrator.initiateScan();
 
72
 * }</pre>
 
73
 *
 
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()}
 
77
 * method.</p>
 
78
 * 
 
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>
 
82
 *
 
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
 
85
 * simplified API.</p>
 
86
 * 
 
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>
 
90
 *
 
91
 * <h2>Sharing text via barcode</h2>
 
92
 *
 
93
 * <p>To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.</p>
 
94
 *
 
95
 * <p>Some code, particularly download integration, was contributed from the Anobiit application.</p>
 
96
 *
 
97
 * <h2>Enabling experimental barcode formats</h2>
 
98
 *
 
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
 
102
 * formats.</p>
 
103
 *
 
104
 * @author Sean Owen
 
105
 * @author Fred Lin
 
106
 * @author Isaac Potoczny-Jones
 
107
 * @author Brad Drehmer
 
108
 * @author gcstang
 
109
 */
 
110
public class IntentIntegrator {
 
111
 
 
112
  public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
 
113
  private static final String TAG = IntentIntegrator.class.getSimpleName();
 
114
 
 
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";
 
120
 
 
121
  private static final String BS_PACKAGE = "com.google.zxing.client.android";
 
122
  private static final String BSPLUS_PACKAGE = "com.srowen.bs.android";
 
123
 
 
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");
 
131
 
 
132
  public static final Collection<String> ALL_CODE_TYPES = null;
 
133
  
 
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?
 
140
      );
 
141
  
 
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;
 
149
  
 
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);
 
158
  }
 
159
  
 
160
  public String getTitle() {
 
161
    return title;
 
162
  }
 
163
  
 
164
  public void setTitle(String title) {
 
165
    this.title = title;
 
166
  }
 
167
 
 
168
  public void setTitleByID(int titleID) {
 
169
    title = activity.getString(titleID);
 
170
  }
 
171
 
 
172
  public String getMessage() {
 
173
    return message;
 
174
  }
 
175
 
 
176
  public void setMessage(String message) {
 
177
    this.message = message;
 
178
  }
 
179
 
 
180
  public void setMessageByID(int messageID) {
 
181
    message = activity.getString(messageID);
 
182
  }
 
183
 
 
184
  public String getButtonYes() {
 
185
    return buttonYes;
 
186
  }
 
187
 
 
188
  public void setButtonYes(String buttonYes) {
 
189
    this.buttonYes = buttonYes;
 
190
  }
 
191
 
 
192
  public void setButtonYesByID(int buttonYesID) {
 
193
    buttonYes = activity.getString(buttonYesID);
 
194
  }
 
195
 
 
196
  public String getButtonNo() {
 
197
    return buttonNo;
 
198
  }
 
199
 
 
200
  public void setButtonNo(String buttonNo) {
 
201
    this.buttonNo = buttonNo;
 
202
  }
 
203
 
 
204
  public void setButtonNoByID(int buttonNoID) {
 
205
    buttonNo = activity.getString(buttonNoID);
 
206
  }
 
207
  
 
208
  public Collection<String> getTargetApplications() {
 
209
    return targetApplications;
 
210
  }
 
211
 
 
212
  /**
 
213
   * @deprecated call {@link #setTargetApplications(List)}
 
214
   */
 
215
  @Deprecated
 
216
  public void setTargetApplications(Collection<String> targetApplications) {
 
217
    List<String> list = new ArrayList<String>(targetApplications.size());
 
218
    for (String app : targetApplications) {
 
219
      list.add(app);
 
220
    }
 
221
    setTargetApplications(list);
 
222
  }
 
223
  
 
224
  public final void setTargetApplications(List<String> targetApplications) {
 
225
    if (targetApplications.isEmpty()) {
 
226
      throw new IllegalArgumentException("No target applications");
 
227
    }
 
228
    this.targetApplications = targetApplications;
 
229
  }
 
230
  
 
231
  public void setSingleTargetApplication(String targetApplication) {
 
232
    this.targetApplications = Collections.singletonList(targetApplication);
 
233
  }
 
234
 
 
235
  public Map<String,?> getMoreExtras() {
 
236
    return moreExtras;
 
237
  }
 
238
 
 
239
  public final void addExtra(String key, Object value) {
 
240
    moreExtras.put(key, value);
 
241
  }
 
242
 
 
243
  /**
 
244
   * Initiates a scan for all known barcode types.
 
245
   */
 
246
  public final AlertDialog initiateScan() {
 
247
    return initiateScan(ALL_CODE_TYPES);
 
248
  }
 
249
 
 
250
  /**
 
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.
 
254
   *
 
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
 
257
   */
 
258
  public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) {
 
259
    Intent intentScan = new Intent(BS_PACKAGE + ".SCAN");
 
260
    intentScan.addCategory(Intent.CATEGORY_DEFAULT);
 
261
 
 
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(',');
 
269
        }
 
270
        joinedByComma.append(format);
 
271
      }
 
272
      intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString());
 
273
    }
 
274
 
 
275
    String targetAppPackage = findTargetAppPackage(intentScan);
 
276
    if (targetAppPackage == null) {
 
277
      return showDownloadDialog();
 
278
    }
 
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);
 
284
    return null;
 
285
  }
 
286
 
 
287
  /**
 
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.
 
291
   *
 
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)
 
296
   */
 
297
  protected void startActivityForResult(Intent intent, int code) {
 
298
    activity.startActivityForResult(intent, code);
 
299
  }
 
300
  
 
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)) {
 
308
          return packageName;
 
309
        }
 
310
      }
 
311
    }
 
312
    return null;
 
313
  }
 
314
 
 
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() {
 
320
      @Override
 
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);
 
325
        try {
 
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);
 
330
        }
 
331
      }
 
332
    });
 
333
    downloadDialog.setNegativeButton(buttonNo, new DialogInterface.OnClickListener() {
 
334
      @Override
 
335
      public void onClick(DialogInterface dialogInterface, int i) {}
 
336
    });
 
337
    return downloadDialog.show();
 
338
  }
 
339
 
 
340
 
 
341
  /**
 
342
   * <p>Call this from your {@link Activity}'s
 
343
   * {@link Activity#onActivityResult(int, int, Intent)} method.</p>
 
344
   *
 
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.
 
348
   */
 
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,
 
359
                                formatName,
 
360
                                rawBytes,
 
361
                                orientation,
 
362
                                errorCorrectionLevel);
 
363
      }
 
364
      return new IntentResult();
 
365
    }
 
366
    return null;
 
367
  }
 
368
 
 
369
 
 
370
  /**
 
371
   * Defaults to type "TEXT_TYPE".
 
372
   * @see #shareText(CharSequence, CharSequence)
 
373
   */
 
374
  public final AlertDialog shareText(CharSequence text) {
 
375
    return shareText(text, "TEXT_TYPE");
 
376
  }
 
377
 
 
378
  /**
 
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.
 
381
   *
 
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
 
386
   */
 
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();
 
396
    }
 
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);
 
402
    return null;
 
403
  }
 
404
  
 
405
  private static List<String> list(String... values) {
 
406
    return Collections.unmodifiableList(Arrays.asList(values));
 
407
  }
 
408
 
 
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();
 
413
      // Kind of hacky
 
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);
 
426
      } else {
 
427
        intent.putExtra(key, value.toString());
 
428
      }
 
429
    }
 
430
  }
 
431
 
 
432
}