/*
This file is part of Project MAXS.
MAXS and its modules is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
MAXS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with MAXS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.projectmaxs.shared.module;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.projectmaxs.shared.global.GlobalConstants;
import org.projectmaxs.shared.global.messagecontent.Contact;
import org.projectmaxs.shared.global.messagecontent.ContactNumber;
import org.projectmaxs.shared.global.util.PackageManagerUtil;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup;
public class ContactUtil {
public static final String CONTACTS_MODULE_PACKAGE = GlobalConstants.MODULE_PACKAGE
+ ".contactsread";
public static final Uri CONTACTS_MODULE_AUTHORITY = Uri.parse("content://"
+ CONTACTS_MODULE_PACKAGE);
/**
* ContactsContract.PhoneLookup.CONTENT_FILTER_URI
*/
public static final Uri MAXS_PHONE_LOOKUP_CONTENT_FILTER_URI = maxsContactUriFrom(ContactsContract.PhoneLookup.CONTENT_FILTER_URI);
/**
* ContactsContract.Data.CONTENT_URI
*/
public static final Uri MAXS_DATA_CONTENT_URI = maxsContactUriFrom(ContactsContract.Data.CONTENT_URI);
/**
* ContactsContract.Contacts.CONTENT_URI
*/
public static final Uri MAXS_CONTACTS_CONTENT_URI = maxsContactUriFrom(ContactsContract.Contacts.CONTENT_URI);
/**
* ContactsContract.Contacts.CONTENT_LOOKUP_URI
*/
public static final Uri MAXS_CONTACTS_CONTENT_LOOKUP_URI = maxsContactUriFrom(ContactsContract.Contacts.CONTENT_LOOKUP_URI);
/**
* ContactsContract.Contacts.CONTENT_FILTER_URI
*/
public static final Uri MAXS_CONTACTS_CONTENT_FILTER_URI = maxsContactUriFrom(ContactsContract.Contacts.CONTENT_FILTER_URI);
private static final String AND = " AND ";
private static final String LIMIT = " LIMIT";
private static final String LIMIT_1 = LIMIT + " 1";
public static Uri maxsContactUriFrom(Uri uri) {
String pathSegment = uri.getEncodedPath();
return Uri.withAppendedPath(CONTACTS_MODULE_AUTHORITY, pathSegment);
}
@SuppressLint("InlinedApi")
private static final String DISPLAY_NAME = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? Contacts.DISPLAY_NAME_PRIMARY
: Contacts.DISPLAY_NAME;
private static ContactUtil sContactUtil;
public static synchronized ContactUtil getInstance(Context context) {
if (sContactUtil == null) sContactUtil = new ContactUtil(context);
return sContactUtil;
}
private ContactUtil(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
}
private final Context mContext;
private final ContentResolver mContentResolver;
public boolean contactsReadModuleInstalled() {
return PackageManagerUtil.getInstance(mContext).isPackageInstalled(CONTACTS_MODULE_PACKAGE);
}
/**
* Magical method that tries to find contacts based on a given String.
*
* Only returns null if the contacts module is not installed or on error.
*
* @param info
* @return A collection of matching contacts.
*/
public Collection<Contact> lookupContacts(String info) {
if (!contactsReadModuleInstalled()) return null;
String cleanNumber = ContactNumber.cleanNumber(info);
if (ContactNumber.isNumber(cleanNumber)) return contactsByNumber(cleanNumber);
Collection<Contact> contacts = contactsByNickname(info);
if (contacts != null && contacts.size() > 0) return contacts;
return contactsByName(info);
}
/**
* Lookup exactly one contact for a given number.
*
* @param number
* @return the contact, or null if none was found or the contactsread module is not installed.
*/
public Contact contactByNumber(String number) {
if (!contactsReadModuleInstalled()) return null;
number = ContactNumber.cleanNumber(number);
if (!ContactNumber.isNumber(number)) return null;
Uri uri = Uri.withAppendedPath(MAXS_PHONE_LOOKUP_CONTENT_FILTER_URI, Uri.encode(number));
final String[] projection = new String[] { PhoneLookup.LOOKUP_KEY, DISPLAY_NAME };
Cursor c = mContentResolver.query(uri, projection, null, null, null);
Contact contact = null;
if (c.moveToFirst()) {
String displayName = c.getString(c.getColumnIndexOrThrow(DISPLAY_NAME));
String lookupKey = c.getString(c.getColumnIndexOrThrow(PhoneLookup.LOOKUP_KEY));
contact = new Contact(displayName, lookupKey);
lookupContactNumbersFor(contact);
}
c.close();
return contact;
}
/**
* Get all contacts for a given number
*
* @param number
* @return All contacts with that number.
*/
public Collection<Contact> contactsByNumber(String number) {
if (!contactsReadModuleInstalled()) return null;
number = ContactNumber.cleanNumber(number);
if (!ContactNumber.isNumber(number)) return null;
Uri uri = Uri.withAppendedPath(MAXS_PHONE_LOOKUP_CONTENT_FILTER_URI, Uri.encode(number));
final String[] projection = new String[] { PhoneLookup.LOOKUP_KEY, DISPLAY_NAME };
Cursor c = mContentResolver.query(uri, projection, null, null, null);
Map<String, Contact> contactMap = new HashMap<String, Contact>();
for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
String displayName = c.getString(c.getColumnIndexOrThrow(DISPLAY_NAME));
String lookupKey = c.getString(c.getColumnIndexOrThrow(PhoneLookup.LOOKUP_KEY));
Contact contact = contactMap.get(lookupKey);
if (contact == null) {
contact = new Contact(displayName, lookupKey);
contactMap.put(lookupKey, contact);
lookupContactNumbersFor(contact);
}
}
c.close();
return contactMap.values();
}
/**
* Lookup a contact by a given nickname.
*
* The returned contact will come with all known contact numbers and a
* lookup key.
*
* @param nickname
* @return A contact, or null if none found or on error.
*/
public Contact contactByNickname(String nickname) {
if (!contactsReadModuleInstalled()) return null;
final String[] projection = new String[] { Data.LOOKUP_KEY, DISPLAY_NAME, Nickname.NAME };
final String selection = Nickname.NAME + "=?" + AND + Data.MIMETYPE + "='"
+ Nickname.CONTENT_ITEM_TYPE + "'" + LIMIT_1;
final String[] selectionArgs = new String[] { nickname };
Cursor c = mContentResolver.query(MAXS_DATA_CONTENT_URI, projection, selection,
selectionArgs, null);
Contact contact = null;
if (c.moveToFirst()) {
String lookupKey = c.getString(c.getColumnIndexOrThrow(Data.LOOKUP_KEY));
String displayName = c.getString(c.getColumnIndexOrThrow(DISPLAY_NAME));
nickname = c.getString(c.getColumnIndexOrThrow(Nickname.NAME));
contact = new Contact(displayName, lookupKey);
contact.setNickname(nickname);
lookupContactNumbersFor(contact);
}
c.close();
return contact;
}
/**
* Get all matching contacts for a given nickname.
*
* The contacts will come with all known contact numbers and a lookup key.
*
* @param nickname
* @return A contact, or null if none found or on error.
*/
public Collection<Contact> contactsByNickname(String nickname) {
if (!contactsReadModuleInstalled()) return null;
final String[] projection = new String[] { Data.LOOKUP_KEY, DISPLAY_NAME, Nickname.NAME };
final String selection = Nickname.NAME + "=?" + AND + Data.MIMETYPE + "='"
+ Nickname.CONTENT_ITEM_TYPE + "'";
final String[] selectionArgs = new String[] { nickname };
Cursor c = mContentResolver.query(MAXS_DATA_CONTENT_URI, projection, selection,
selectionArgs, null);
Collection<Contact> contacts = new ArrayList<Contact>();
for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
String lookupKey = c.getString(c.getColumnIndexOrThrow(Data.LOOKUP_KEY));
String displayName = c.getString(c.getColumnIndexOrThrow(DISPLAY_NAME));
nickname = c.getString(c.getColumnIndexOrThrow(Nickname.NAME));
Contact contact = new Contact(displayName, lookupKey);
contact.setNickname(nickname);
lookupContactNumbersFor(contact);
contacts.add(contact);
}
c.close();
return contacts;
}
/**
* Get a contact for a given name
*
* The contact will come with all known contact numbers and a lookup key.
*
* @param name
* @return A contact, or null if none found, contactsread is not installed or on error.
*/
public Contact contactByName(String name) {
if (!contactsReadModuleInstalled()) return null;
Uri uri = Uri.withAppendedPath(MAXS_CONTACTS_CONTENT_FILTER_URI, Uri.encode(name));
final String[] projection = new String[] { Contacts.LOOKUP_KEY, DISPLAY_NAME };
Cursor c = mContentResolver.query(uri, projection, LIMIT_1, null, null);
Contact contact = null;
if (c.moveToFirst()) {
String displayName = c.getString(c.getColumnIndexOrThrow(DISPLAY_NAME));
String lookupKey = c.getString(c.getColumnIndexOrThrow(Data.LOOKUP_KEY));
contact = new Contact(displayName, lookupKey);
lookupContactNumbersFor(contact);
}
c.close();
return contact;
}
/**
* Get all known contacts for a given name
*
* The contacts will come with all known contact numbers and a lookup key.
*
* @param name
* @return A collection of matching Contacts or null if contactsread is not installed.
*/
public Collection<Contact> contactsByName(String name) {
if (!contactsReadModuleInstalled()) return null;
Uri uri = Uri.withAppendedPath(MAXS_CONTACTS_CONTENT_FILTER_URI, Uri.encode(name));
final String[] projection = new String[] { Contacts.LOOKUP_KEY, DISPLAY_NAME };
Cursor c = mContentResolver.query(uri, projection, null, null, null);
Collection<Contact> res = new ArrayList<Contact>();
for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
String displayName = c.getString(c.getColumnIndexOrThrow(DISPLAY_NAME));
String lookupKey = c.getString(c.getColumnIndexOrThrow(Data.LOOKUP_KEY));
Contact contact = new Contact(displayName, lookupKey);
lookupContactNumbersFor(contact);
res.add(contact);
}
c.close();
return res;
}
/**
* Lookup the numbers for a given contact.
*
* Usually this is not needed because most methods already return the
* contacts with all known contact numbers. Make sure to call
* {@link #contactsReadModuleInstalled()} first.
*
* @param contact
*/
public void lookupContactNumbersFor(Contact contact) {
if (!contactsReadModuleInstalled()) return;
String lookupKey = contact.getLookupKey();
// @formatter:off
final String[] projection = new String[] {
Phone.NUMBER,
Phone.TYPE,
Phone.LABEL,
Phone.IS_SUPER_PRIMARY
};
// @formatter:on
final String selection = ContactsContract.PhoneLookup.LOOKUP_KEY + "=?" + AND
+ ContactsContract.Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'";
final String[] selectionArgs = new String[] { lookupKey };
Cursor c = mContentResolver.query(MAXS_DATA_CONTENT_URI, projection, selection,
selectionArgs, null);
for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
String number = c.getString(c
.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER));
int type = c.getInt(c
.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE));
String label = c.getString(c
.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL));
boolean superPrimary = c
.getInt(c
.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.IS_SUPER_PRIMARY)) > 0 ? true
: false;
contact.addNumber(number, type, label, superPrimary);
}
c.close();
}
/**
* Pretty print for a given contact and contactInfo. If contact is null, only contactInfo will
* be returned. Otherwise {@code"<contact.getDisplayName()> (<contactInfo>)"} will get
* returned.
*
* @param contactInfo
* @param contact
* @return The contact as String
*/
public static String prettyPrint(String contactInfo, Contact contact) {
if (contact == null) return contactInfo;
String displayName = contact.getDisplayName();
// Contacts can be saved without a name, i.e. just a number as "contact".
return (displayName == null ? "unknown" : displayName) + " (" + contactInfo + ")";
}
/**
* If the given collection contains only one contact with a number, then this contact is
* returned. Otherwise, if more then one contact with a number exists or if none exists, null is
* returned.
*
* @param contacts
* @return the one and only contact with number(s) from contacts or null
*/
public static Contact getOnlyContactWithNumber(Collection<Contact> contacts) {
Contact res = null;
for (Contact contact : contacts) {
if (contact.hasNumbers()) {
if (res != null) {
return null;
} else {
res = contact;
}
}
}
return res;
}
}