blob: bb35b156f73d8ba7cb87e9a697bb6b52b12c0b61 [file] [log] [blame]
mah64c64e72010-02-09 10:36:10 -08001/*
2 * Copyright (C) 2010 The Android Open Source Project
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 */
16package com.android.voicedialer;
17
18
19import android.content.ContentUris;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.PackageManager;
23import android.content.pm.ResolveInfo;
24import android.content.res.Resources;
25import android.net.Uri;
26import android.provider.ContactsContract.CommonDataKinds.Phone;
mah64c64e72010-02-09 10:36:10 -080027import android.speech.srec.Recognizer;
mah64c64e72010-02-09 10:36:10 -080028import android.util.Log;
nieyz06048a7d0d22015-09-29 07:01:41 -040029import android.database.Cursor;
30import android.content.ContentResolver;
31import android.provider.ContactsContract.CommonDataKinds;
32import android.net.Uri;
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -080033
mah64c64e72010-02-09 10:36:10 -080034import java.io.File;
35import java.io.FileFilter;
36import java.io.FileInputStream;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.io.ObjectInputStream;
40import java.io.ObjectOutputStream;
41import java.net.URISyntaxException;
42import java.util.ArrayList;
43import java.util.HashMap;
44import java.util.HashSet;
45import java.util.List;
mah64c64e72010-02-09 10:36:10 -080046/**
47 * This is a RecognizerEngine that processes commands to make phone calls and
48 * open applications.
49 * <ul>
mah7a551502010-02-23 15:20:44 -080050 * <li>setupGrammar
mah64c64e72010-02-09 10:36:10 -080051 * <li>Scans contacts and determine if the Grammar g2g file is stale.
52 * <li>If so, create and rebuild the Grammar,
53 * <li>Else create and load the Grammar from the file.
54 * <li>onRecognitionSuccess is called when we get results from the recognizer,
55 * it will process the results, which will pass a list of intents to
56 * the {@RecognizerClient}. It will accept the following types of commands:
57 * "call" a particular contact
58 * "dial a particular number
59 * "open" a particular application
60 * "redial" the last number called
61 * "voicemail" to call voicemail
62 * <li>Pass a list of {@link Intent} corresponding to the recognition results
63 * to the {@link RecognizerClient}, which notifies the user.
64 * </ul>
65 * Notes:
66 * <ul>
67 * <li>Audio many be read from a file.
68 * <li>A directory tree of audio files may be stepped through.
69 * <li>A contact list may be read from a file.
70 * <li>A {@link RecognizerLogger} may generate a set of log files from
71 * a recognition session.
72 * <li>A static instance of this class is held and reused by the
73 * {@link VoiceDialerActivity}, which saves setup time.
74 * </ul>
75 */
76public class CommandRecognizerEngine extends RecognizerEngine {
77
78 private static final String OPEN_ENTRIES = "openentries.txt";
mah7a551502010-02-23 15:20:44 -080079 public static final String PHONE_TYPE_EXTRA = "phone_type";
80 private static final int MINIMUM_CONFIDENCE = 100;
mah64c64e72010-02-09 10:36:10 -080081 private File mContactsFile;
mah7a551502010-02-23 15:20:44 -080082 private boolean mMinimizeResults;
Martin Hibdon5f256192010-03-04 17:44:51 -080083 private boolean mAllowOpenEntries;
Martin Hibdon4a330952010-03-24 10:48:41 -070084 private HashMap<String,String> mOpenEntries;
Martin Hibdon5f256192010-03-04 17:44:51 -080085
mah64c64e72010-02-09 10:36:10 -080086 /**
87 * Constructor.
88 */
89 public CommandRecognizerEngine() {
mah7a551502010-02-23 15:20:44 -080090 mContactsFile = null;
91 mMinimizeResults = false;
Martin Hibdon5f256192010-03-04 17:44:51 -080092 mAllowOpenEntries = true;
mah64c64e72010-02-09 10:36:10 -080093 }
94
95 public void setContactsFile(File contactsFile) {
Martin Hibdon4a330952010-03-24 10:48:41 -070096 if (contactsFile != mContactsFile) {
97 mContactsFile = contactsFile;
98 // if we change the contacts file, then we need to recreate the grammar.
99 if (mSrecGrammar != null) {
100 mSrecGrammar.destroy();
101 mSrecGrammar = null;
102 mOpenEntries = null;
103 }
mah64c64e72010-02-09 10:36:10 -0800104 }
105 }
106
mah7a551502010-02-23 15:20:44 -0800107 public void setMinimizeResults(boolean minimizeResults) {
108 mMinimizeResults = minimizeResults;
109 }
110
Martin Hibdon5f256192010-03-04 17:44:51 -0800111 public void setAllowOpenEntries(boolean allowOpenEntries) {
112 if (mAllowOpenEntries != allowOpenEntries) {
113 // if we change this setting, then we need to recreate the grammar.
114 if (mSrecGrammar != null) {
115 mSrecGrammar.destroy();
116 mSrecGrammar = null;
Martin Hibdon4a330952010-03-24 10:48:41 -0700117 mOpenEntries = null;
Martin Hibdon5f256192010-03-04 17:44:51 -0800118 }
119 }
120 mAllowOpenEntries = allowOpenEntries;
121 }
122
mah64c64e72010-02-09 10:36:10 -0800123 protected void setupGrammar() throws IOException, InterruptedException {
124 // fetch the contact list
Joe Onoratod3694c02011-04-07 18:41:13 -0700125 if (false) Log.d(TAG, "start getVoiceContacts");
126 if (false) Log.d(TAG, "contactsFile is " + (mContactsFile == null ?
mah64c64e72010-02-09 10:36:10 -0800127 "null" : "not null"));
128 List<VoiceContact> contacts = mContactsFile != null ?
129 VoiceContact.getVoiceContactsFromFile(mContactsFile) :
130 VoiceContact.getVoiceContacts(mActivity);
131
132 // log contacts if requested
133 if (mLogger != null) mLogger.logContacts(contacts);
134 // generate g2g grammar file name
135 File g2g = mActivity.getFileStreamPath("voicedialer." +
136 Integer.toHexString(contacts.hashCode()) + ".g2g");
137
138 // rebuild g2g file if current one is out of date
139 if (!g2g.exists()) {
140 // clean up existing Grammar and old file
141 deleteAllG2GFiles(mActivity);
142 if (mSrecGrammar != null) {
143 mSrecGrammar.destroy();
144 mSrecGrammar = null;
145 }
146
147 // load the empty Grammar
Joe Onoratod3694c02011-04-07 18:41:13 -0700148 if (false) Log.d(TAG, "start new Grammar");
mah64c64e72010-02-09 10:36:10 -0800149 mSrecGrammar = mSrec.new Grammar(SREC_DIR + "/grammars/VoiceDialer.g2g");
150 mSrecGrammar.setupRecognizer();
151
152 // reset slots
Joe Onoratod3694c02011-04-07 18:41:13 -0700153 if (false) Log.d(TAG, "start grammar.resetAllSlots");
mah64c64e72010-02-09 10:36:10 -0800154 mSrecGrammar.resetAllSlots();
155
156 // add names to the grammar
157 addNameEntriesToGrammar(contacts);
158
Martin Hibdon5f256192010-03-04 17:44:51 -0800159 if (mAllowOpenEntries) {
160 // add open entries to the grammar
161 addOpenEntriesToGrammar();
162 }
Martin Hibdon78d02352010-02-24 10:54:12 -0800163
mah64c64e72010-02-09 10:36:10 -0800164 // compile the grammar
Joe Onoratod3694c02011-04-07 18:41:13 -0700165 if (false) Log.d(TAG, "start grammar.compile");
mah64c64e72010-02-09 10:36:10 -0800166 mSrecGrammar.compile();
167
168 // update g2g file
Joe Onoratod3694c02011-04-07 18:41:13 -0700169 if (false) Log.d(TAG, "start grammar.save " + g2g.getPath());
mah64c64e72010-02-09 10:36:10 -0800170 g2g.getParentFile().mkdirs();
171 mSrecGrammar.save(g2g.getPath());
172 }
173
174 // g2g file exists, but is not loaded
175 else if (mSrecGrammar == null) {
Joe Onoratod3694c02011-04-07 18:41:13 -0700176 if (false) Log.d(TAG, "start new Grammar loading " + g2g);
mah64c64e72010-02-09 10:36:10 -0800177 mSrecGrammar = mSrec.new Grammar(g2g.getPath());
178 mSrecGrammar.setupRecognizer();
179 }
Martin Hibdon4a330952010-03-24 10:48:41 -0700180 if (mOpenEntries == null && mAllowOpenEntries) {
181 // make sure to load the openEntries mapping table.
182 loadOpenEntriesTable();
183 }
184
mah64c64e72010-02-09 10:36:10 -0800185 }
186
187 /**
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -0800188 * Number of phone ids appended to a grammer in {@link #addNameEntriesToGrammar(List)}.
189 */
190 private static final int PHONE_ID_COUNT = 7;
191
192 /**
mah64c64e72010-02-09 10:36:10 -0800193 * Add a list of names to the grammar
194 * @param contacts list of VoiceContacts to be added.
195 */
196 private void addNameEntriesToGrammar(List<VoiceContact> contacts)
197 throws InterruptedException {
Joe Onoratod3694c02011-04-07 18:41:13 -0700198 if (false) Log.d(TAG, "addNameEntriesToGrammar " + contacts.size());
mah64c64e72010-02-09 10:36:10 -0800199
200 HashSet<String> entries = new HashSet<String>();
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -0800201 StringBuilder sb = new StringBuilder();
mah64c64e72010-02-09 10:36:10 -0800202 int count = 0;
203 for (VoiceContact contact : contacts) {
204 if (Thread.interrupted()) throw new InterruptedException();
205 String name = scrubName(contact.mName);
206 if (name.length() == 0 || !entries.add(name)) continue;
207 sb.setLength(0);
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -0800208 // The number of ids appended here must be same as PHONE_ID_COUNT.
mah64c64e72010-02-09 10:36:10 -0800209 sb.append("V='");
210 sb.append(contact.mContactId).append(' ');
211 sb.append(contact.mPrimaryId).append(' ');
212 sb.append(contact.mHomeId).append(' ');
213 sb.append(contact.mMobileId).append(' ');
214 sb.append(contact.mWorkId).append(' ');
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -0800215 sb.append(contact.mOtherId).append(' ');
216 sb.append(contact.mFallbackId);
mah64c64e72010-02-09 10:36:10 -0800217 sb.append("'");
218 try {
219 mSrecGrammar.addWordToSlot("@Names", name, null, 1, sb.toString());
220 } catch (Exception e) {
221 Log.e(TAG, "Cannot load all contacts to voice recognizer, loaded " +
222 count, e);
223 break;
224 }
225
226 count++;
227 }
228 }
229
230 /**
231 * add a list of application labels to the 'open x' grammar
232 */
Martin Hibdon4a330952010-03-24 10:48:41 -0700233 private void loadOpenEntriesTable() throws InterruptedException, IOException {
Joe Onoratod3694c02011-04-07 18:41:13 -0700234 if (false) Log.d(TAG, "addOpenEntriesToGrammar");
mah64c64e72010-02-09 10:36:10 -0800235
236 // fill this
mah64c64e72010-02-09 10:36:10 -0800237 File oe = mActivity.getFileStreamPath(OPEN_ENTRIES);
238
239 // build and write list of entries
240 if (!oe.exists()) {
Martin Hibdon4a330952010-03-24 10:48:41 -0700241 mOpenEntries = new HashMap<String, String>();
mah64c64e72010-02-09 10:36:10 -0800242
243 // build a list of 'open' entries
244 PackageManager pm = mActivity.getPackageManager();
245 List<ResolveInfo> riList = pm.queryIntentActivities(
246 new Intent(Intent.ACTION_MAIN).
247 addCategory("android.intent.category.VOICE_LAUNCH"),
248 PackageManager.GET_ACTIVITIES);
249 if (Thread.interrupted()) throw new InterruptedException();
250 riList.addAll(pm.queryIntentActivities(
251 new Intent(Intent.ACTION_MAIN).
252 addCategory("android.intent.category.LAUNCHER"),
253 PackageManager.GET_ACTIVITIES));
254 String voiceDialerClassName = mActivity.getComponentName().getClassName();
255
256 // scan list, adding complete phrases, as well as individual words
257 for (ResolveInfo ri : riList) {
258 if (Thread.interrupted()) throw new InterruptedException();
259
260 // skip self
261 if (voiceDialerClassName.equals(ri.activityInfo.name)) continue;
262
263 // fetch a scrubbed window label
264 String label = scrubName(ri.loadLabel(pm).toString());
265 if (label.length() == 0) continue;
266
267 // insert it into the result list
Martin Hibdon4a330952010-03-24 10:48:41 -0700268 addClassName(mOpenEntries, label,
Martin Hibdon27d52002010-03-22 17:05:16 -0700269 ri.activityInfo.packageName, ri.activityInfo.name);
mah64c64e72010-02-09 10:36:10 -0800270
271 // split it into individual words, and insert them
272 String[] words = label.split(" ");
273 if (words.length > 1) {
274 for (String word : words) {
275 word = word.trim();
276 // words must be three characters long, or two if capitalized
277 int len = word.length();
278 if (len <= 1) continue;
279 if (len == 2 && !(Character.isUpperCase(word.charAt(0)) &&
280 Character.isUpperCase(word.charAt(1)))) continue;
281 if ("and".equalsIgnoreCase(word) ||
282 "the".equalsIgnoreCase(word)) continue;
283 // add the word
Martin Hibdon4a330952010-03-24 10:48:41 -0700284 addClassName(mOpenEntries, word,
Martin Hibdon27d52002010-03-22 17:05:16 -0700285 ri.activityInfo.packageName, ri.activityInfo.name);
mah64c64e72010-02-09 10:36:10 -0800286 }
287 }
288 }
289
290 // write list
Joe Onoratod3694c02011-04-07 18:41:13 -0700291 if (false) Log.d(TAG, "addOpenEntriesToGrammar writing " + oe);
mah64c64e72010-02-09 10:36:10 -0800292 try {
293 FileOutputStream fos = new FileOutputStream(oe);
294 try {
295 ObjectOutputStream oos = new ObjectOutputStream(fos);
Martin Hibdon4a330952010-03-24 10:48:41 -0700296 oos.writeObject(mOpenEntries);
mah64c64e72010-02-09 10:36:10 -0800297 oos.close();
298 } finally {
299 fos.close();
300 }
301 } catch (IOException ioe) {
302 deleteCachedGrammarFiles(mActivity);
303 throw ioe;
304 }
305 }
306
307 // read the list
308 else {
Joe Onoratod3694c02011-04-07 18:41:13 -0700309 if (false) Log.d(TAG, "addOpenEntriesToGrammar reading " + oe);
mah64c64e72010-02-09 10:36:10 -0800310 try {
311 FileInputStream fis = new FileInputStream(oe);
312 try {
313 ObjectInputStream ois = new ObjectInputStream(fis);
Martin Hibdon4a330952010-03-24 10:48:41 -0700314 mOpenEntries = (HashMap<String, String>)ois.readObject();
mah64c64e72010-02-09 10:36:10 -0800315 ois.close();
316 } finally {
317 fis.close();
318 }
319 } catch (Exception e) {
320 deleteCachedGrammarFiles(mActivity);
321 throw new IOException(e.toString());
322 }
323 }
Martin Hibdon4a330952010-03-24 10:48:41 -0700324 }
325
326 private void addOpenEntriesToGrammar() throws InterruptedException, IOException {
327 // load up our open entries table
328 loadOpenEntriesTable();
mah64c64e72010-02-09 10:36:10 -0800329
330 // add list of 'open' entries to the grammar
Martin Hibdon4a330952010-03-24 10:48:41 -0700331 for (String label : mOpenEntries.keySet()) {
mah64c64e72010-02-09 10:36:10 -0800332 if (Thread.interrupted()) throw new InterruptedException();
Martin Hibdon4a330952010-03-24 10:48:41 -0700333 String entry = mOpenEntries.get(label);
mah64c64e72010-02-09 10:36:10 -0800334 // don't add if too many results
335 int count = 0;
336 for (int i = 0; 0 != (i = entry.indexOf(' ', i) + 1); count++) ;
337 if (count > RESULT_LIMIT) continue;
338 // add the word to the grammar
Martin Hibdon4a330952010-03-24 10:48:41 -0700339 // See Bug: 2457238.
340 // We used to store the entire list of components into the grammar.
341 // Unfortuantely, the recognizer has a fixed limit on the length of
342 // the "semantic" string, which is easy to overflow. So now,
343 // the we store our own mapping table between words and component
344 // names, and the entries in the grammar have the same value
345 // for literal and semantic.
346 mSrecGrammar.addWordToSlot("@Opens", label, null, 1, "V='" + label + "'");
mah64c64e72010-02-09 10:36:10 -0800347 }
348 }
349
350 /**
351 * Add a className to a hash table of class name lists.
352 * @param openEntries HashMap of lists of class names.
353 * @param label a label or word corresponding to the list of classes.
354 * @param className class name to add
355 */
356 private static void addClassName(HashMap<String,String> openEntries,
Martin Hibdon27d52002010-03-22 17:05:16 -0700357 String label, String packageName, String className) {
Martin Hibdon4a330952010-03-24 10:48:41 -0700358 String component = packageName + "/" + className;
mah64c64e72010-02-09 10:36:10 -0800359 String labelLowerCase = label.toLowerCase();
360 String classList = openEntries.get(labelLowerCase);
361
362 // first item in the list
363 if (classList == null) {
Martin Hibdon4a330952010-03-24 10:48:41 -0700364 openEntries.put(labelLowerCase, component);
mah64c64e72010-02-09 10:36:10 -0800365 return;
366 }
367 // already in list
Martin Hibdon4a330952010-03-24 10:48:41 -0700368 int index = classList.indexOf(component);
369 int after = index + component.length();
mah64c64e72010-02-09 10:36:10 -0800370 if (index != -1 && (index == 0 || classList.charAt(index - 1) == ' ') &&
371 (after == classList.length() || classList.charAt(after) == ' ')) return;
372
373 // add it to the end
Martin Hibdon4a330952010-03-24 10:48:41 -0700374 openEntries.put(labelLowerCase, classList + ' ' + component);
mah64c64e72010-02-09 10:36:10 -0800375 }
376
377 // map letters in Latin1 Supplement to basic ascii
378 // from http://en.wikipedia.org/wiki/Latin-1_Supplement_unicode_block
379 // not all letters map well, including Eth and Thorn
380 // TODO: this should really be all handled in the pronunciation engine
381 private final static char[] mLatin1Letters =
382 "AAAAAAACEEEEIIIIDNOOOOO OUUUUYDsaaaaaaaceeeeiiiidnooooo ouuuuydy".
383 toCharArray();
384 private final static int mLatin1Base = 0x00c0;
385
386 /**
387 * Reformat a raw name from the contact list into a form a
388 * {@link Recognizer.Grammar} can digest.
389 * @param name the raw name.
390 * @return the reformatted name.
391 */
392 private static String scrubName(String name) {
393 // replace '&' with ' and '
394 name = name.replace("&", " and ");
395
396 // replace '@' with ' at '
397 name = name.replace("@", " at ");
398
399 // remove '(...)'
400 while (true) {
401 int i = name.indexOf('(');
402 if (i == -1) break;
403 int j = name.indexOf(')', i);
404 if (j == -1) break;
405 name = name.substring(0, i) + " " + name.substring(j + 1);
406 }
407
408 // map letters of Latin1 Supplement to basic ascii
409 char[] nm = null;
410 for (int i = name.length() - 1; i >= 0; i--) {
411 char ch = name.charAt(i);
412 if (ch < ' ' || '~' < ch) {
413 if (nm == null) nm = name.toCharArray();
414 nm[i] = mLatin1Base <= ch && ch < mLatin1Base + mLatin1Letters.length ?
415 mLatin1Letters[ch - mLatin1Base] : ' ';
416 }
417 }
418 if (nm != null) {
419 name = new String(nm);
420 }
421
422 // if '.' followed by alnum, replace with ' dot '
423 while (true) {
424 int i = name.indexOf('.');
425 if (i == -1 ||
426 i + 1 >= name.length() ||
427 !Character.isLetterOrDigit(name.charAt(i + 1))) break;
428 name = name.substring(0, i) + " dot " + name.substring(i + 1);
429 }
430
431 // trim
432 name = name.trim();
433
434 // ensure at least one alphanumeric character, or the pron engine will fail
435 for (int i = name.length() - 1; true; i--) {
436 if (i < 0) return "";
437 char ch = name.charAt(i);
438 if (('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9')) {
439 break;
440 }
441 }
442
443 return name;
444 }
445
446 /**
447 * Delete all g2g files in the directory indicated by {@link File},
448 * which is typically /data/data/com.android.voicedialer/files.
449 * There should only be one g2g file at any one time, with a hashcode
450 * embedded in it's name, but if stale ones are present, this will delete
451 * them all.
452 * @param context fetch directory for the stuffed and compiled g2g file.
453 */
454 private static void deleteAllG2GFiles(Context context) {
455 FileFilter ff = new FileFilter() {
456 public boolean accept(File f) {
457 String name = f.getName();
458 return name.endsWith(".g2g");
459 }
460 };
461 File[] files = context.getFilesDir().listFiles(ff);
462 if (files != null) {
463 for (File file : files) {
Joe Onoratod3694c02011-04-07 18:41:13 -0700464 if (false) Log.d(TAG, "deleteAllG2GFiles " + file);
mah64c64e72010-02-09 10:36:10 -0800465 file.delete();
466 }
467 }
468 }
469
470 /**
471 * Delete G2G and OpenEntries files, to force regeneration of the g2g file
472 * from scratch.
473 * @param context fetch directory for file.
474 */
475 public static void deleteCachedGrammarFiles(Context context) {
476 deleteAllG2GFiles(context);
477 File oe = context.getFileStreamPath(OPEN_ENTRIES);
Joe Onoratod3694c02011-04-07 18:41:13 -0700478 if (false) Log.v(TAG, "deleteCachedGrammarFiles " + oe);
mah64c64e72010-02-09 10:36:10 -0800479 if (oe.exists()) oe.delete();
480 }
481
482 // NANP number formats
483 private final static String mNanpFormats =
484 "xxx xxx xxxx\n" +
485 "xxx xxxx\n" +
486 "x11\n";
487
488 // a list of country codes
489 private final static String mPlusFormats =
490
491 ////////////////////////////////////////////////////////////
492 // zone 1: nanp (north american numbering plan), us, canada, caribbean
493 ////////////////////////////////////////////////////////////
494
495 "+1 xxx xxx xxxx\n" + // nanp
496
497 ////////////////////////////////////////////////////////////
498 // zone 2: africa, some atlantic and indian ocean islands
499 ////////////////////////////////////////////////////////////
500
501 "+20 x xxx xxxx\n" + // Egypt
502 "+20 1x xxx xxxx\n" + // Egypt
503 "+20 xx xxx xxxx\n" + // Egypt
504 "+20 xxx xxx xxxx\n" + // Egypt
505
506 "+212 xxxx xxxx\n" + // Morocco
507
508 "+213 xx xx xx xx\n" + // Algeria
509 "+213 xx xxx xxxx\n" + // Algeria
510
511 "+216 xx xxx xxx\n" + // Tunisia
512
513 "+218 xx xxx xxx\n" + // Libya
514
515 "+22x \n" +
516 "+23x \n" +
517 "+24x \n" +
518 "+25x \n" +
519 "+26x \n" +
520
521 "+27 xx xxx xxxx\n" + // South africa
522
523 "+290 x xxx\n" + // Saint Helena, Tristan da Cunha
524
525 "+291 x xxx xxx\n" + // Eritrea
526
527 "+297 xxx xxxx\n" + // Aruba
528
529 "+298 xxx xxx\n" + // Faroe Islands
530
531 "+299 xxx xxx\n" + // Greenland
532
533 ////////////////////////////////////////////////////////////
534 // zone 3: europe, southern and small countries
535 ////////////////////////////////////////////////////////////
536
537 "+30 xxx xxx xxxx\n" + // Greece
538
539 "+31 6 xxxx xxxx\n" + // Netherlands
540 "+31 xx xxx xxxx\n" + // Netherlands
541 "+31 xxx xx xxxx\n" + // Netherlands
542
543 "+32 2 xxx xx xx\n" + // Belgium
544 "+32 3 xxx xx xx\n" + // Belgium
545 "+32 4xx xx xx xx\n" + // Belgium
546 "+32 9 xxx xx xx\n" + // Belgium
547 "+32 xx xx xx xx\n" + // Belgium
548
549 "+33 xxx xxx xxx\n" + // France
550
551 "+34 xxx xxx xxx\n" + // Spain
552
553 "+351 3xx xxx xxx\n" + // Portugal
554 "+351 7xx xxx xxx\n" + // Portugal
555 "+351 8xx xxx xxx\n" + // Portugal
556 "+351 xx xxx xxxx\n" + // Portugal
557
558 "+352 xx xxxx\n" + // Luxembourg
559 "+352 6x1 xxx xxx\n" + // Luxembourg
560 "+352 \n" + // Luxembourg
561
562 "+353 xxx xxxx\n" + // Ireland
563 "+353 xxxx xxxx\n" + // Ireland
564 "+353 xx xxx xxxx\n" + // Ireland
565
566 "+354 3xx xxx xxx\n" + // Iceland
567 "+354 xxx xxxx\n" + // Iceland
568
569 "+355 6x xxx xxxx\n" + // Albania
570 "+355 xxx xxxx\n" + // Albania
571
572 "+356 xx xx xx xx\n" + // Malta
573
574 "+357 xx xx xx xx\n" + // Cyprus
575
576 "+358 \n" + // Finland
577
578 "+359 \n" + // Bulgaria
579
580 "+36 1 xxx xxxx\n" + // Hungary
581 "+36 20 xxx xxxx\n" + // Hungary
582 "+36 21 xxx xxxx\n" + // Hungary
583 "+36 30 xxx xxxx\n" + // Hungary
584 "+36 70 xxx xxxx\n" + // Hungary
585 "+36 71 xxx xxxx\n" + // Hungary
586 "+36 xx xxx xxx\n" + // Hungary
587
588 "+370 6x xxx xxx\n" + // Lithuania
589 "+370 xxx xx xxx\n" + // Lithuania
590
591 "+371 xxxx xxxx\n" + // Latvia
592
593 "+372 5 xxx xxxx\n" + // Estonia
594 "+372 xxx xxxx\n" + // Estonia
595
596 "+373 6xx xx xxx\n" + // Moldova
597 "+373 7xx xx xxx\n" + // Moldova
598 "+373 xxx xxxxx\n" + // Moldova
599
600 "+374 xx xxx xxx\n" + // Armenia
601
602 "+375 xx xxx xxxx\n" + // Belarus
603
604 "+376 xx xx xx\n" + // Andorra
605
606 "+377 xxxx xxxx\n" + // Monaco
607
608 "+378 xxx xxx xxxx\n" + // San Marino
609
610 "+380 xxx xx xx xx\n" + // Ukraine
611
612 "+381 xx xxx xxxx\n" + // Serbia
613
614 "+382 xx xxx xxxx\n" + // Montenegro
615
616 "+385 xx xxx xxxx\n" + // Croatia
617
618 "+386 x xxx xxxx\n" + // Slovenia
619
620 "+387 xx xx xx xx\n" + // Bosnia and herzegovina
621
622 "+389 2 xxx xx xx\n" + // Macedonia
623 "+389 xx xx xx xx\n" + // Macedonia
624
625 "+39 xxx xxx xxx\n" + // Italy
626 "+39 3xx xxx xxxx\n" + // Italy
627 "+39 xx xxxx xxxx\n" + // Italy
628
629 ////////////////////////////////////////////////////////////
630 // zone 4: europe, northern countries
631 ////////////////////////////////////////////////////////////
632
633 "+40 xxx xxx xxx\n" + // Romania
634
635 "+41 xx xxx xx xx\n" + // Switzerland
636
637 "+420 xxx xxx xxx\n" + // Czech republic
638
639 "+421 xxx xxx xxx\n" + // Slovakia
640
641 "+421 xxx xxx xxxx\n" + // Liechtenstein
642
643 "+43 \n" + // Austria
644
645 "+44 xxx xxx xxxx\n" + // UK
646
647 "+45 xx xx xx xx\n" + // Denmark
648
649 "+46 \n" + // Sweden
650
651 "+47 xxxx xxxx\n" + // Norway
652
653 "+48 xx xxx xxxx\n" + // Poland
654
655 "+49 1xx xxxx xxx\n" + // Germany
656 "+49 1xx xxxx xxxx\n" + // Germany
657 "+49 \n" + // Germany
658
659 ////////////////////////////////////////////////////////////
660 // zone 5: latin america
661 ////////////////////////////////////////////////////////////
662
663 "+50x \n" +
664
665 "+51 9xx xxx xxx\n" + // Peru
666 "+51 1 xxx xxxx\n" + // Peru
667 "+51 xx xx xxxx\n" + // Peru
668
669 "+52 1 xxx xxx xxxx\n" + // Mexico
670 "+52 xxx xxx xxxx\n" + // Mexico
671
672 "+53 xxxx xxxx\n" + // Cuba
673
674 "+54 9 11 xxxx xxxx\n" + // Argentina
675 "+54 9 xxx xxx xxxx\n" + // Argentina
676 "+54 11 xxxx xxxx\n" + // Argentina
677 "+54 xxx xxx xxxx\n" + // Argentina
678
679 "+55 xx xxxx xxxx\n" + // Brazil
680
681 "+56 2 xxxxxx\n" + // Chile
682 "+56 9 xxxx xxxx\n" + // Chile
683 "+56 xx xxxxxx\n" + // Chile
684 "+56 xx xxxxxxx\n" + // Chile
685
686 "+57 x xxx xxxx\n" + // Columbia
687 "+57 3xx xxx xxxx\n" + // Columbia
688
689 "+58 xxx xxx xxxx\n" + // Venezuela
690
691 "+59x \n" +
692
693 ////////////////////////////////////////////////////////////
694 // zone 6: southeast asia and oceania
695 ////////////////////////////////////////////////////////////
696
697 // TODO is this right?
698 "+60 3 xxxx xxxx\n" + // Malaysia
699 "+60 8x xxxxxx\n" + // Malaysia
700 "+60 x xxx xxxx\n" + // Malaysia
701 "+60 14 x xxx xxxx\n" + // Malaysia
702 "+60 1x xxx xxxx\n" + // Malaysia
703 "+60 x xxxx xxxx\n" + // Malaysia
704 "+60 \n" + // Malaysia
705
706 "+61 4xx xxx xxx\n" + // Australia
707 "+61 x xxxx xxxx\n" + // Australia
708
709 // TODO: is this right?
710 "+62 8xx xxxx xxxx\n" + // Indonesia
711 "+62 21 xxxxx\n" + // Indonesia
712 "+62 xx xxxxxx\n" + // Indonesia
713 "+62 xx xxx xxxx\n" + // Indonesia
714 "+62 xx xxxx xxxx\n" + // Indonesia
715
716 "+63 2 xxx xxxx\n" + // Phillipines
717 "+63 xx xxx xxxx\n" + // Phillipines
718 "+63 9xx xxx xxxx\n" + // Phillipines
719
720 // TODO: is this right?
721 "+64 2 xxx xxxx\n" + // New Zealand
722 "+64 2 xxx xxxx x\n" + // New Zealand
723 "+64 2 xxx xxxx xx\n" + // New Zealand
724 "+64 x xxx xxxx\n" + // New Zealand
725
726 "+65 xxxx xxxx\n" + // Singapore
727
728 "+66 8 xxxx xxxx\n" + // Thailand
729 "+66 2 xxx xxxx\n" + // Thailand
730 "+66 xx xx xxxx\n" + // Thailand
731
732 "+67x \n" +
733 "+68x \n" +
734
735 "+690 x xxx\n" + // Tokelau
736
737 "+691 xxx xxxx\n" + // Micronesia
738
739 "+692 xxx xxxx\n" + // marshall Islands
740
741 ////////////////////////////////////////////////////////////
742 // zone 7: russia and kazakstan
743 ////////////////////////////////////////////////////////////
744
745 "+7 6xx xx xxxxx\n" + // Kazakstan
746 "+7 7xx 2 xxxxxx\n" + // Kazakstan
747 "+7 7xx xx xxxxx\n" + // Kazakstan
748
749 "+7 xxx xxx xx xx\n" + // Russia
750
751 ////////////////////////////////////////////////////////////
752 // zone 8: east asia
753 ////////////////////////////////////////////////////////////
754
755 "+81 3 xxxx xxxx\n" + // Japan
756 "+81 6 xxxx xxxx\n" + // Japan
757 "+81 xx xxx xxxx\n" + // Japan
758 "+81 x0 xxxx xxxx\n" + // Japan
759
760 "+82 2 xxx xxxx\n" + // South korea
761 "+82 2 xxxx xxxx\n" + // South korea
762 "+82 xx xxxx xxxx\n" + // South korea
763 "+82 xx xxx xxxx\n" + // South korea
764
765 "+84 4 xxxx xxxx\n" + // Vietnam
766 "+84 xx xxxx xxx\n" + // Vietnam
767 "+84 xx xxxx xxxx\n" + // Vietnam
768
769 "+850 \n" + // North Korea
770
771 "+852 xxxx xxxx\n" + // Hong Kong
772
773 "+853 xxxx xxxx\n" + // Macau
774
775 "+855 1x xxx xxx\n" + // Cambodia
776 "+855 9x xxx xxx\n" + // Cambodia
777 "+855 xx xx xx xx\n" + // Cambodia
778
779 "+856 20 x xxx xxx\n" + // Laos
780 "+856 xx xxx xxx\n" + // Laos
781
782 "+852 xxxx xxxx\n" + // Hong kong
783
784 "+86 10 xxxx xxxx\n" + // China
785 "+86 2x xxxx xxxx\n" + // China
786 "+86 xxx xxx xxxx\n" + // China
787 "+86 xxx xxxx xxxx\n" + // China
788
789 "+880 xx xxxx xxxx\n" + // Bangladesh
790
791 "+886 \n" + // Taiwan
792
793 ////////////////////////////////////////////////////////////
794 // zone 9: south asia, west asia, central asia, middle east
795 ////////////////////////////////////////////////////////////
796
797 "+90 xxx xxx xxxx\n" + // Turkey
798
799 "+91 9x xx xxxxxx\n" + // India
800 "+91 xx xxxx xxxx\n" + // India
801
802 "+92 xx xxx xxxx\n" + // Pakistan
803 "+92 3xx xxx xxxx\n" + // Pakistan
804
805 "+93 70 xxx xxx\n" + // Afghanistan
806 "+93 xx xxx xxxx\n" + // Afghanistan
807
808 "+94 xx xxx xxxx\n" + // Sri Lanka
809
810 "+95 1 xxx xxx\n" + // Burma
811 "+95 2 xxx xxx\n" + // Burma
812 "+95 xx xxxxx\n" + // Burma
813 "+95 9 xxx xxxx\n" + // Burma
814
815 "+960 xxx xxxx\n" + // Maldives
816
817 "+961 x xxx xxx\n" + // Lebanon
818 "+961 xx xxx xxx\n" + // Lebanon
819
820 "+962 7 xxxx xxxx\n" + // Jordan
821 "+962 x xxx xxxx\n" + // Jordan
822
823 "+963 11 xxx xxxx\n" + // Syria
824 "+963 xx xxx xxx\n" + // Syria
825
826 "+964 \n" + // Iraq
827
828 "+965 xxxx xxxx\n" + // Kuwait
829
830 "+966 5x xxx xxxx\n" + // Saudi Arabia
831 "+966 x xxx xxxx\n" + // Saudi Arabia
832
833 "+967 7xx xxx xxx\n" + // Yemen
834 "+967 x xxx xxx\n" + // Yemen
835
836 "+968 xxxx xxxx\n" + // Oman
837
838 "+970 5x xxx xxxx\n" + // Palestinian Authority
839 "+970 x xxx xxxx\n" + // Palestinian Authority
840
841 "+971 5x xxx xxxx\n" + // United Arab Emirates
842 "+971 x xxx xxxx\n" + // United Arab Emirates
843
844 "+972 5x xxx xxxx\n" + // Israel
845 "+972 x xxx xxxx\n" + // Israel
846
847 "+973 xxxx xxxx\n" + // Bahrain
848
849 "+974 xxx xxxx\n" + // Qatar
850
851 "+975 1x xxx xxx\n" + // Bhutan
852 "+975 x xxx xxx\n" + // Bhutan
853
854 "+976 \n" + // Mongolia
855
856 "+977 xxxx xxxx\n" + // Nepal
857 "+977 98 xxxx xxxx\n" + // Nepal
858
859 "+98 xxx xxx xxxx\n" + // Iran
860
861 "+992 xxx xxx xxx\n" + // Tajikistan
862
863 "+993 xxxx xxxx\n" + // Turkmenistan
864
865 "+994 xx xxx xxxx\n" + // Azerbaijan
866 "+994 xxx xxxxx\n" + // Azerbaijan
867
868 "+995 xx xxx xxx\n" + // Georgia
869
870 "+996 xxx xxx xxx\n" + // Kyrgyzstan
871
872 "+998 xx xxx xxxx\n"; // Uzbekistan
873
874
875 // TODO: need to handle variable number notation
876 private static String formatNumber(String formats, String number) {
877 number = number.trim();
878 final int nlen = number.length();
879 final int formatslen = formats.length();
880 StringBuffer sb = new StringBuffer();
881
882 // loop over country codes
883 for (int f = 0; f < formatslen; ) {
884 sb.setLength(0);
885 int n = 0;
886
887 // loop over letters of pattern
888 while (true) {
889 final char fch = formats.charAt(f);
890 if (fch == '\n' && n >= nlen) return sb.toString();
891 if (fch == '\n' || n >= nlen) break;
892 final char nch = number.charAt(n);
893 // pattern matches number
894 if (fch == nch || (fch == 'x' && Character.isDigit(nch))) {
895 f++;
896 n++;
897 sb.append(nch);
898 }
899 // don't match ' ' in pattern, but insert into result
900 else if (fch == ' ') {
901 f++;
902 sb.append(' ');
903 // ' ' at end -> match all the rest
904 if (formats.charAt(f) == '\n') {
905 return sb.append(number, n, nlen).toString();
906 }
907 }
908 // match failed
909 else break;
910 }
911
912 // step to the next pattern
913 f = formats.indexOf('\n', f) + 1;
914 if (f == 0) break;
915 }
916
917 return null;
918 }
919
920 /**
921 * Format a phone number string.
922 * At some point, PhoneNumberUtils.formatNumber will handle this.
923 * @param num phone number string.
924 * @return formatted phone number string.
925 */
926 private static String formatNumber(String num) {
927 String fmt = null;
928
929 fmt = formatNumber(mPlusFormats, num);
930 if (fmt != null) return fmt;
931
932 fmt = formatNumber(mNanpFormats, num);
933 if (fmt != null) return fmt;
934
935 return null;
936 }
937
938 /**
939 * Called when recognition succeeds. It receives a list
940 * of results, builds a corresponding list of Intents, and
941 * passes them to the {@link RecognizerClient}, which selects and
942 * performs a corresponding action.
943 * @param recognizerClient the client that will be sent the results
944 */
945 protected void onRecognitionSuccess(RecognizerClient recognizerClient)
946 throws InterruptedException {
Joe Onoratod3694c02011-04-07 18:41:13 -0700947 if (false) Log.d(TAG, "onRecognitionSuccess");
mah64c64e72010-02-09 10:36:10 -0800948
949 if (mLogger != null) mLogger.logNbestHeader();
950
951 ArrayList<Intent> intents = new ArrayList<Intent>();
mah64c64e72010-02-09 10:36:10 -0800952
mah7a551502010-02-23 15:20:44 -0800953 int highestConfidence = 0;
954 int examineLimit = RESULT_LIMIT;
955 if (mMinimizeResults) {
956 examineLimit = 1;
957 }
mah64c64e72010-02-09 10:36:10 -0800958 for (int result = 0; result < mSrec.getResultCount() &&
mah7a551502010-02-23 15:20:44 -0800959 intents.size() < examineLimit; result++) {
mah64c64e72010-02-09 10:36:10 -0800960
961 // parse the semanticMeaning string and build an Intent
962 String conf = mSrec.getResult(result, Recognizer.KEY_CONFIDENCE);
963 String literal = mSrec.getResult(result, Recognizer.KEY_LITERAL);
964 String semantic = mSrec.getResult(result, Recognizer.KEY_MEANING);
965 String msg = "conf=" + conf + " lit=" + literal + " sem=" + semantic;
Joe Onoratod3694c02011-04-07 18:41:13 -0700966 if (false) Log.d(TAG, msg);
mah7a551502010-02-23 15:20:44 -0800967 int confInt = Integer.parseInt(conf);
968 if (highestConfidence < confInt) highestConfidence = confInt;
969 if (confInt < MINIMUM_CONFIDENCE || confInt * 2 < highestConfidence) {
Joe Onoratod3694c02011-04-07 18:41:13 -0700970 if (false) Log.d(TAG, "confidence too low, dropping");
mah7a551502010-02-23 15:20:44 -0800971 break;
972 }
mah64c64e72010-02-09 10:36:10 -0800973 if (mLogger != null) mLogger.logLine(msg);
974 String[] commands = semantic.trim().split(" ");
975
976 // DIAL 650 867 5309
977 // DIAL 867 5309
978 // DIAL 911
mah7a551502010-02-23 15:20:44 -0800979 if ("DIAL".equalsIgnoreCase(commands[0])) {
mah64c64e72010-02-09 10:36:10 -0800980 Uri uri = Uri.fromParts("tel", commands[1], null);
981 String num = formatNumber(commands[1]);
982 if (num != null) {
983 addCallIntent(intents, uri,
mah7a551502010-02-23 15:20:44 -0800984 literal.split(" ")[0].trim() + " " + num, "", 0);
mah64c64e72010-02-09 10:36:10 -0800985 }
986 }
987
988 // CALL JACK JONES
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -0800989 // commands should become ["CALL", id, id, ..] reflecting addNameEntriesToGrammar().
990 else if ("CALL".equalsIgnoreCase(commands[0])
991 && commands.length >= PHONE_ID_COUNT + 1) {
mah64c64e72010-02-09 10:36:10 -0800992 // parse the ids
993 long contactId = Long.parseLong(commands[1]); // people table
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -0800994 long primaryId = Long.parseLong(commands[2]); // phones table
mah64c64e72010-02-09 10:36:10 -0800995 long homeId = Long.parseLong(commands[3]); // phones table
996 long mobileId = Long.parseLong(commands[4]); // phones table
997 long workId = Long.parseLong(commands[5]); // phones table
998 long otherId = Long.parseLong(commands[6]); // phones table
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -0800999 long fallbackId = Long.parseLong(commands[7]); // phones table
mah64c64e72010-02-09 10:36:10 -08001000 Resources res = mActivity.getResources();
1001
1002 int count = 0;
1003
nieyz06048a7d0d22015-09-29 07:01:41 -04001004 String ContactIdString =Long.toString(contactId);
1005 String homeNumber ="";
1006 String mobileNumber ="";
1007 String workNumber="";
1008 String otherNumber="";
1009
1010 ContentResolver cr = mActivity.getContentResolver();
1011 Cursor PhoneCur = cr.query(Phone.CONTENT_URI,
1012 null,
1013 Phone.RAW_CONTACT_ID +" =?",
1014 new String[]{ContactIdString}, null);
1015 while (PhoneCur.moveToNext())
1016 {
1017 String number = PhoneCur.getString(PhoneCur.getColumnIndex(Phone.NUMBER));
1018 String numberType = PhoneCur.getString(PhoneCur.getColumnIndex(Phone.TYPE));
1019 if (Integer.parseInt(numberType) == Phone.TYPE_HOME){
1020 homeNumber = number;
1021 }else if (Integer.parseInt(numberType) == Phone.TYPE_MOBILE){
1022 mobileNumber = number;
1023 }else if (Integer.parseInt(numberType) == Phone.TYPE_WORK){
1024 workNumber = number;
1025 }else {
1026 otherNumber = number;
1027 }
1028 }
1029 Log.d(TAG, "commands.length= " +commands.length +" contactId" +contactId );
1030
mah64c64e72010-02-09 10:36:10 -08001031 //
1032 // generate the best entry corresponding to what was said
1033 //
1034
1035 // 'CALL JACK JONES AT HOME|MOBILE|WORK|OTHER'
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001036 if (commands.length == PHONE_ID_COUNT + 2) {
1037 // The last command should imply the type of the phone number.
1038 final String spokenPhoneIdCommand = commands[PHONE_ID_COUNT + 1];
mah64c64e72010-02-09 10:36:10 -08001039 long spokenPhoneId =
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001040 "H".equalsIgnoreCase(spokenPhoneIdCommand) ? homeId :
1041 "M".equalsIgnoreCase(spokenPhoneIdCommand) ? mobileId :
1042 "W".equalsIgnoreCase(spokenPhoneIdCommand) ? workId :
1043 "O".equalsIgnoreCase(spokenPhoneIdCommand) ? otherId :
mah64c64e72010-02-09 10:36:10 -08001044 VoiceContact.ID_UNDEFINED;
nieyz06048a7d0d22015-09-29 07:01:41 -04001045 String spokenNumber =
1046 "H".equalsIgnoreCase(spokenPhoneIdCommand) ? homeNumber :
1047 "M".equalsIgnoreCase(spokenPhoneIdCommand) ? mobileNumber :
1048 "W".equalsIgnoreCase(spokenPhoneIdCommand) ? workNumber :
1049 "O".equalsIgnoreCase(spokenPhoneIdCommand) ? otherNumber :
1050 "";
1051
1052 if (spokenPhoneId != VoiceContact.ID_UNDEFINED) {
1053 /*addCallIntent(intents, ContentUris.withAppendedId(
mah64c64e72010-02-09 10:36:10 -08001054 Phone.CONTENT_URI, spokenPhoneId),
nieyz06048a7d0d22015-09-29 07:01:41 -04001055 literal, spokenPhoneIdCommand, 0);*/
1056 addCallIntent(intents, Uri.parse("tel:" + spokenNumber),
1057 literal, spokenPhoneIdCommand, 0);
mah64c64e72010-02-09 10:36:10 -08001058 count++;
nieyz06048a7d0d22015-09-29 07:01:41 -04001059 Log.d(TAG, "spokenNumber");
mah64c64e72010-02-09 10:36:10 -08001060 }
1061 }
1062
1063 // 'CALL JACK JONES', with valid default phoneId
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001064 else if (commands.length == PHONE_ID_COUNT + 1) {
mah7a551502010-02-23 15:20:44 -08001065 String phoneType = null;
1066 CharSequence phoneIdMsg = null;
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001067 if (primaryId == VoiceContact.ID_UNDEFINED) {
mah7a551502010-02-23 15:20:44 -08001068 phoneType = null;
1069 phoneIdMsg = null;
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001070 } else if (primaryId == homeId) {
mah7a551502010-02-23 15:20:44 -08001071 phoneType = "H";
1072 phoneIdMsg = res.getText(R.string.at_home);
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001073 } else if (primaryId == mobileId) {
mah7a551502010-02-23 15:20:44 -08001074 phoneType = "M";
1075 phoneIdMsg = res.getText(R.string.on_mobile);
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001076 } else if (primaryId == workId) {
mah7a551502010-02-23 15:20:44 -08001077 phoneType = "W";
1078 phoneIdMsg = res.getText(R.string.at_work);
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001079 } else if (primaryId == otherId) {
mah7a551502010-02-23 15:20:44 -08001080 phoneType = "O";
1081 phoneIdMsg = res.getText(R.string.at_other);
1082 }
nieyz06048a7d0d22015-09-29 07:01:41 -04001083
1084 String spokenNumber =
1085 "H".equalsIgnoreCase(phoneType) ? homeNumber :
1086 "M".equalsIgnoreCase(phoneType) ? mobileNumber :
1087 "W".equalsIgnoreCase(phoneType) ? workNumber :
1088 "O".equalsIgnoreCase(phoneType) ? otherNumber :
1089 "";
mah64c64e72010-02-09 10:36:10 -08001090 if (phoneIdMsg != null) {
nieyz06048a7d0d22015-09-29 07:01:41 -04001091 /*addCallIntent(intents, ContentUris.withAppendedId(
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001092 Phone.CONTENT_URI, primaryId),
nieyz06048a7d0d22015-09-29 07:01:41 -04001093 literal + phoneIdMsg, phoneType, 0);*/
1094 addCallIntent(intents,Uri.parse("tel:" + spokenNumber),
1095 literal + phoneIdMsg, phoneType, 0);
mah64c64e72010-02-09 10:36:10 -08001096 count++;
1097 }
1098 }
1099
mah7a551502010-02-23 15:20:44 -08001100 if (count == 0 || !mMinimizeResults) {
1101 //
1102 // generate all other entries for this person
1103 //
mah64c64e72010-02-09 10:36:10 -08001104
mah7a551502010-02-23 15:20:44 -08001105 // trim last two words, ie 'at home', etc
1106 String lit = literal;
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001107 if (commands.length == PHONE_ID_COUNT + 2) {
mah7a551502010-02-23 15:20:44 -08001108 String[] words = literal.trim().split(" ");
1109 StringBuffer sb = new StringBuffer();
1110 for (int i = 0; i < words.length - 2; i++) {
1111 if (i != 0) {
1112 sb.append(' ');
1113 }
1114 sb.append(words[i]);
mah64c64e72010-02-09 10:36:10 -08001115 }
mah7a551502010-02-23 15:20:44 -08001116 lit = sb.toString();
mah64c64e72010-02-09 10:36:10 -08001117 }
mah64c64e72010-02-09 10:36:10 -08001118
mah7a551502010-02-23 15:20:44 -08001119 // add 'CALL JACK JONES at home' using phoneId
1120 if (homeId != VoiceContact.ID_UNDEFINED) {
nieyz06048a7d0d22015-09-29 07:01:41 -04001121 addCallIntent(intents, Uri.parse("tel:" + homeNumber),
mah7a551502010-02-23 15:20:44 -08001122 lit + res.getText(R.string.at_home), "H", 0);
1123 count++;
1124 }
mah64c64e72010-02-09 10:36:10 -08001125
mah7a551502010-02-23 15:20:44 -08001126 // add 'CALL JACK JONES on mobile' using mobileId
1127 if (mobileId != VoiceContact.ID_UNDEFINED) {
nieyz06048a7d0d22015-09-29 07:01:41 -04001128 addCallIntent(intents, Uri.parse("tel:" + mobileNumber),
mah7a551502010-02-23 15:20:44 -08001129 lit + res.getText(R.string.on_mobile), "M", 0);
1130 count++;
1131 }
mah64c64e72010-02-09 10:36:10 -08001132
mah7a551502010-02-23 15:20:44 -08001133 // add 'CALL JACK JONES at work' using workId
1134 if (workId != VoiceContact.ID_UNDEFINED) {
nieyz06048a7d0d22015-09-29 07:01:41 -04001135 addCallIntent(intents, Uri.parse("tel:" + workNumber),
mah7a551502010-02-23 15:20:44 -08001136 lit + res.getText(R.string.at_work), "W", 0);
1137 count++;
1138 }
mah64c64e72010-02-09 10:36:10 -08001139
mah7a551502010-02-23 15:20:44 -08001140 // add 'CALL JACK JONES at other' using otherId
1141 if (otherId != VoiceContact.ID_UNDEFINED) {
nieyz06048a7d0d22015-09-29 07:01:41 -04001142 addCallIntent(intents, Uri.parse("tel:" + otherNumber),
mah7a551502010-02-23 15:20:44 -08001143 lit + res.getText(R.string.at_other), "O", 0);
1144 count++;
1145 }
mah64c64e72010-02-09 10:36:10 -08001146
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001147 if (fallbackId != VoiceContact.ID_UNDEFINED) {
1148 addCallIntent(intents, ContentUris.withAppendedId(
1149 Phone.CONTENT_URI, fallbackId),
1150 lit, "", 0);
1151 count++;
1152 }
mah64c64e72010-02-09 10:36:10 -08001153 }
1154 }
Martin Hibdon8c7aac02010-03-12 15:16:57 -08001155
1156 else if ("X".equalsIgnoreCase(commands[0])) {
1157 Intent intent = new Intent(RecognizerEngine.ACTION_RECOGNIZER_RESULT, null);
1158 intent.putExtra(RecognizerEngine.SENTENCE_EXTRA, literal);
1159 intent.putExtra(RecognizerEngine.SEMANTIC_EXTRA, semantic);
1160 addIntent(intents, intent);
1161 }
1162
mah64c64e72010-02-09 10:36:10 -08001163 // "CALL VoiceMail"
mah7a551502010-02-23 15:20:44 -08001164 else if ("voicemail".equalsIgnoreCase(commands[0]) && commands.length == 1) {
mah64c64e72010-02-09 10:36:10 -08001165 addCallIntent(intents, Uri.fromParts("voicemail", "x", null),
mah7a551502010-02-23 15:20:44 -08001166 literal, "", Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
mah64c64e72010-02-09 10:36:10 -08001167 }
1168
1169 // "REDIAL"
mah7a551502010-02-23 15:20:44 -08001170 else if ("redial".equalsIgnoreCase(commands[0]) && commands.length == 1) {
mah64c64e72010-02-09 10:36:10 -08001171 String number = VoiceContact.redialNumber(mActivity);
1172 if (number != null) {
mah7a551502010-02-23 15:20:44 -08001173 addCallIntent(intents, Uri.fromParts("tel", number, null),
1174 literal, "", Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
mah64c64e72010-02-09 10:36:10 -08001175 }
1176 }
1177
1178 // "Intent ..."
1179 else if ("Intent".equalsIgnoreCase(commands[0])) {
1180 for (int i = 1; i < commands.length; i++) {
1181 try {
1182 Intent intent = Intent.getIntent(commands[i]);
1183 if (intent.getStringExtra(SENTENCE_EXTRA) == null) {
1184 intent.putExtra(SENTENCE_EXTRA, literal);
1185 }
1186 addIntent(intents, intent);
1187 } catch (URISyntaxException e) {
Joe Onoratod3694c02011-04-07 18:41:13 -07001188 if (false) {
mah7a551502010-02-23 15:20:44 -08001189 Log.d(TAG, "onRecognitionSuccess: poorly " +
1190 "formed URI in grammar" + e);
mah64c64e72010-02-09 10:36:10 -08001191 }
1192 }
1193 }
1194 }
1195
1196 // "OPEN ..."
Martin Hibdon5f256192010-03-04 17:44:51 -08001197 else if ("OPEN".equalsIgnoreCase(commands[0]) && mAllowOpenEntries) {
mah64c64e72010-02-09 10:36:10 -08001198 PackageManager pm = mActivity.getPackageManager();
Martin Hibdon4a330952010-03-24 10:48:41 -07001199 if (commands.length > 1 & mOpenEntries != null) {
Martin Hibdone5258ff2010-04-01 16:31:15 -07001200 // the semantic value is equal to the literal in this case.
Martin Hibdon4a330952010-03-24 10:48:41 -07001201 // We have to do the mapping from this text to the
1202 // componentname ourselves. See Bug: 2457238.
1203 // The problem is that the list of all componentnames
1204 // can be pretty large and overflow the limit that
1205 // the recognizer has.
1206 String meaning = mOpenEntries.get(commands[1]);
1207 String[] components = meaning.trim().split(" ");
1208 for (int i=0; i < components.length; i++) {
1209 String component = components[i];
1210 Intent intent = new Intent(Intent.ACTION_MAIN);
mah64c64e72010-02-09 10:36:10 -08001211 intent.addCategory("android.intent.category.VOICE_LAUNCH");
Martin Hibdon4a330952010-03-24 10:48:41 -07001212 String packageName = component.substring(
1213 0, component.lastIndexOf('/'));
1214 String className = component.substring(
1215 component.lastIndexOf('/')+1, component.length());
Martin Hibdon27d52002010-03-22 17:05:16 -07001216 intent.setClassName(packageName, className);
Martin Hibdon4a330952010-03-24 10:48:41 -07001217 List<ResolveInfo> riList = pm.queryIntentActivities(intent, 0);
1218 for (ResolveInfo ri : riList) {
1219 String label = ri.loadLabel(pm).toString();
1220 intent = new Intent(Intent.ACTION_MAIN);
1221 intent.addCategory("android.intent.category.VOICE_LAUNCH");
1222 intent.setClassName(packageName, className);
1223 intent.putExtra(SENTENCE_EXTRA, literal.split(" ")[0] + " " + label);
1224 addIntent(intents, intent);
1225 }
mah64c64e72010-02-09 10:36:10 -08001226 }
1227 }
1228 }
1229
1230 // can't parse result
1231 else {
Joe Onoratod3694c02011-04-07 18:41:13 -07001232 if (false) Log.d(TAG, "onRecognitionSuccess: parse error");
mah64c64e72010-02-09 10:36:10 -08001233 }
1234 }
1235
1236 // log if requested
1237 if (mLogger != null) mLogger.logIntents(intents);
1238
1239 // bail out if cancelled
1240 if (Thread.interrupted()) throw new InterruptedException();
1241
1242 if (intents.size() == 0) {
1243 // TODO: strip HOME|MOBILE|WORK and try default here?
1244 recognizerClient.onRecognitionFailure("No Intents generated");
1245 }
1246 else {
1247 recognizerClient.onRecognitionSuccess(
1248 intents.toArray(new Intent[intents.size()]));
1249 }
1250 }
1251
1252 // only add if different
1253 private static void addCallIntent(ArrayList<Intent> intents, Uri uri, String literal,
mah7a551502010-02-23 15:20:44 -08001254 String phoneType, int flags) {
Daisuke Miyakawa85a421c2011-11-28 14:07:11 -08001255 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, uri)
1256 .setFlags(flags)
1257 .putExtra(SENTENCE_EXTRA, literal)
1258 .putExtra(PHONE_TYPE_EXTRA, phoneType);
mah64c64e72010-02-09 10:36:10 -08001259 addIntent(intents, intent);
1260 }
1261}