Add history feature; group some functionality into subpackages
[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.WriterException;
22 import com.google.zxing.client.android.Intents;
23 import com.google.zxing.client.android.Contents;
24 import com.google.zxing.client.android.R;
25 import com.google.zxing.common.ByteMatrix;
26
27 import android.app.Activity;
28 import android.content.Intent;
29 import android.graphics.Bitmap;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.provider.Contacts;
34 import android.telephony.PhoneNumberUtils;
35 import android.util.Log;
36
37 /**
38  * This class does the work of decoding the user's request and extracting all the data
39  * to be encoded in a QR Code.
40  *
41  * @author dswitkin@google.com (Daniel Switkin)
42  */
43 final class QRCodeEncoder {
44   private final Activity activity;
45   private String contents;
46   private String displayContents;
47   private String title;
48   private BarcodeFormat format;
49
50   public QRCodeEncoder(Activity activity, Intent intent) {
51     this.activity = activity;
52     if (!encodeContents(intent)) {
53       throw new IllegalArgumentException("No valid data to encode.");
54     }
55   }
56
57   public void requestBarcode(Handler handler, int pixelResolution) {
58     Thread encodeThread = new EncodeThread(contents, handler, pixelResolution,
59         format);
60     encodeThread.start();
61   }
62
63   public String getContents() {
64     return contents;
65   }
66
67   public String getDisplayContents() {
68     return displayContents;
69   }
70
71   public String getTitle() {
72     return title;
73   }
74   
75   public String getFormat() {
76     return format.toString();
77   }
78
79   // It would be nice if the string encoding lived in the core ZXing library,
80   // but we use platform specific code like PhoneNumberUtils, so it can't.
81   private boolean encodeContents(Intent intent) {
82     if (intent == null) {
83       return false;
84     }
85     
86     // default to QR_CODE if no format given
87     String format = intent.getStringExtra(Intents.Encode.FORMAT);
88     if (format == null || format.length() == 0 || 
89         format.equals(Contents.Format.QR_CODE)) {
90       String type = intent.getStringExtra(Intents.Encode.TYPE);
91       if (type == null || type.length() == 0) {
92         return false;
93       }
94       this.format = BarcodeFormat.QR_CODE;
95       encodeQRCodeContents(intent, type);
96     } else {
97       String data = intent.getStringExtra(Intents.Encode.DATA);
98       if (data != null && data.length() != 0) {
99         contents = data;
100         displayContents = data;
101         title = activity.getString(R.string.contents_text);
102         if (format.equals(Contents.Format.CODE_128)) {
103           this.format = BarcodeFormat.CODE_128;
104         } else if (format.equals(Contents.Format.CODE_39)) {
105           this.format = BarcodeFormat.CODE_39;
106         } else if (format.equals(Contents.Format.EAN_8)) {
107           this.format = BarcodeFormat.EAN_8;
108         } else if (format.equals(Contents.Format.EAN_13)) {
109           this.format = BarcodeFormat.EAN_13;
110         } else if (format.equals(Contents.Format.UPC_A)) {
111           this.format = BarcodeFormat.UPC_A;
112         } else if (format.equals(Contents.Format.UPC_E)) {
113           this.format = BarcodeFormat.UPC_E;
114         }
115       }
116     }
117     return contents != null && contents.length() > 0;
118   }
119
120   private void encodeQRCodeContents(Intent intent, String type) {
121     if (type.equals(Contents.Type.TEXT)) {
122       String data = intent.getStringExtra(Intents.Encode.DATA);
123       if (data != null && data.length() > 0) {
124         contents = data;
125         displayContents = data;
126         title = activity.getString(R.string.contents_text);
127       }
128     } else if (type.equals(Contents.Type.EMAIL)) {
129       String data = intent.getStringExtra(Intents.Encode.DATA);
130       if (data != null && data.length() > 0) {
131         contents = "mailto:" + data;
132         displayContents = data;
133         title = activity.getString(R.string.contents_email);
134       }
135     } else if (type.equals(Contents.Type.PHONE)) {
136       String data = intent.getStringExtra(Intents.Encode.DATA);
137       if (data != null && data.length() > 0) {
138         contents = "tel:" + data;
139         displayContents = PhoneNumberUtils.formatNumber(data);
140         title = activity.getString(R.string.contents_phone);
141       }
142     } else if (type.equals(Contents.Type.SMS)) {
143       String data = intent.getStringExtra(Intents.Encode.DATA);
144       if (data != null && data.length() > 0) {
145         contents = "sms:" + data;
146         displayContents = PhoneNumberUtils.formatNumber(data);
147         title = activity.getString(R.string.contents_sms);
148       }
149     } else if (type.equals(Contents.Type.CONTACT)) {
150       Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
151       if (bundle != null) {
152         StringBuilder newContents = new StringBuilder();
153         StringBuilder newDisplayContents = new StringBuilder();
154         newContents.append("MECARD:");
155         String name = bundle.getString(Contacts.Intents.Insert.NAME);
156         if (name != null && name.length() > 0) {
157           newContents.append("N:").append(name).append(';');
158           newDisplayContents.append(name);
159         }
160         String address = bundle.getString(Contacts.Intents.Insert.POSTAL);
161         if (address != null && address.length() > 0) {
162           newContents.append("ADR:").append(address).append(';');
163           newDisplayContents.append('\n').append(address);
164         }
165         for (int x = 0; x < Contents.PHONE_KEYS.length; x++) {
166           String phone = bundle.getString(Contents.PHONE_KEYS[x]);
167           if (phone != null && phone.length() > 0) {
168             newContents.append("TEL:").append(phone).append(';');
169             newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
170           }
171         }
172         for (int x = 0; x < Contents.EMAIL_KEYS.length; x++) {
173           String email = bundle.getString(Contents.EMAIL_KEYS[x]);
174           if (email != null && email.length() > 0) {
175             newContents.append("EMAIL:").append(email).append(';');
176             newDisplayContents.append('\n').append(email);
177           }
178         }
179         // Make sure we've encoded at least one field.
180         if (newDisplayContents.length() > 0) {
181           newContents.append(';');
182           contents = newContents.toString();
183           displayContents = newDisplayContents.toString();
184           title = activity.getString(R.string.contents_contact);
185         } else {
186           contents = null;
187           displayContents = null;
188         }
189       }
190     } else if (type.equals(Contents.Type.LOCATION)) {
191       Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
192       if (bundle != null) {
193         // These must use Bundle.getFloat(), not getDouble(), it's part of the API.
194         float latitude = bundle.getFloat("LAT", Float.MAX_VALUE);
195         float longitude = bundle.getFloat("LONG", Float.MAX_VALUE);
196         if (latitude != Float.MAX_VALUE && longitude != Float.MAX_VALUE) {
197           contents = "geo:" + latitude + ',' + longitude;
198           displayContents = latitude + "," + longitude;
199           title = activity.getString(R.string.contents_location);
200         }
201       }
202     }
203   }
204
205   private static final class EncodeThread extends Thread {
206     private static final String TAG = "EncodeThread";
207
208     private final String contents;
209     private final Handler handler;
210     private final int pixelResolution;
211     private final BarcodeFormat format;
212
213     EncodeThread(String contents, Handler handler, int pixelResolution,
214         BarcodeFormat format) {
215       this.contents = contents;
216       this.handler = handler;
217       this.pixelResolution = pixelResolution;
218       this.format = format;
219     }
220
221     @Override
222     public void run() {
223       try {
224         ByteMatrix result = new MultiFormatWriter().encode(contents, format,
225             pixelResolution, pixelResolution);
226         int width = result.getWidth();
227         int height = result.getHeight();
228         byte[][] array = result.getArray();
229         int[] pixels = new int[width * height];
230         for (int y = 0; y < height; y++) {
231           for (int x = 0; x < width; x++) {
232             int grey = array[y][x] & 0xff;
233             // pixels[y * width + x] = (0xff << 24) | (grey << 16) | (grey << 8) | grey;
234             pixels[y * width + x] = 0xff000000 | (0x00010101 * grey);
235           }
236         }
237
238         Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
239         bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
240         Message message = Message.obtain(handler, R.id.encode_succeeded);
241         message.obj = bitmap;
242         message.sendToTarget();
243       } catch (WriterException e) {
244         Log.e(TAG, e.toString());
245         Message message = Message.obtain(handler, R.id.encode_failed);
246         message.sendToTarget();
247       } catch (IllegalArgumentException e) {
248         Log.e(TAG, e.toString());
249         Message message = Message.obtain(handler, R.id.encode_failed);
250         message.sendToTarget();
251       }
252     }
253   }
254 }