2 * Copyright (C) 2008 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.client.android.encode;
19 import com.google.zxing.BarcodeFormat;
20 import com.google.zxing.MultiFormatWriter;
21 import com.google.zxing.Result;
22 import com.google.zxing.WriterException;
23 import com.google.zxing.client.android.Contents;
24 import com.google.zxing.client.android.Intents;
25 import com.google.zxing.client.android.R;
26 import com.google.zxing.client.result.AddressBookParsedResult;
27 import com.google.zxing.client.result.ParsedResult;
28 import com.google.zxing.client.result.ResultParser;
29 import com.google.zxing.common.BitMatrix;
31 import android.app.Activity;
32 import android.content.Intent;
33 import android.graphics.Bitmap;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.Message;
38 import android.provider.Contacts;
39 import android.telephony.PhoneNumberUtils;
40 import android.util.Log;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.io.InputStream;
47 * This class does the work of decoding the user's request and extracting all the data
48 * to be encoded in a barcode.
50 * @author dswitkin@google.com (Daniel Switkin)
52 final class QRCodeEncoder {
54 private static final String TAG = "QRCodeEncoder";
56 private static final int WHITE = 0xFFFFFFFF;
58 private final Activity activity;
59 private String contents;
60 private String displayContents;
62 private BarcodeFormat format;
64 QRCodeEncoder(Activity activity, Intent intent) {
65 this.activity = activity;
67 throw new IllegalArgumentException("No valid data to encode.");
70 String action = intent.getAction();
71 if (action.equals(Intents.Encode.ACTION)) {
72 if (!encodeContentsFromZXingIntent(intent)) {
73 throw new IllegalArgumentException("No valid data to encode.");
75 } else if (action.equals(Intent.ACTION_SEND)) {
76 if (!encodeContentsFromShareIntent(intent)) {
77 throw new IllegalArgumentException("No valid data to encode.");
82 public void requestBarcode(Handler handler, int pixelResolution) {
83 Thread encodeThread = new EncodeThread(contents, handler, pixelResolution,
88 public String getContents() {
92 public String getDisplayContents() {
93 return displayContents;
96 public String getTitle() {
100 public String getFormat() {
101 return format.toString();
104 // It would be nice if the string encoding lived in the core ZXing library,
105 // but we use platform specific code like PhoneNumberUtils, so it can't.
106 private boolean encodeContentsFromZXingIntent(Intent intent) {
107 // Default to QR_CODE if no format given.
108 String format = intent.getStringExtra(Intents.Encode.FORMAT);
109 if (format == null || format.length() == 0 ||
110 format.equals(Contents.Format.QR_CODE)) {
111 String type = intent.getStringExtra(Intents.Encode.TYPE);
112 if (type == null || type.length() == 0) {
115 this.format = BarcodeFormat.QR_CODE;
116 encodeQRCodeContents(intent, type);
118 String data = intent.getStringExtra(Intents.Encode.DATA);
119 if (data != null && data.length() != 0) {
121 displayContents = data;
122 title = activity.getString(R.string.contents_text);
123 if (format.equals(Contents.Format.CODE_128)) {
124 this.format = BarcodeFormat.CODE_128;
125 } else if (format.equals(Contents.Format.CODE_39)) {
126 this.format = BarcodeFormat.CODE_39;
127 } else if (format.equals(Contents.Format.EAN_8)) {
128 this.format = BarcodeFormat.EAN_8;
129 } else if (format.equals(Contents.Format.EAN_13)) {
130 this.format = BarcodeFormat.EAN_13;
131 } else if (format.equals(Contents.Format.UPC_A)) {
132 this.format = BarcodeFormat.UPC_A;
133 } else if (format.equals(Contents.Format.UPC_E)) {
134 this.format = BarcodeFormat.UPC_E;
138 return contents != null && contents.length() > 0;
141 // Handles send intents from the Contacts app, retrieving a contact as a VCARD.
142 private boolean encodeContentsFromShareIntent(Intent intent) {
143 format = BarcodeFormat.QR_CODE;
145 Uri uri = (Uri)intent.getExtras().getParcelable(Intent.EXTRA_STREAM);
146 InputStream stream = activity.getContentResolver().openInputStream(uri);
147 int length = stream.available();
148 byte[] vcard = new byte[length];
149 stream.read(vcard, 0, length);
150 String vcardString = new String(vcard, "UTF-8");
151 Log.d(TAG, "Encoding share intent content: " + vcardString);
152 Result result = new Result(vcardString, vcard, null, BarcodeFormat.QR_CODE);
153 ParsedResult parsedResult = ResultParser.parseResult(result);
154 if (!(parsedResult instanceof AddressBookParsedResult)) {
157 if (!encodeQRCodeContents((AddressBookParsedResult) parsedResult)) {
160 } catch (FileNotFoundException e) {
162 } catch (IOException e) {
164 } catch (NullPointerException e) {
165 // In case the uri was not found in the Intent.
168 return contents != null && contents.length() > 0;
171 private void encodeQRCodeContents(Intent intent, String type) {
172 if (type.equals(Contents.Type.TEXT)) {
173 String data = intent.getStringExtra(Intents.Encode.DATA);
174 if (data != null && data.length() > 0) {
176 displayContents = data;
177 title = activity.getString(R.string.contents_text);
179 } else if (type.equals(Contents.Type.EMAIL)) {
180 String data = intent.getStringExtra(Intents.Encode.DATA);
181 if (data != null && data.length() > 0) {
182 contents = "mailto:" + data;
183 displayContents = data;
184 title = activity.getString(R.string.contents_email);
186 } else if (type.equals(Contents.Type.PHONE)) {
187 String data = intent.getStringExtra(Intents.Encode.DATA);
188 if (data != null && data.length() > 0) {
189 contents = "tel:" + data;
190 displayContents = PhoneNumberUtils.formatNumber(data);
191 title = activity.getString(R.string.contents_phone);
193 } else if (type.equals(Contents.Type.SMS)) {
194 String data = intent.getStringExtra(Intents.Encode.DATA);
195 if (data != null && data.length() > 0) {
196 contents = "sms:" + data;
197 displayContents = PhoneNumberUtils.formatNumber(data);
198 title = activity.getString(R.string.contents_sms);
200 } else if (type.equals(Contents.Type.CONTACT)) {
201 Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
202 if (bundle != null) {
203 StringBuilder newContents = new StringBuilder();
204 StringBuilder newDisplayContents = new StringBuilder();
205 newContents.append("MECARD:");
206 String name = bundle.getString(Contacts.Intents.Insert.NAME);
207 if (name != null && name.length() > 0) {
208 newContents.append("N:").append(name).append(';');
209 newDisplayContents.append(name);
211 String address = bundle.getString(Contacts.Intents.Insert.POSTAL);
212 if (address != null && address.length() > 0) {
213 newContents.append("ADR:").append(address).append(';');
214 newDisplayContents.append('\n').append(address);
216 for (int x = 0; x < Contents.PHONE_KEYS.length; x++) {
217 String phone = bundle.getString(Contents.PHONE_KEYS[x]);
218 if (phone != null && phone.length() > 0) {
219 newContents.append("TEL:").append(phone).append(';');
220 newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
223 for (int x = 0; x < Contents.EMAIL_KEYS.length; x++) {
224 String email = bundle.getString(Contents.EMAIL_KEYS[x]);
225 if (email != null && email.length() > 0) {
226 newContents.append("EMAIL:").append(email).append(';');
227 newDisplayContents.append('\n').append(email);
230 // Make sure we've encoded at least one field.
231 if (newDisplayContents.length() > 0) {
232 newContents.append(';');
233 contents = newContents.toString();
234 displayContents = newDisplayContents.toString();
235 title = activity.getString(R.string.contents_contact);
238 displayContents = null;
241 } else if (type.equals(Contents.Type.LOCATION)) {
242 Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
243 if (bundle != null) {
244 // These must use Bundle.getFloat(), not getDouble(), it's part of the API.
245 float latitude = bundle.getFloat("LAT", Float.MAX_VALUE);
246 float longitude = bundle.getFloat("LONG", Float.MAX_VALUE);
247 if (latitude != Float.MAX_VALUE && longitude != Float.MAX_VALUE) {
248 contents = "geo:" + latitude + ',' + longitude;
249 displayContents = latitude + "," + longitude;
250 title = activity.getString(R.string.contents_location);
256 private boolean encodeQRCodeContents(AddressBookParsedResult contact) {
257 StringBuilder newContents = new StringBuilder();
258 StringBuilder newDisplayContents = new StringBuilder();
259 newContents.append("MECARD:");
260 String[] names = contact.getNames();
261 if (names != null && names.length > 0) {
262 newContents.append("N:").append(names[0]).append(';');
263 newDisplayContents.append(names[0]);
265 String[] addresses = contact.getAddresses();
266 if (addresses != null) {
267 for (int x = 0; x < addresses.length; x++) {
268 if (addresses[x] != null && addresses[x].length() > 0) {
269 newContents.append("ADR:").append(addresses[x]).append(';');
270 newDisplayContents.append('\n').append(addresses[x]);
274 String[] phoneNumbers = contact.getPhoneNumbers();
275 if (phoneNumbers != null) {
276 for (int x = 0; x < phoneNumbers.length; x++) {
277 String phone = phoneNumbers[x];
278 if (phone != null && phone.length() > 0) {
279 newContents.append("TEL:").append(phone).append(';');
280 newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
284 String[] emails = contact.getEmails();
285 if (emails != null) {
286 for (int x = 0; x < emails.length; x++) {
287 String email = emails[x];
288 if (email != null && email.length() > 0) {
289 newContents.append("EMAIL:").append(email).append(';');
290 newDisplayContents.append('\n').append(email);
294 String url = contact.getURL();
295 if (url != null && url.length() > 0) {
296 newContents.append("URL:").append(url).append(';');
297 newDisplayContents.append('\n').append(url);
299 // Make sure we've encoded at least one field.
300 if (newDisplayContents.length() > 0) {
301 newContents.append(';');
302 contents = newContents.toString();
303 displayContents = newDisplayContents.toString();
304 title = activity.getString(R.string.contents_contact);
308 displayContents = null;
313 private static final class EncodeThread extends Thread {
314 private static final String TAG = "EncodeThread";
316 private final String contents;
317 private final Handler handler;
318 private final int pixelResolution;
319 private final BarcodeFormat format;
321 EncodeThread(String contents, Handler handler, int pixelResolution,
322 BarcodeFormat format) {
323 this.contents = contents;
324 this.handler = handler;
325 this.pixelResolution = pixelResolution;
326 this.format = format;
332 BitMatrix result = new MultiFormatWriter().encode(contents, format,
333 pixelResolution, pixelResolution);
334 int width = result.getWidth();
335 int height = result.getHeight();
336 int[] pixels = new int[width * height];
337 // All are 0, or black, by default
338 for (int y = 0; y < height; y++) {
339 int offset = y * width;
340 for (int x = 0; x < width; x++) {
341 if (!result.get(x, y)) {
342 pixels[offset + x] = WHITE;
347 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
348 bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
349 Message message = Message.obtain(handler, R.id.encode_succeeded);
350 message.obj = bitmap;
351 message.sendToTarget();
352 } catch (WriterException e) {
353 Log.e(TAG, e.toString());
354 Message message = Message.obtain(handler, R.id.encode_failed);
355 message.sendToTarget();
356 } catch (IllegalArgumentException e) {
357 Log.e(TAG, e.toString());
358 Message message = Message.obtain(handler, R.id.encode_failed);
359 message.sendToTarget();