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.ByteMatrix;
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 {
53 private final Activity activity;
54 private String contents;
55 private String displayContents;
57 private BarcodeFormat format;
59 public QRCodeEncoder(Activity activity, Intent intent) {
60 this.activity = activity;
62 throw new IllegalArgumentException("No valid data to encode.");
65 String action = intent.getAction();
66 if (action.equals(Intents.Encode.ACTION)) {
67 if (!encodeContentsFromZXingIntent(intent)) {
68 throw new IllegalArgumentException("No valid data to encode.");
70 } else if (action.equals(Intent.ACTION_SEND)) {
71 if (!encodeContentsFromShareIntent(intent)) {
72 throw new IllegalArgumentException("No valid data to encode.");
77 public void requestBarcode(Handler handler, int pixelResolution) {
78 Thread encodeThread = new EncodeThread(contents, handler, pixelResolution,
83 public String getContents() {
87 public String getDisplayContents() {
88 return displayContents;
91 public String getTitle() {
95 public String getFormat() {
96 return format.toString();
99 // It would be nice if the string encoding lived in the core ZXing library,
100 // but we use platform specific code like PhoneNumberUtils, so it can't.
101 private boolean encodeContentsFromZXingIntent(Intent intent) {
102 // Default to QR_CODE if no format given.
103 String format = intent.getStringExtra(Intents.Encode.FORMAT);
104 if (format == null || format.length() == 0 ||
105 format.equals(Contents.Format.QR_CODE)) {
106 String type = intent.getStringExtra(Intents.Encode.TYPE);
107 if (type == null || type.length() == 0) {
110 this.format = BarcodeFormat.QR_CODE;
111 encodeQRCodeContents(intent, type);
113 String data = intent.getStringExtra(Intents.Encode.DATA);
114 if (data != null && data.length() != 0) {
116 displayContents = data;
117 title = activity.getString(R.string.contents_text);
118 if (format.equals(Contents.Format.CODE_128)) {
119 this.format = BarcodeFormat.CODE_128;
120 } else if (format.equals(Contents.Format.CODE_39)) {
121 this.format = BarcodeFormat.CODE_39;
122 } else if (format.equals(Contents.Format.EAN_8)) {
123 this.format = BarcodeFormat.EAN_8;
124 } else if (format.equals(Contents.Format.EAN_13)) {
125 this.format = BarcodeFormat.EAN_13;
126 } else if (format.equals(Contents.Format.UPC_A)) {
127 this.format = BarcodeFormat.UPC_A;
128 } else if (format.equals(Contents.Format.UPC_E)) {
129 this.format = BarcodeFormat.UPC_E;
133 return contents != null && contents.length() > 0;
136 // Handles send intents from the Contacts app, retrieving a contact as a VCARD.
137 private boolean encodeContentsFromShareIntent(Intent intent) {
138 format = BarcodeFormat.QR_CODE;
140 Uri uri = (Uri)intent.getExtras().getParcelable(Intent.EXTRA_STREAM);
141 InputStream stream = activity.getContentResolver().openInputStream(uri);
142 int length = stream.available();
143 byte[] vcard = new byte[length];
144 stream.read(vcard, 0, length);
145 String vcardString = new String(vcard, "utf-8");
146 Result result = new Result(vcardString, vcard, null, BarcodeFormat.QR_CODE);
147 ParsedResult parsedResult = ResultParser.parseResult(result);
148 if (!(parsedResult instanceof AddressBookParsedResult)) {
151 if (!encodeQRCodeContents((AddressBookParsedResult) parsedResult)) {
154 } catch (FileNotFoundException e) {
156 } catch (IOException e) {
159 return contents != null && contents.length() > 0;
162 private void encodeQRCodeContents(Intent intent, String type) {
163 if (type.equals(Contents.Type.TEXT)) {
164 String data = intent.getStringExtra(Intents.Encode.DATA);
165 if (data != null && data.length() > 0) {
167 displayContents = data;
168 title = activity.getString(R.string.contents_text);
170 } else if (type.equals(Contents.Type.EMAIL)) {
171 String data = intent.getStringExtra(Intents.Encode.DATA);
172 if (data != null && data.length() > 0) {
173 contents = "mailto:" + data;
174 displayContents = data;
175 title = activity.getString(R.string.contents_email);
177 } else if (type.equals(Contents.Type.PHONE)) {
178 String data = intent.getStringExtra(Intents.Encode.DATA);
179 if (data != null && data.length() > 0) {
180 contents = "tel:" + data;
181 displayContents = PhoneNumberUtils.formatNumber(data);
182 title = activity.getString(R.string.contents_phone);
184 } else if (type.equals(Contents.Type.SMS)) {
185 String data = intent.getStringExtra(Intents.Encode.DATA);
186 if (data != null && data.length() > 0) {
187 contents = "sms:" + data;
188 displayContents = PhoneNumberUtils.formatNumber(data);
189 title = activity.getString(R.string.contents_sms);
191 } else if (type.equals(Contents.Type.CONTACT)) {
192 Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
193 if (bundle != null) {
194 StringBuilder newContents = new StringBuilder();
195 StringBuilder newDisplayContents = new StringBuilder();
196 newContents.append("MECARD:");
197 String name = bundle.getString(Contacts.Intents.Insert.NAME);
198 if (name != null && name.length() > 0) {
199 newContents.append("N:").append(name).append(';');
200 newDisplayContents.append(name);
202 String address = bundle.getString(Contacts.Intents.Insert.POSTAL);
203 if (address != null && address.length() > 0) {
204 newContents.append("ADR:").append(address).append(';');
205 newDisplayContents.append('\n').append(address);
207 for (int x = 0; x < Contents.PHONE_KEYS.length; x++) {
208 String phone = bundle.getString(Contents.PHONE_KEYS[x]);
209 if (phone != null && phone.length() > 0) {
210 newContents.append("TEL:").append(phone).append(';');
211 newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
214 for (int x = 0; x < Contents.EMAIL_KEYS.length; x++) {
215 String email = bundle.getString(Contents.EMAIL_KEYS[x]);
216 if (email != null && email.length() > 0) {
217 newContents.append("EMAIL:").append(email).append(';');
218 newDisplayContents.append('\n').append(email);
221 // Make sure we've encoded at least one field.
222 if (newDisplayContents.length() > 0) {
223 newContents.append(';');
224 contents = newContents.toString();
225 displayContents = newDisplayContents.toString();
226 title = activity.getString(R.string.contents_contact);
229 displayContents = null;
232 } else if (type.equals(Contents.Type.LOCATION)) {
233 Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
234 if (bundle != null) {
235 // These must use Bundle.getFloat(), not getDouble(), it's part of the API.
236 float latitude = bundle.getFloat("LAT", Float.MAX_VALUE);
237 float longitude = bundle.getFloat("LONG", Float.MAX_VALUE);
238 if (latitude != Float.MAX_VALUE && longitude != Float.MAX_VALUE) {
239 contents = "geo:" + latitude + ',' + longitude;
240 displayContents = latitude + "," + longitude;
241 title = activity.getString(R.string.contents_location);
247 private boolean encodeQRCodeContents(AddressBookParsedResult contact) {
248 StringBuilder newContents = new StringBuilder();
249 StringBuilder newDisplayContents = new StringBuilder();
250 newContents.append("MECARD:");
251 String[] names = contact.getNames();
252 if (names != null && names.length > 0) {
253 newContents.append("N:").append(names[0]).append(';');
254 newDisplayContents.append(names[0]);
256 String address = contact.getAddress();
257 if (address != null && address.length() > 0) {
258 newContents.append("ADR:").append(address).append(';');
259 newDisplayContents.append('\n').append(address);
261 String[] phoneNumbers = contact.getPhoneNumbers();
262 if (phoneNumbers != null) {
263 for (int x = 0; x < phoneNumbers.length; x++) {
264 String phone = phoneNumbers[x];
265 if (phone != null && phone.length() > 0) {
266 newContents.append("TEL:").append(phone).append(';');
267 newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
271 String[] emails = contact.getEmails();
272 if (emails != null) {
273 for (int x = 0; x < emails.length; x++) {
274 String email = emails[x];
275 if (email != null && email.length() > 0) {
276 newContents.append("EMAIL:").append(email).append(';');
277 newDisplayContents.append('\n').append(email);
281 String url = contact.getURL();
282 if (url != null && url.length() > 0) {
283 newContents.append("URL:").append(url).append(';');
284 newDisplayContents.append('\n').append(url);
286 // Make sure we've encoded at least one field.
287 if (newDisplayContents.length() > 0) {
288 newContents.append(';');
289 contents = newContents.toString();
290 displayContents = newDisplayContents.toString();
291 title = activity.getString(R.string.contents_contact);
295 displayContents = null;
300 private static final class EncodeThread extends Thread {
301 private static final String TAG = "EncodeThread";
303 private final String contents;
304 private final Handler handler;
305 private final int pixelResolution;
306 private final BarcodeFormat format;
308 EncodeThread(String contents, Handler handler, int pixelResolution,
309 BarcodeFormat format) {
310 this.contents = contents;
311 this.handler = handler;
312 this.pixelResolution = pixelResolution;
313 this.format = format;
319 ByteMatrix result = new MultiFormatWriter().encode(contents, format,
320 pixelResolution, pixelResolution);
321 int width = result.getWidth();
322 int height = result.getHeight();
323 byte[][] array = result.getArray();
324 int[] pixels = new int[width * height];
325 for (int y = 0; y < height; y++) {
326 for (int x = 0; x < width; x++) {
327 int grey = array[y][x] & 0xff;
328 // pixels[y * width + x] = (0xff << 24) | (grey << 16) | (grey << 8) | grey;
329 pixels[y * width + x] = 0xff000000 | (0x00010101 * grey);
333 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
334 bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
335 Message message = Message.obtain(handler, R.id.encode_succeeded);
336 message.obj = bitmap;
337 message.sendToTarget();
338 } catch (WriterException e) {
339 Log.e(TAG, e.toString());
340 Message message = Message.obtain(handler, R.id.encode_failed);
341 message.sendToTarget();
342 } catch (IllegalArgumentException e) {
343 Log.e(TAG, e.toString());
344 Message message = Message.obtain(handler, R.id.encode_failed);
345 message.sendToTarget();