1 /*
2  * Copyright (C) 2007 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  */
16 
17 package com.android.voicedialer;
18 
19 import android.app.Activity;
20 import android.database.Cursor;
21 import android.database.DatabaseUtils;
22 import android.provider.ContactsContract.CommonDataKinds.Phone;
23 import android.provider.CallLog;
24 import android.util.Log;
25 import java.io.BufferedReader;
26 import java.io.File;
27 import java.io.FileReader;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 
33 /**
34  * This class represents a person who may be called via the VoiceDialer app.
35  * The person has a name and a list of phones (home, mobile, work, other).
36  */
37 public class VoiceContact {
38     private static final String TAG = "VoiceContact";
39 
40     /**
41      * Corresponding row doesn't exist.
42      */
43     public static final long ID_UNDEFINED = -1;
44 
45     public final String mName;
46     public final long mContactId;
47     public final long mPrimaryId;
48     public final long mHomeId;
49     public final long mMobileId;
50     public final long mWorkId;
51     public final long mOtherId;
52     /**
53      * Id for a phone number which doesn't belong to any other ids stored above.
54      */
55     public final long mFallbackId;
56 
57     /**
58      * Constructor.
59      *
60      * @param name person's name.
61      * @param contactId ID in person table.
62      * @param primaryId primary ID in phone table.
63      * @param homeId home ID in phone table.
64      * @param mobileId mobile ID in phone table.
65      * @param workId work ID in phone table.
66      * @param otherId other ID in phone table.
67      */
VoiceContact(String name, long contactId, long primaryId, long homeId, long mobileId, long workId, long otherId, long fallbackId)68     private VoiceContact(String name, long contactId, long primaryId,
69             long homeId, long mobileId, long workId, long otherId, long fallbackId) {
70         mName = name;
71         mContactId = contactId;
72         mPrimaryId = primaryId;
73         mHomeId = homeId;
74         mMobileId = mobileId;
75         mWorkId = workId;
76         mOtherId = otherId;
77         mFallbackId = fallbackId;
78     }
79 
80     @Override
hashCode()81     public int hashCode() {
82         final int LARGE_PRIME = 1610612741;
83         int hash = 0;
84         hash = LARGE_PRIME * (hash + (int)mContactId);
85         hash = LARGE_PRIME * (hash + (int)mPrimaryId);
86         hash = LARGE_PRIME * (hash + (int)mHomeId);
87         hash = LARGE_PRIME * (hash + (int)mMobileId);
88         hash = LARGE_PRIME * (hash + (int)mWorkId);
89         hash = LARGE_PRIME * (hash + (int)mOtherId);
90         hash = LARGE_PRIME * (hash + (int)mFallbackId);
91         return mName.hashCode() + hash;
92     }
93 
94     @Override
toString()95     public String toString() {
96         return "mName=" + mName
97                 + " mPersonId=" + mContactId
98                 + " mPrimaryId=" + mPrimaryId
99                 + " mHomeId=" + mHomeId
100                 + " mMobileId=" + mMobileId
101                 + " mWorkId=" + mWorkId
102                 + " mOtherId=" + mOtherId
103                 + " mFallbackId=" + mFallbackId;
104     }
105 
106     /**
107      * @param activity The VoiceDialerActivity instance.
108      * @return List of {@link VoiceContact} from
109      * the contact list content provider.
110      */
getVoiceContacts(Activity activity)111     public static List<VoiceContact> getVoiceContacts(Activity activity) {
112         if (false) Log.d(TAG, "VoiceContact.getVoiceContacts");
113 
114         List<VoiceContact> contacts = new ArrayList<VoiceContact>();
115 
116         String[] phonesProjection = new String[] {
117             Phone._ID,
118             Phone.TYPE,
119             Phone.IS_PRIMARY,
120             // TODO: handle type != 0,1,2, and use LABEL
121             Phone.LABEL,
122             Phone.DISPLAY_NAME,
123             Phone.CONTACT_ID,
124         };
125 
126         // Table is sorted by number of times contacted and name. If we cannot fit all contacts
127         // in the recognizer, we will at least have the commonly used ones.
128         Cursor cursor = activity.getContentResolver().query(
129                 Phone.CONTENT_URI, phonesProjection,
130                 Phone.NUMBER + " NOT NULL", null,
131                 Phone.LAST_TIME_CONTACTED + " DESC, "
132                         + Phone.DISPLAY_NAME + " ASC, "
133                         + Phone._ID + " DESC");
134 
135         final int phoneIdColumn = cursor.getColumnIndexOrThrow(Phone._ID);
136         final int typeColumn = cursor.getColumnIndexOrThrow(Phone.TYPE);
137         final int isPrimaryColumn = cursor.getColumnIndexOrThrow(Phone.IS_PRIMARY);
138         final int labelColumn = cursor.getColumnIndexOrThrow(Phone.LABEL);
139         final int nameColumn = cursor.getColumnIndexOrThrow(Phone.DISPLAY_NAME);
140         final int personIdColumn = cursor.getColumnIndexOrThrow(Phone.CONTACT_ID);
141 
142         // pieces of next VoiceContact
143         String name = null;
144         long personId = ID_UNDEFINED;
145         long primaryId = ID_UNDEFINED;
146         long homeId = ID_UNDEFINED;
147         long mobileId = ID_UNDEFINED;
148         long workId = ID_UNDEFINED;
149         long otherId = ID_UNDEFINED;
150         long fallbackId = ID_UNDEFINED;
151 
152         // loop over phone table
153         for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
154             long phoneIdAtCursor = cursor.getLong(phoneIdColumn);
155             int typeAtCursor = cursor.getInt(typeColumn);
156             long isPrimaryAtCursor = cursor.getLong(isPrimaryColumn);
157             String labelAtCursor = cursor.getString(labelColumn);
158             String nameAtCursor = cursor.getString(nameColumn);
159             long personIdAtCursor = cursor.getLong(personIdColumn);
160 
161             /*
162             if (false) {
163                 Log.d(TAG, "phoneId=" + phoneIdAtCursor
164                         + " type=" + typeAtCursor
165                         + " isPrimary=" + isPrimaryAtCursor
166                         + " label=" + labelAtCursor
167                         + " name=" + nameAtCursor
168                         + " personId=" + personIdAtCursor
169                         );
170             }
171             */
172 
173             // encountered a new name, so generate current VoiceContact
174             if (name != null && !name.equals(nameAtCursor)) {
175                 contacts.add(new VoiceContact(name, personId, primaryId,
176                         homeId, mobileId, workId, otherId, fallbackId));
177                 name = null;
178             }
179 
180             // start accumulating pieces for a new VoiceContact
181             if (name == null) {
182                 name = nameAtCursor;
183                 personId = personIdAtCursor;
184                 primaryId = ID_UNDEFINED;
185                 homeId = ID_UNDEFINED;
186                 mobileId = ID_UNDEFINED;
187                 workId = ID_UNDEFINED;
188                 otherId = ID_UNDEFINED;
189                 fallbackId = ID_UNDEFINED;
190             }
191 
192             // if labeled, then patch to HOME/MOBILE/WORK/OTHER
193             if (typeAtCursor == Phone.TYPE_CUSTOM &&
194                     labelAtCursor != null) {
195                 String label = labelAtCursor.toLowerCase();
196                 if (label.contains("home") || label.contains("house")) {
197                     typeAtCursor = Phone.TYPE_HOME;
198                 }
199                 else if (label.contains("mobile") || label.contains("cell")) {
200                     typeAtCursor = Phone.TYPE_MOBILE;
201                 }
202                 else if (label.contains("work") || label.contains("office")) {
203                     typeAtCursor = Phone.TYPE_WORK;
204                 }
205                 else if (label.contains("other")) {
206                     typeAtCursor = Phone.TYPE_OTHER;
207                 }
208             }
209 
210             boolean idAtCursorWasUsed = false;
211             // save the home, mobile, or work phone id
212             switch (typeAtCursor) {
213                 case Phone.TYPE_HOME:
214                     homeId = phoneIdAtCursor;
215                     if (isPrimaryAtCursor != 0) {
216                         primaryId = phoneIdAtCursor;
217                     }
218                     idAtCursorWasUsed = true;
219                     break;
220                 case Phone.TYPE_MOBILE:
221                     mobileId = phoneIdAtCursor;
222                     if (isPrimaryAtCursor != 0) {
223                         primaryId = phoneIdAtCursor;
224                     }
225                     idAtCursorWasUsed = true;
226                     break;
227                 case Phone.TYPE_WORK:
228                     workId = phoneIdAtCursor;
229                     if (isPrimaryAtCursor != 0) {
230                         primaryId = phoneIdAtCursor;
231                     }
232                     idAtCursorWasUsed = true;
233                     break;
234                 case Phone.TYPE_OTHER:
235                     otherId = phoneIdAtCursor;
236                     if (isPrimaryAtCursor != 0) {
237                         primaryId = phoneIdAtCursor;
238                     }
239                     idAtCursorWasUsed = true;
240                     break;
241             }
242 
243             if (fallbackId == ID_UNDEFINED && !idAtCursorWasUsed) {
244                 fallbackId = phoneIdAtCursor;
245             }
246         }
247 
248         // generate the last VoiceContact
249         if (name != null) {
250             contacts.add(new VoiceContact(name, personId, primaryId,
251                             homeId, mobileId, workId, otherId, fallbackId));
252         }
253 
254         // clean up cursor
255         cursor.close();
256 
257         if (false) Log.d(TAG, "VoiceContact.getVoiceContacts " + contacts.size());
258 
259         return contacts;
260     }
261 
262     /**
263      * @param contactsFile File containing a list of names,
264      * one per line.
265      * @return a List of {@link VoiceContact} in a File.
266      */
getVoiceContactsFromFile(File contactsFile)267     public static List<VoiceContact> getVoiceContactsFromFile(File contactsFile) {
268         if (false) Log.d(TAG, "getVoiceContactsFromFile " + contactsFile);
269 
270         List<VoiceContact> contacts = new ArrayList<VoiceContact>();
271 
272         // read from a file
273         BufferedReader br = null;
274         try {
275             br = new BufferedReader(new FileReader(contactsFile), 8192);
276             String name;
277             for (int id = 1; (name = br.readLine()) != null; id++) {
278                 contacts.add(new VoiceContact(name, id, ID_UNDEFINED,
279                         ID_UNDEFINED, ID_UNDEFINED, ID_UNDEFINED, ID_UNDEFINED, ID_UNDEFINED));
280             }
281         }
282         catch (IOException e) {
283             if (false) Log.d(TAG, "getVoiceContactsFromFile failed " + e);
284         }
285         finally {
286             try {
287                 br.close();
288             } catch (IOException e) {
289                 if (false) Log.d(TAG, "getVoiceContactsFromFile failed during close " + e);
290             }
291         }
292 
293         if (false) Log.d(TAG, "getVoiceContactsFromFile " + contacts.size());
294 
295         return contacts;
296     }
297 
298     /**
299      * @param activity The VoiceDialerActivity instance.
300      * @return String of last number dialed.
301      */
redialNumber(Activity activity)302     public static String redialNumber(Activity activity) {
303         Cursor cursor = activity.getContentResolver().query(
304                 CallLog.Calls.CONTENT_URI,
305                 new String[] { CallLog.Calls.NUMBER },
306                 CallLog.Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE,
307                 null,
308                 CallLog.Calls.DEFAULT_SORT_ORDER + " LIMIT 1");
309         String number = null;
310         if (cursor.getCount() >= 1) {
311             cursor.moveToNext();
312             int column = cursor.getColumnIndexOrThrow(CallLog.Calls.NUMBER);
313             number = cursor.getString(column);
314         }
315         cursor.close();
316 
317         if (false) Log.d(TAG, "redialNumber " + number);
318 
319         return number;
320     }
321 
322 }
323