Adding the actionscript3/Flex conversion of the zxing library plus a demo app.
[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   private final Activity activity;
54   private String contents;
55   private String displayContents;
56   private String title;
57   private BarcodeFormat format;
58
59   public QRCodeEncoder(Activity activity, Intent intent) {
60     this.activity = activity;
61     if (intent == null) {
62       throw new IllegalArgumentException("No valid data to encode.");
63     }
64
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.");
69       }
70     } else if (action.equals(Intent.ACTION_SEND)) {
71       if (!encodeContentsFromShareIntent(intent)) {
72       throw new IllegalArgumentException("No valid data to encode.");
73       }
74     }
75   }
76
77   public void requestBarcode(Handler handler, int pixelResolution) {
78     Thread encodeThread = new EncodeThread(contents, handler, pixelResolution,
79         format);
80     encodeThread.start();
81   }
82
83   public String getContents() {
84     return contents;
85   }
86
87   public String getDisplayContents() {
88     return displayContents;
89   }
90
91   public String getTitle() {
92     return title;
93   }
94
95   public String getFormat() {
96     return format.toString();
97   }
98
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) {
108         return false;
109       }
110       this.format = BarcodeFormat.QR_CODE;
111       encodeQRCodeContents(intent, type);
112     } else {
113       String data = intent.getStringExtra(Intents.Encode.DATA);
114       if (data != null && data.length() != 0) {
115         contents = data;
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;
130         }
131       }
132     }
133     return contents != null && contents.length() > 0;
134   }
135
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;
139     try {
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)) {
149         return false;
150       }
151       if (!encodeQRCodeContents((AddressBookParsedResult) parsedResult)) {
152         return false;
153       }
154     } catch (FileNotFoundException e) {
155       return false;
156     } catch (IOException e) {
157       return false;
158     }
159     return contents != null && contents.length() > 0;
160   }
161
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) {
166         contents = data;
167         displayContents = data;
168         title = activity.getString(R.string.contents_text);
169       }
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);
176       }
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);
183       }
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);
190       }
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);
201         }
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);
206         }
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));
212           }
213         }
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);
219           }
220         }
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);
227         } else {
228           contents = null;
229           displayContents = null;
230         }
231       }
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);
242         }
243       }
244     }
245   }
246
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]);
255     }
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);
260     }
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));
268         }
269       }
270     }
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);
278         }
279       }
280     }
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);
285     }
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);
292       return true;
293     } else {
294       contents = null;
295       displayContents = null;
296       return false;
297     }
298   }
299
300   private static final class EncodeThread extends Thread {
301     private static final String TAG = "EncodeThread";
302
303     private final String contents;
304     private final Handler handler;
305     private final int pixelResolution;
306     private final BarcodeFormat format;
307
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;
314     }
315
316     @Override
317     public void run() {
318       try {
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);
330           }
331         }
332
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();
346       }
347     }
348   }
349 }