599e40f5b338157062c8ae2d782350a6dd9fd883
[zxing.git] / android / src / com / google / zxing / client / android / encode / QRCodeEncoder.java
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.encode;
18
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;
30
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;
41
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.io.InputStream;
45
46 /**
47  * This class does the work of decoding the user's request and extracting all the data
48  * to be encoded in a barcode.
49  *
50  * @author dswitkin@google.com (Daniel Switkin)
51  */
52 final class QRCodeEncoder {
53
54   private static final String TAG = "QRCodeEncoder";
55
56   private final Activity activity;
57   private String contents;
58   private String displayContents;
59   private String title;
60   private BarcodeFormat format;
61
62   QRCodeEncoder(Activity activity, Intent intent) {
63     this.activity = activity;
64     if (intent == null) {
65       throw new IllegalArgumentException("No valid data to encode.");
66     }
67
68     String action = intent.getAction();
69     if (action.equals(Intents.Encode.ACTION)) {
70       if (!encodeContentsFromZXingIntent(intent)) {
71         throw new IllegalArgumentException("No valid data to encode.");
72       }
73     } else if (action.equals(Intent.ACTION_SEND)) {
74       if (!encodeContentsFromShareIntent(intent)) {
75         throw new IllegalArgumentException("No valid data to encode.");
76       }
77     }
78   }
79
80   public void requestBarcode(Handler handler, int pixelResolution) {
81     Thread encodeThread = new EncodeThread(contents, handler, pixelResolution,
82         format);
83     encodeThread.start();
84   }
85
86   public String getContents() {
87     return contents;
88   }
89
90   public String getDisplayContents() {
91     return displayContents;
92   }
93
94   public String getTitle() {
95     return title;
96   }
97
98   public String getFormat() {
99     return format.toString();
100   }
101
102   // It would be nice if the string encoding lived in the core ZXing library,
103   // but we use platform specific code like PhoneNumberUtils, so it can't.
104   private boolean encodeContentsFromZXingIntent(Intent intent) {
105      // Default to QR_CODE if no format given.
106     String format = intent.getStringExtra(Intents.Encode.FORMAT);
107     if (format == null || format.length() == 0 ||
108         format.equals(Contents.Format.QR_CODE)) {
109       String type = intent.getStringExtra(Intents.Encode.TYPE);
110       if (type == null || type.length() == 0) {
111         return false;
112       }
113       this.format = BarcodeFormat.QR_CODE;
114       encodeQRCodeContents(intent, type);
115     } else {
116       String data = intent.getStringExtra(Intents.Encode.DATA);
117       if (data != null && data.length() != 0) {
118         contents = data;
119         displayContents = data;
120         title = activity.getString(R.string.contents_text);
121         if (format.equals(Contents.Format.CODE_128)) {
122           this.format = BarcodeFormat.CODE_128;
123         } else if (format.equals(Contents.Format.CODE_39)) {
124           this.format = BarcodeFormat.CODE_39;
125         } else if (format.equals(Contents.Format.EAN_8)) {
126           this.format = BarcodeFormat.EAN_8;
127         } else if (format.equals(Contents.Format.EAN_13)) {
128           this.format = BarcodeFormat.EAN_13;
129         } else if (format.equals(Contents.Format.UPC_A)) {
130           this.format = BarcodeFormat.UPC_A;
131         } else if (format.equals(Contents.Format.UPC_E)) {
132           this.format = BarcodeFormat.UPC_E;
133         }
134       }
135     }
136     return contents != null && contents.length() > 0;
137   }
138
139   // Handles send intents from the Contacts app, retrieving a contact as a VCARD.
140   private boolean encodeContentsFromShareIntent(Intent intent) {
141     format = BarcodeFormat.QR_CODE;
142     try {
143       Uri uri = (Uri)intent.getExtras().getParcelable(Intent.EXTRA_STREAM);
144       InputStream stream = activity.getContentResolver().openInputStream(uri);
145       int length = stream.available();
146       byte[] vcard = new byte[length];
147       stream.read(vcard, 0, length);
148       String vcardString = new String(vcard, "UTF-8");
149       Log.d(TAG, "Encoding share intent content: " + vcardString);
150       Result result = new Result(vcardString, vcard, null, BarcodeFormat.QR_CODE);
151       ParsedResult parsedResult = ResultParser.parseResult(result);
152       if (!(parsedResult instanceof AddressBookParsedResult)) {
153         return false;
154       }
155       if (!encodeQRCodeContents((AddressBookParsedResult) parsedResult)) {
156         return false;
157       }
158     } catch (FileNotFoundException e) {
159       return false;
160     } catch (IOException e) {
161       return false;
162     }
163     return contents != null && contents.length() > 0;
164   }
165
166   private void encodeQRCodeContents(Intent intent, String type) {
167     if (type.equals(Contents.Type.TEXT)) {
168       String data = intent.getStringExtra(Intents.Encode.DATA);
169       if (data != null && data.length() > 0) {
170         contents = data;
171         displayContents = data;
172         title = activity.getString(R.string.contents_text);
173       }
174     } else if (type.equals(Contents.Type.EMAIL)) {
175       String data = intent.getStringExtra(Intents.Encode.DATA);
176       if (data != null && data.length() > 0) {
177         contents = "mailto:" + data;
178         displayContents = data;
179         title = activity.getString(R.string.contents_email);
180       }
181     } else if (type.equals(Contents.Type.PHONE)) {
182       String data = intent.getStringExtra(Intents.Encode.DATA);
183       if (data != null && data.length() > 0) {
184         contents = "tel:" + data;
185         displayContents = PhoneNumberUtils.formatNumber(data);
186         title = activity.getString(R.string.contents_phone);
187       }
188     } else if (type.equals(Contents.Type.SMS)) {
189       String data = intent.getStringExtra(Intents.Encode.DATA);
190       if (data != null && data.length() > 0) {
191         contents = "sms:" + data;
192         displayContents = PhoneNumberUtils.formatNumber(data);
193         title = activity.getString(R.string.contents_sms);
194       }
195     } else if (type.equals(Contents.Type.CONTACT)) {
196       Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
197       if (bundle != null) {
198         StringBuilder newContents = new StringBuilder();
199         StringBuilder newDisplayContents = new StringBuilder();
200         newContents.append("MECARD:");
201         String name = bundle.getString(Contacts.Intents.Insert.NAME);
202         if (name != null && name.length() > 0) {
203           newContents.append("N:").append(name).append(';');
204           newDisplayContents.append(name);
205         }
206         String address = bundle.getString(Contacts.Intents.Insert.POSTAL);
207         if (address != null && address.length() > 0) {
208           newContents.append("ADR:").append(address).append(';');
209           newDisplayContents.append('\n').append(address);
210         }
211         for (int x = 0; x < Contents.PHONE_KEYS.length; x++) {
212           String phone = bundle.getString(Contents.PHONE_KEYS[x]);
213           if (phone != null && phone.length() > 0) {
214             newContents.append("TEL:").append(phone).append(';');
215             newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
216           }
217         }
218         for (int x = 0; x < Contents.EMAIL_KEYS.length; x++) {
219           String email = bundle.getString(Contents.EMAIL_KEYS[x]);
220           if (email != null && email.length() > 0) {
221             newContents.append("EMAIL:").append(email).append(';');
222             newDisplayContents.append('\n').append(email);
223           }
224         }
225         // Make sure we've encoded at least one field.
226         if (newDisplayContents.length() > 0) {
227           newContents.append(';');
228           contents = newContents.toString();
229           displayContents = newDisplayContents.toString();
230           title = activity.getString(R.string.contents_contact);
231         } else {
232           contents = null;
233           displayContents = null;
234         }
235       }
236     } else if (type.equals(Contents.Type.LOCATION)) {
237       Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
238       if (bundle != null) {
239         // These must use Bundle.getFloat(), not getDouble(), it's part of the API.
240         float latitude = bundle.getFloat("LAT", Float.MAX_VALUE);
241         float longitude = bundle.getFloat("LONG", Float.MAX_VALUE);
242         if (latitude != Float.MAX_VALUE && longitude != Float.MAX_VALUE) {
243           contents = "geo:" + latitude + ',' + longitude;
244           displayContents = latitude + "," + longitude;
245           title = activity.getString(R.string.contents_location);
246         }
247       }
248     }
249   }
250
251   private boolean encodeQRCodeContents(AddressBookParsedResult contact) {
252     StringBuilder newContents = new StringBuilder();
253     StringBuilder newDisplayContents = new StringBuilder();
254     newContents.append("MECARD:");
255     String[] names = contact.getNames();
256     if (names != null && names.length > 0) {
257       newContents.append("N:").append(names[0]).append(';');
258       newDisplayContents.append(names[0]);
259     }
260     String[] addresses = contact.getAddresses();
261     if (addresses != null) {
262       for (int x = 0; x < addresses.length; x++) {
263         if (addresses[x] != null && addresses[x].length() > 0) {
264           newContents.append("ADR:").append(addresses[x]).append(';');
265           newDisplayContents.append('\n').append(addresses[x]);
266         }
267       }
268     }
269     String[] phoneNumbers = contact.getPhoneNumbers();
270     if (phoneNumbers != null) {
271       for (int x = 0; x < phoneNumbers.length; x++) {
272         String phone = phoneNumbers[x];
273         if (phone != null && phone.length() > 0) {
274           newContents.append("TEL:").append(phone).append(';');
275           newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
276         }
277       }
278     }
279     String[] emails = contact.getEmails();
280     if (emails != null) {
281       for (int x = 0; x < emails.length; x++) {
282         String email = emails[x];
283         if (email != null && email.length() > 0) {
284           newContents.append("EMAIL:").append(email).append(';');
285           newDisplayContents.append('\n').append(email);
286         }
287       }
288     }
289     String url = contact.getURL();
290     if (url != null && url.length() > 0) {
291       newContents.append("URL:").append(url).append(';');
292       newDisplayContents.append('\n').append(url);
293     }
294     // Make sure we've encoded at least one field.
295     if (newDisplayContents.length() > 0) {
296       newContents.append(';');
297       contents = newContents.toString();
298       displayContents = newDisplayContents.toString();
299       title = activity.getString(R.string.contents_contact);
300       return true;
301     } else {
302       contents = null;
303       displayContents = null;
304       return false;
305     }
306   }
307
308   private static final class EncodeThread extends Thread {
309     private static final String TAG = "EncodeThread";
310
311     private final String contents;
312     private final Handler handler;
313     private final int pixelResolution;
314     private final BarcodeFormat format;
315
316     EncodeThread(String contents, Handler handler, int pixelResolution,
317         BarcodeFormat format) {
318       this.contents = contents;
319       this.handler = handler;
320       this.pixelResolution = pixelResolution;
321       this.format = format;
322     }
323
324     @Override
325     public void run() {
326       try {
327         ByteMatrix result = new MultiFormatWriter().encode(contents, format,
328             pixelResolution, pixelResolution);
329         int width = result.getWidth();
330         int height = result.getHeight();
331         byte[][] array = result.getArray();
332         int[] pixels = new int[width * height];
333         for (int y = 0; y < height; y++) {
334           for (int x = 0; x < width; x++) {
335             int grey = array[y][x] & 0xff;
336             // pixels[y * width + x] = (0xff << 24) | (grey << 16) | (grey << 8) | grey;
337             pixels[y * width + x] = 0xff000000 | (0x00010101 * grey);
338           }
339         }
340
341         Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
342         bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
343         Message message = Message.obtain(handler, R.id.encode_succeeded);
344         message.obj = bitmap;
345         message.sendToTarget();
346       } catch (WriterException e) {
347         Log.e(TAG, e.toString());
348         Message message = Message.obtain(handler, R.id.encode_failed);
349         message.sendToTarget();
350       } catch (IllegalArgumentException e) {
351         Log.e(TAG, e.toString());
352         Message message = Message.obtain(handler, R.id.encode_failed);
353         message.sendToTarget();
354       }
355     }
356   }
357 }