Lots of updates:
[zxing.git] / android / src / com / google / zxing / client / android / result / ResultHandler.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.result;
18
19 import com.google.zxing.client.android.Contents;
20 import com.google.zxing.client.android.Intents;
21 import com.google.zxing.client.android.LocaleManager;
22 import com.google.zxing.client.android.R;
23 import com.google.zxing.client.android.SearchBookContentsActivity;
24 import com.google.zxing.client.result.ParsedResult;
25 import com.google.zxing.client.result.ParsedResultType;
26
27 import android.app.Activity;
28 import android.app.AlertDialog;
29 import android.content.ActivityNotFoundException;
30 import android.content.Intent;
31 import android.net.Uri;
32 import android.provider.Contacts;
33
34 import java.text.DateFormat;
35 import java.text.ParsePosition;
36 import java.text.SimpleDateFormat;
37 import java.util.Calendar;
38 import java.util.Date;
39 import java.util.GregorianCalendar;
40
41 /**
42  * A base class for the Android-specific barcode handlers. These allow the app to polymorphically
43  * suggest the appropriate actions for each data type.
44  *
45  * This class also contains a bunch of utility methods to take common actions like opening a URL.
46  * They could easily be moved into a helper object, but it can't be static because the Activity
47  * instance is needed to launch an intent.
48  *
49  * @author dswitkin@google.com (Daniel Switkin)
50  */
51 public abstract class ResultHandler {
52   private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
53   private static final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
54
55   public static final int MAX_BUTTON_COUNT = 4;
56
57   protected final ParsedResult result;
58   private final Activity activity;
59
60   protected ResultHandler(Activity activity, ParsedResult result) {
61     this.result = result;
62     this.activity = activity;
63   }
64
65   /**
66    * Indicates how many buttons the derived class wants shown.
67    *
68    * @return The integer button count.
69    */
70   public abstract int getButtonCount();
71
72   /**
73    * The text of the nth action button.
74    *
75    * @param index From 0 to getButtonCount() - 1
76    * @return The button text as a resource ID
77    */
78   public abstract int getButtonText(int index);
79
80
81   /**
82    * Execute the action which corresponds to the nth button.
83    *
84    * @param index The button that was clicked.
85    */
86   public abstract void handleButtonPress(int index);
87
88   /**
89    * Create a possibly styled string for the contents of the current barcode.
90    *
91    * @return The text to be displayed.
92    */
93   public CharSequence getDisplayContents() {
94     String contents = result.getDisplayResult();
95     return contents.replace("\r", "");
96   }
97
98   /**
99    * A string describing the kind of barcode that was found, e.g. "Found contact info".
100    *
101    * @return The resource ID of the string.
102    */
103   public abstract int getDisplayTitle();
104
105   /**
106    * A convenience method to get the parsed type. Should not be overridden.
107    *
108    * @return The parsed type, e.g. URI or ISBN
109    */
110   public final ParsedResultType getType() {
111     return result.getType();
112   }
113
114   /**
115    * Sends an intent to create a new calendar event by prepopulating the Add Event UI. Older
116    * versions of the system have a bug where the event title will not be filled out.
117    *
118    * @param summary A description of the event
119    * @param start   The start time as yyyyMMdd or yyyyMMdd'T'HHmmss or yyyyMMdd'T'HHmmss'Z'
120    * @param end     The end time as yyyyMMdd or yyyyMMdd'T'HHmmss or yyyyMMdd'T'HHmmss'Z'
121    */
122   public final void addCalendarEvent(String summary, String start, String end) {
123     Intent intent = new Intent(Intent.ACTION_EDIT);
124     intent.setType("vnd.android.cursor.item/event");
125     intent.putExtra("beginTime", calculateMilliseconds(start));
126     if (start.length() == 8) {
127       intent.putExtra("allDay", true);
128     }
129     intent.putExtra("endTime", calculateMilliseconds(end));
130     intent.putExtra("title", summary);
131     launchIntent(intent);
132   }
133
134   private static long calculateMilliseconds(String when) {
135     if (when.length() == 8) {
136       // Only contains year/month/day
137       Date date;
138       synchronized (DATE_FORMAT) {
139         date = DATE_FORMAT.parse(when, new ParsePosition(0));
140       }
141       return date.getTime();
142     } else {
143       // The when string can be local time, or UTC if it ends with a Z
144       Date date;
145       synchronized (DATE_TIME_FORMAT) {
146        date = DATE_TIME_FORMAT.parse(when.substring(0, 15), new ParsePosition(0));
147       }
148       long milliseconds = date.getTime();
149       if (when.length() == 16 && when.charAt(15) == 'Z') {
150         Calendar calendar = new GregorianCalendar();
151         int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
152         milliseconds += offset;
153       }
154       return milliseconds;
155     }
156   }
157
158   public final void addContact(String[] names, String[] phoneNumbers, String[] emails, String note,
159                          String address, String org, String title) {
160
161     // Only use the first name in the array, if present.
162     Intent intent = new Intent(Contacts.Intents.Insert.ACTION, Contacts.People.CONTENT_URI);
163     putExtra(intent, Contacts.Intents.Insert.NAME, names != null ? names[0] : null);
164
165     int phoneCount = Math.min((phoneNumbers != null) ? phoneNumbers.length : 0,
166         Contents.PHONE_KEYS.length);
167     for (int x = 0; x < phoneCount; x++) {
168       putExtra(intent, Contents.PHONE_KEYS[x], phoneNumbers[x]);
169     }
170
171     int emailCount = Math.min((emails != null) ? emails.length : 0, Contents.EMAIL_KEYS.length);
172     for (int x = 0; x < emailCount; x++) {
173       putExtra(intent, Contents.EMAIL_KEYS[x], emails[x]);
174     }
175
176     putExtra(intent, Contacts.Intents.Insert.NOTES, note);
177     putExtra(intent, Contacts.Intents.Insert.POSTAL, address);
178     putExtra(intent, Contacts.Intents.Insert.COMPANY, org);
179     putExtra(intent, Contacts.Intents.Insert.JOB_TITLE, title);
180     launchIntent(intent);
181   }
182
183   public final void shareByEmail(String contents) {
184     sendEmailFromUri("mailto:", activity.getString(R.string.msg_share_subject_line), contents);
185   }
186
187   public final void sendEmail(String address, String subject, String body) {
188     sendEmailFromUri("mailto:" + address, subject, body);
189   }
190
191   // Use public Intent fields rather than private GMail app fields to specify subject and body.
192   public final void sendEmailFromUri(String uri, String subject, String body) {
193     Intent intent = new Intent(Intent.ACTION_SEND, Uri.parse(uri));
194     putExtra(intent, Intent.EXTRA_SUBJECT, subject);
195     putExtra(intent, Intent.EXTRA_TEXT, body);
196     intent.setType("text/plain");
197     launchIntent(intent);
198   }
199
200   public final void shareBySMS(String contents) {
201     sendSMSFromUri("smsto:", activity.getString(R.string.msg_share_subject_line) + ":\n" +
202         contents);
203   }
204
205   public final void sendSMS(String phoneNumber, String body) {
206     sendSMSFromUri("smsto:" + phoneNumber, body);
207   }
208
209   public final void sendSMSFromUri(String uri, String body) {
210     Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse(uri));
211     putExtra(intent, "sms_body", body);
212     // Exit the app once the SMS is sent
213     intent.putExtra("compose_mode", true);
214     launchIntent(intent);
215   }
216
217   public final void sendMMS(String phoneNumber, String subject, String body) {
218     sendMMSFromUri("mmsto:" + phoneNumber, subject, body);
219   }
220
221   public final void sendMMSFromUri(String uri, String subject, String body) {
222     Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse(uri));
223     // The Messaging app needs to see a valid subject or else it will treat this an an SMS.
224     if (subject == null || subject.length() == 0) {
225       putExtra(intent, "subject", activity.getString(R.string.msg_default_mms_subject));
226     } else {
227       putExtra(intent, "subject", subject);
228     }
229     putExtra(intent, "sms_body", body);
230     intent.putExtra("compose_mode", true);
231     launchIntent(intent);
232   }
233
234   public final void dialPhone(String phoneNumber) {
235     launchIntent(new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber)));
236   }
237
238   public final void dialPhoneFromUri(String uri) {
239     launchIntent(new Intent(Intent.ACTION_DIAL, Uri.parse(uri)));
240   }
241
242   public final void openMap(String geoURI) {
243     launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(geoURI)));
244   }
245
246   /**
247    * Do a geo search using the address as the query.
248    *
249    * @param address The address to find
250    * @param title An optional title, e.g. the name of the business at this address
251    */
252   public final void searchMap(String address, String title) {
253     String query = address;
254     if (title != null && title.length() > 0) {
255       query = query + " (" + title + ')';
256     }
257     launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q=" + Uri.encode(query))));
258   }
259
260   public final void getDirections(double latitude, double longitude) {
261     launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://maps.google." +
262         LocaleManager.getCountryTLD() + "/maps?f=d&daddr=" + latitude + ',' + longitude)));
263   }
264
265   // Uses the mobile-specific version of Product Search, which is formatted for small screens.
266   public final void openProductSearch(String upc) {
267     Uri uri = Uri.parse("http://www.google." + LocaleManager.getProductSearchCountryTLD() +
268         "/m/products?q=" + upc + "&source=zxing");
269     launchIntent(new Intent(Intent.ACTION_VIEW, uri));
270   }
271
272   public final void openBookSearch(String isbn) {
273     Uri uri = Uri.parse("http://books.google." + LocaleManager.getBookSearchCountryTLD() +
274         "/books?vid=isbn" + isbn);
275     launchIntent(new Intent(Intent.ACTION_VIEW, uri));
276   }
277
278   public final void searchBookContents(String isbn) {
279     Intent intent = new Intent(Intents.SearchBookContents.ACTION);
280     intent.setClassName(activity, SearchBookContentsActivity.class.getName());
281     putExtra(intent, Intents.SearchBookContents.ISBN, isbn);
282     launchIntent(intent);
283   }
284
285   public final void openURL(String url) {
286     launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
287   }
288
289   public final void webSearch(String query) {
290     Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
291     intent.putExtra("query", query);
292     launchIntent(intent);
293   }
294
295   private void launchIntent(Intent intent) {
296     if (intent != null) {
297       try {
298         activity.startActivity(intent);
299       } catch (ActivityNotFoundException e) {
300         AlertDialog.Builder builder = new AlertDialog.Builder(activity);
301         builder.setTitle(activity.getString(R.string.app_name));
302         builder.setMessage(activity.getString(R.string.msg_intent_failed));
303         builder.setPositiveButton(R.string.button_ok, null);
304         builder.show();
305       }
306     }
307   }
308
309   private static void putExtra(Intent intent, String key, String value) {
310     if (value != null && value.length() > 0) {
311       intent.putExtra(key, value);
312     }
313   }
314 }