de54e605c41c92072b7b5e887ec10418b2256a02
[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.BitMatrix;
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 static final int WHITE = 0xFFFFFFFF;
57
58   private final Activity activity;
59   private String contents;
60   private String displayContents;
61   private String title;
62   private BarcodeFormat format;
63
64   QRCodeEncoder(Activity activity, Intent intent) {
65     this.activity = activity;
66     if (intent == null) {
67       throw new IllegalArgumentException("No valid data to encode.");
68     }
69
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.");
74       }
75     } else if (action.equals(Intent.ACTION_SEND)) {
76       if (!encodeContentsFromShareIntent(intent)) {
77         throw new IllegalArgumentException("No valid data to encode.");
78       }
79     }
80   }
81
82   public void requestBarcode(Handler handler, int pixelResolution) {
83     Thread encodeThread = new EncodeThread(contents, handler, pixelResolution,
84         format);
85     encodeThread.start();
86   }
87
88   public String getContents() {
89     return contents;
90   }
91
92   public String getDisplayContents() {
93     return displayContents;
94   }
95
96   public String getTitle() {
97     return title;
98   }
99
100   public String getFormat() {
101     return format.toString();
102   }
103
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) {
113         return false;
114       }
115       this.format = BarcodeFormat.QR_CODE;
116       encodeQRCodeContents(intent, type);
117     } else {
118       String data = intent.getStringExtra(Intents.Encode.DATA);
119       if (data != null && data.length() != 0) {
120         contents = data;
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;
135         }
136       }
137     }
138     return contents != null && contents.length() > 0;
139   }
140
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;
144     try {
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)) {
155         return false;
156       }
157       if (!encodeQRCodeContents((AddressBookParsedResult) parsedResult)) {
158         return false;
159       }
160     } catch (FileNotFoundException e) {
161       return false;
162     } catch (IOException e) {
163       return false;
164     } catch (NullPointerException e) {
165       // In case the uri was not found in the Intent.
166       return false;
167     }
168     return contents != null && contents.length() > 0;
169   }
170
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) {
175         contents = data;
176         displayContents = data;
177         title = activity.getString(R.string.contents_text);
178       }
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);
185       }
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);
192       }
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);
199       }
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);
210         }
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);
215         }
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));
221           }
222         }
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);
228           }
229         }
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);
236         } else {
237           contents = null;
238           displayContents = null;
239         }
240       }
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);
251         }
252       }
253     }
254   }
255
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]);
264     }
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]);
271         }
272       }
273     }
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));
281         }
282       }
283     }
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);
291         }
292       }
293     }
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);
298     }
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);
305       return true;
306     } else {
307       contents = null;
308       displayContents = null;
309       return false;
310     }
311   }
312
313   private static final class EncodeThread extends Thread {
314     private static final String TAG = "EncodeThread";
315
316     private final String contents;
317     private final Handler handler;
318     private final int pixelResolution;
319     private final BarcodeFormat format;
320
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;
327     }
328
329     @Override
330     public void run() {
331       try {
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;
343             }
344           }
345         }
346
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();
360       }
361     }
362   }
363 }