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
17package com.android.voicedialer;
18
19import android.app.Activity;
20import android.database.Cursor;
21import android.database.DatabaseUtils;
22import android.provider.ContactsContract.CommonDataKinds.Phone;
23import android.provider.CallLog;
24import android.util.Log;
25import java.io.BufferedReader;
26import java.io.File;
27import java.io.FileReader;
28import java.io.IOException;
29import java.util.ArrayList;
30import 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 */
37public 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     */
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
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
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     */
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     */
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     */
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