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