diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4ceacc9a7a..c9ae93b500 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -7,26 +7,28 @@
+ android:label="Access to TextSecure Secrets"
+ android:protectionLevel="signature" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Don\'t copy
+ Currently: %s
Not found!
No valid identity key was found in the specified contact.
You don\'t have an identity key!
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index fd58dd9607..df6695a8cc 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -38,7 +38,7 @@
android:title="@string/preferences__pref_enter_sends_title" />
-
+
rawContactIds = ContactIdentityManager
+ .getInstance(ApplicationPreferencesActivity.this)
+ .getSelfIdentityRawContactIds();
+
+ if (rawContactIds== null) {
Toast.makeText(ApplicationPreferencesActivity.this,
R.string.ApplicationPreferenceActivity_you_have_not_yet_defined_a_contact_for_yourself,
Toast.LENGTH_LONG).show();
return true;
}
- ContactAccessor.getInstance().insertIdentityKey(ApplicationPreferencesActivity.this, Uri.parse(contactUri),
+ ContactAccessor.getInstance().insertIdentityKey(ApplicationPreferencesActivity.this, rawContactIds,
IdentityKeyUtil.getIdentityKey(ApplicationPreferencesActivity.this));
Toast.makeText(ApplicationPreferencesActivity.this,
@@ -205,7 +247,8 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
MasterSecret masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
if (masterSecret != null) {
- Intent importIntent = ContactAccessor.getInstance().getIntentForContactSelection();
+ Intent importIntent = new Intent(Intent.ACTION_PICK);
+ importIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);
startActivityForResult(importIntent, IMPORT_IDENTITY_ID);
} else {
Toast.makeText(ApplicationPreferencesActivity.this,
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
index 5a0bacae00..2c27a1652b 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
@@ -292,11 +292,7 @@ public class ContactSelectionListFragment extends SherlockListFragment
if (!isChecked)
throw new AssertionError("We shouldn't be unchecking data that doesn't exist.");
- existing = new ContactData();
- existing.id = contactData.id;
- existing.name = contactData.name;
- existing.numbers = new LinkedList();
-
+ existing = new ContactData(contactData.id, contactData.name);
selectedContacts.put(existing.id, existing);
}
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java
index 81b1fefa64..b6df83200e 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java
@@ -182,13 +182,7 @@ public class ContactSelectionRecentFragment extends SherlockListFragment
else if (type == Calls.OUTGOING_TYPE || type == RedPhoneCallTypes.OUTGOING) callTypeIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_call_log_list_outgoing_call));
else if (type == Calls.MISSED_TYPE || type == RedPhoneCallTypes.MISSED) callTypeIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_call_log_list_missed_call));
- this.contactData = new ContactData();
-
- if (name != null)
- this.contactData.name = name;
-
- this.contactData.id = id;
- this.contactData.numbers = new LinkedList();
+ this.contactData = new ContactData(id, name);
this.contactData.numbers.add(new NumberData(null, number));
if (selectedContacts.containsKey(id))
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index c85b9097af..c52b64663c 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -26,10 +26,8 @@ import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
-import android.preference.PreferenceManager;
import android.provider.Contacts.Intents;
import android.provider.ContactsContract.QuickContact;
-import android.telephony.TelephonyManager;
import android.text.Spannable;
import android.text.format.DateUtils;
import android.text.style.ForegroundColorSpan;
@@ -43,6 +41,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
+import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.MmsDatabase;
@@ -52,7 +51,6 @@ import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.protocol.Tag;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
-import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.service.SendReceiveService;
import java.io.File;
@@ -231,24 +229,16 @@ public class ConversationItem extends LinearLayout {
}
private void setContactPhotoForUserIdentity() {
- String configuredContact = PreferenceManager.getDefaultSharedPreferences(context).getString(ApplicationPreferencesActivity.IDENTITY_PREF, null);
+ Uri selfIdentityContact = ContactIdentityManager.getInstance(context).getSelfIdentityUri();
- try {
- if (configuredContact != null) {
- Recipient recipient = RecipientFactory.getRecipientForUri(context, Uri.parse(configuredContact));
- if (recipient != null) {
- contactPhoto.setImageBitmap(recipient.getContactPhoto());
- return;
- }
+ if (selfIdentityContact!= null) {
+ Recipient recipient = RecipientFactory.getRecipientForUri(context, selfIdentityContact);
+ if (recipient != null) {
+ contactPhoto.setImageBitmap(recipient.getContactPhoto());
+ return;
}
-
- if (hasLocalNumber()) {
- contactPhoto.setImageBitmap(RecipientFactory.getRecipientsFromString(context, getLocalNumber()).getPrimaryRecipient().getContactPhoto());
- } else {
- contactPhoto.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_contact_picture));
- }
- } catch (RecipientFormattingException rfe) {
- Log.w("ConversationItem", rfe);
+ } else {
+ contactPhoto.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_contact_picture));
}
}
@@ -280,15 +270,6 @@ public class ConversationItem extends LinearLayout {
setBodyImage(messageRecord);
}
- private String getLocalNumber() {
- return ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
- }
-
- private boolean hasLocalNumber() {
- String number = getLocalNumber();
- return (number != null) && (number.trim().length() > 0);
- }
-
private void setStatusIcons(MessageRecord messageRecord) {
failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE);
secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE);
diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java
index 87e80a9a70..507e1d8085 100644
--- a/src/org/thoughtcrime/securesms/ConversationListActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java
@@ -12,6 +12,7 @@ import android.database.ContentObserver;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
+import android.provider.ContactsContract;
import android.util.Log;
import com.actionbarsherlock.app.SherlockFragmentActivity;
@@ -20,7 +21,6 @@ import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import org.thoughtcrime.securesms.ApplicationExportManager.ApplicationExportListener;
-import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@@ -249,7 +249,7 @@ public class ConversationListActivity extends SherlockFragmentActivity
}
};
- getContentResolver().registerContentObserver(ContactAccessor.getInstance().getContactsUri(),
+ getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI,
true, observer);
}
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java
index 3a80393efa..b439efab53 100644
--- a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java
+++ b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java
@@ -17,56 +17,257 @@
package org.thoughtcrime.securesms.contacts;
import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
import android.database.Cursor;
+import android.database.MergeCursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.PhoneLookup;
import android.support.v4.content.CursorLoader;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKey;
+import org.thoughtcrime.securesms.crypto.InvalidKeyException;
+import org.thoughtcrime.securesms.util.Base64;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
- * Android changed their contacts API pretty heavily between
- * 1.x and 2.x. This class provides a common interface to both
- * API operations, using a singleton pattern that will Class.forName
- * the correct one so we don't trigger NoClassDefFound exceptions on
- * old platforms.
+ * This class was originally a layer of indirection between
+ * ContactAccessorNewApi and ContactAccesorOldApi, which corresponded
+ * to the API changes between 1.x and 2.x.
+ *
+ * Now that we no longer support 1.x, this class mostly serves as a place
+ * to encapsulate Contact-related logic. It's still a singleton, mostly
+ * just because that's how it's currently called from everywhere.
*
* @author Moxie Marlinspike
*/
-public abstract class ContactAccessor {
- public static final int UNIQUE_ID = 0;
- public static final int DISPLAY_NAME = 1;
+public class ContactAccessor {
- private static final ContactAccessor sInstance = new ContactAccessorNewApi();
+ private static final ContactAccessor instance = new ContactAccessor();
public static synchronized ContactAccessor getInstance() {
- return sInstance;
+ return instance;
}
- public abstract NameAndNumber getNameAndNumberFromContact(Context context, Uri uri);
- public abstract String getNameFromContact(Context context, Uri uri);
- public abstract IdentityKey importIdentityKey(Context context, Uri uri);
- public abstract void insertIdentityKey(Context context, Uri uri, IdentityKey identityKey);
- public abstract Intent getIntentForContactSelection();
- public abstract List getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver);
- public abstract List getGroupMembership(Context context, long groupId);
- public abstract Cursor getCursorForContactGroups(Context context);
- public abstract CursorLoader getCursorLoaderForContactGroups(Context context);
- public abstract CursorLoader getCursorLoaderForContactsWithNumbers(Context context);
- public abstract Cursor getCursorForContactsWithNumbers(Context context);
- public abstract GroupData getGroupData(Context context, Cursor cursor);
- public abstract ContactData getContactData(Context context, Cursor cursor);
- public abstract Cursor getCursorForRecipientFilter(CharSequence constraint, ContentResolver mContentResolver);
- public abstract CharSequence phoneTypeToString(Context mContext, int type, CharSequence label);
- public abstract String getNameForNumber(Context context, String number);
- public abstract Uri getContactsUri();
+ public CursorLoader getCursorLoaderForContactsWithNumbers(Context context) {
+ Uri uri = ContactsContract.Contacts.CONTENT_URI;
+ String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
+
+ return new CursorLoader(context, uri, null, selection, null,
+ ContactsContract.Contacts.DISPLAY_NAME + " ASC");
+ }
+
+ public CursorLoader getCursorLoaderForContactGroups(Context context) {
+ return new CursorLoader(context, ContactsContract.Groups.CONTENT_URI,
+ null, null, null, ContactsContract.Groups.TITLE + " ASC");
+ }
+
+ public Cursor getCursorForContactsWithNumbers(Context context) {
+ Uri uri = ContactsContract.Contacts.CONTENT_URI;
+ String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
+
+ return context.getContentResolver().query(uri, null, selection, null,
+ ContactsContract.Contacts.DISPLAY_NAME + " ASC");
+ }
+
+ public String getNameFromContact(Context context, Uri uri) {
+ Cursor cursor = null;
+
+ try {
+ cursor = context.getContentResolver().query(uri, new String[] {Contacts.DISPLAY_NAME},
+ null, null, null);
+
+ if (cursor != null && cursor.moveToFirst())
+ return cursor.getString(0);
+
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ public String getNameForNumber(Context context, String number) {
+ Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+
+ try {
+ if (cursor != null && cursor.moveToFirst())
+ return cursor.getString(cursor.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME));
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ public GroupData getGroupData(Context context, Cursor cursor) {
+ long id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.Groups._ID));
+ String title = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Groups.TITLE));
+
+ return new GroupData(id, title);
+ }
+
+ public ContactData getContactData(Context context, Cursor cursor) {
+ return getContactData(context,
+ cursor.getString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME)),
+ cursor.getLong(cursor.getColumnIndexOrThrow(Contacts._ID)));
+}
+
+ private ContactData getContactData(Context context, String displayName, long id) {
+ ContactData contactData = new ContactData(id, displayName);
+ Cursor numberCursor = null;
+
+ try {
+ numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null,
+ Phone.CONTACT_ID + " = ?",
+ new String[] {contactData.id + ""}, null);
+
+ while (numberCursor != null && numberCursor.moveToNext()) {
+ int type = numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE));
+ String label = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL));
+ String number = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.NUMBER));
+ String typeLabel = Phone.getTypeLabel(context.getResources(), type, label).toString();
+
+ contactData.numbers.add(new NumberData(typeLabel, number));
+ }
+ } finally {
+ if (numberCursor != null)
+ numberCursor.close();
+ }
+
+ return contactData;
+ }
+
+ public List getGroupMembership(Context context, long groupId) {
+ LinkedList contacts = new LinkedList();
+ Cursor groupMembership = null;
+
+ try {
+ String selection = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? AND " +
+ ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE + " = ?";
+ String[] args = new String[] {groupId+"",
+ ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE};
+
+ groupMembership = context.getContentResolver().query(Data.CONTENT_URI, null, selection, args, null);
+
+ while (groupMembership != null && groupMembership.moveToNext()) {
+ String displayName = groupMembership.getString(groupMembership.getColumnIndexOrThrow(Data.DISPLAY_NAME));
+ long contactId = groupMembership.getLong(groupMembership.getColumnIndexOrThrow(Data.CONTACT_ID));
+
+ contacts.add(getContactData(context, displayName, contactId));
+ }
+ } finally {
+ if (groupMembership != null)
+ groupMembership.close();
+ }
+
+ return contacts;
+ }
+
+ public List getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver) {
+ LinkedList numberList = new LinkedList();
+ Cursor cursor = null;
+
+ try {
+ cursor = contentResolver.query(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI,
+ Uri.encode(constraint)),
+ null, null, null, null);
+
+ while (cursor != null && cursor.moveToNext()) {
+ numberList.add(cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER)));
+ }
+
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ return numberList;
+ }
+
+ public CharSequence phoneTypeToString(Context mContext, int type, CharSequence label) {
+ return Phone.getTypeLabel(mContext.getResources(), type, label);
+ }
+
+ public void insertIdentityKey(Context context, List rawContactIds, IdentityKey identityKey) {
+ for (long rawContactId : rawContactIds) {
+ Log.w("ContactAccessorNewApi", "Inserting data for raw contact id: " + rawContactId);
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Data.RAW_CONTACT_ID, rawContactId);
+ contentValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
+ contentValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
+ contentValues.put(Im.CUSTOM_PROTOCOL, "TextSecure-IdentityKey");
+ contentValues.put(Im.DATA, Base64.encodeBytes(identityKey.serialize()));
+
+ context.getContentResolver().insert(Data.CONTENT_URI, contentValues);
+ }
+ }
+
+ public IdentityKey importIdentityKey(Context context, Uri uri) {
+ long contactId = getContactIdFromLookupUri(context, uri);
+ String selection = Im.CONTACT_ID + " = ? AND " + Im.PROTOCOL + " = ? AND " + Im.CUSTOM_PROTOCOL + " = ?";
+ String[] selectionArgs = new String[] {contactId+"", Im.PROTOCOL_CUSTOM+"", "TextSecure-IdentityKey"};
+
+ Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, null, selection, selectionArgs, null);
+
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ String data = cursor.getString(cursor.getColumnIndexOrThrow(Im.DATA));
+
+ if (data != null)
+ return new IdentityKey(Base64.decode(data), 0);
+
+ }
+ } catch (InvalidKeyException e) {
+ Log.w("ContactAccessorNewApi", e);
+ return null;
+ } catch (IOException e) {
+ Log.w("ContactAccessorNewApi", e);
+ return null;
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ private long getContactIdFromLookupUri(Context context, Uri uri) {
+ Cursor cursor = null;
+
+ try {
+ cursor = context.getContentResolver().query(uri,
+ new String[] {ContactsContract.Contacts._ID},
+ null, null, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ } else {
+ return -1;
+ }
+
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
public static class NumberData implements Parcelable {
@@ -80,8 +281,8 @@ public abstract class ContactAccessor {
}
};
- public String number;
- public String type;
+ public final String number;
+ public final String type;
public NumberData(String type, String number) {
this.type = type;
@@ -104,8 +305,13 @@ public abstract class ContactAccessor {
}
public static class GroupData {
- public long id;
- public String name;
+ public final long id;
+ public final String name;
+
+ public GroupData(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
}
public static class ContactData implements Parcelable {
@@ -120,11 +326,15 @@ public abstract class ContactAccessor {
}
};
- public long id;
- public String name;
- public List numbers;
+ public final long id;
+ public final String name;
+ public final List numbers;
- public ContactData() {}
+ public ContactData(long id, String name) {
+ this.id = id;
+ this.name = name;
+ this.numbers = new LinkedList();
+ }
public ContactData(Parcel in) {
id = in.readLong();
@@ -144,4 +354,81 @@ public abstract class ContactAccessor {
}
}
+ /***
+ * If the code below looks shitty to you, that's because it was taken
+ * directly from the Android source, where shitty code is all you get.
+ */
+
+ public Cursor getCursorForRecipientFilter(CharSequence constraint,
+ ContentResolver mContentResolver)
+ {
+ final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," +
+ Contacts.DISPLAY_NAME + "," + Phone.TYPE;
+
+ final String[] PROJECTION_PHONE = {
+ Phone._ID, // 0
+ Phone.CONTACT_ID, // 1
+ Phone.TYPE, // 2
+ Phone.NUMBER, // 3
+ Phone.LABEL, // 4
+ Phone.DISPLAY_NAME, // 5
+ };
+
+ String phone = "";
+ String cons = null;
+
+ if (constraint != null) {
+ cons = constraint.toString();
+
+ if (RecipientsAdapter.usefulAsDigits(cons)) {
+ phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons);
+ if (phone.equals(cons)) {
+ phone = "";
+ } else {
+ phone = phone.trim();
+ }
+ }
+ }
+
+ Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(cons));
+ String selection = String.format("%s=%s OR %s=%s OR %s=%s",
+ Phone.TYPE,
+ Phone.TYPE_MOBILE,
+ Phone.TYPE,
+ Phone.TYPE_WORK_MOBILE,
+ Phone.TYPE,
+ Phone.TYPE_MMS);
+
+ Cursor phoneCursor = mContentResolver.query(uri,
+ PROJECTION_PHONE,
+ null,
+ null,
+ SORT_ORDER);
+
+ if (phone.length() > 0) {
+ ArrayList result = new ArrayList();
+ result.add(Integer.valueOf(-1)); // ID
+ result.add(Long.valueOf(-1)); // CONTACT_ID
+ result.add(Integer.valueOf(Phone.TYPE_CUSTOM)); // TYPE
+ result.add(phone); // NUMBER
+
+ /*
+ * The "\u00A0" keeps Phone.getDisplayLabel() from deciding
+ * to display the default label ("Home") next to the transformation
+ * of the letters into numbers.
+ */
+ result.add("\u00A0"); // LABEL
+ result.add(cons); // NAME
+
+ ArrayList wrap = new ArrayList();
+ wrap.add(result);
+
+ ArrayListCursor translated = new ArrayListCursor(PROJECTION_PHONE, wrap);
+
+ return new MergeCursor(new Cursor[] { translated, phoneCursor });
+ } else {
+ return phoneCursor;
+ }
+ }
+
}
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessorNewApi.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessorNewApi.java
deleted file mode 100644
index 7aa331c07f..0000000000
--- a/src/org/thoughtcrime/securesms/contacts/ContactAccessorNewApi.java
+++ /dev/null
@@ -1,424 +0,0 @@
-/**
- * Copyright (C) 2011 Whisper Systems
- *
- * This program 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.
- *
- * This program 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 this program. If not, see .
- */
-package org.thoughtcrime.securesms.contacts;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.database.MergeCursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.PhoneLookup;
-import android.provider.ContactsContract.RawContacts;
-import android.support.v4.content.CursorLoader;
-import android.telephony.PhoneNumberUtils;
-import android.util.Log;
-
-import org.thoughtcrime.securesms.crypto.IdentityKey;
-import org.thoughtcrime.securesms.crypto.InvalidKeyException;
-import org.thoughtcrime.securesms.util.Base64;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Interface into the Android 2.x+ contacts operations.
- *
- * @author Stuart Anderson
- */
-
-public class ContactAccessorNewApi extends ContactAccessor {
-
- private static final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," + Contacts.DISPLAY_NAME + "," + Phone.TYPE;
-
- private static final String[] PROJECTION_PHONE = {
- Phone._ID, // 0
- Phone.CONTACT_ID, // 1
- Phone.TYPE, // 2
- Phone.NUMBER, // 3
- Phone.LABEL, // 4
- Phone.DISPLAY_NAME, // 5
- };
-
- @Override
- public List getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver) {
- LinkedList numberList = new LinkedList();
- Cursor cursor = null;
-
- try {
- cursor = contentResolver.query(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(constraint)),
- null, null, null, null);
-
- while (cursor != null && cursor.moveToNext())
- numberList.add(cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER)));
-
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- return numberList;
- }
-
- @Override
- public Cursor getCursorForRecipientFilter(CharSequence constraint, ContentResolver mContentResolver) {
- String phone = "";
- String cons = null;
- if (constraint != null) {
- cons = constraint.toString();
-
- if (RecipientsAdapter.usefulAsDigits(cons)) {
- phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons);
- if (phone.equals(cons)) {
- phone = "";
- } else {
- phone = phone.trim();
- }
- }
- }
-
- Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(cons));
- String selection = String.format("%s=%s OR %s=%s OR %s=%s",
- Phone.TYPE,
- Phone.TYPE_MOBILE,
- Phone.TYPE,
- Phone.TYPE_WORK_MOBILE,
- Phone.TYPE,
- Phone.TYPE_MMS);
-
- Cursor phoneCursor = mContentResolver.query(uri,
- PROJECTION_PHONE,
- null,
- null,
- SORT_ORDER);
-
-
-
- if (phone.length() > 0) {
- ArrayList result = new ArrayList();
- result.add(Integer.valueOf(-1)); // ID
- result.add(Long.valueOf(-1)); // CONTACT_ID
- result.add(Integer.valueOf(Phone.TYPE_CUSTOM)); // TYPE
- result.add(phone); // NUMBER
-
- /*
- * The "\u00A0" keeps Phone.getDisplayLabel() from deciding
- * to display the default label ("Home") next to the transformation
- * of the letters into numbers.
- */
- result.add("\u00A0"); // LABEL
- result.add(cons); // NAME
-
- ArrayList wrap = new ArrayList();
- wrap.add(result);
-
- ArrayListCursor translated = new ArrayListCursor(PROJECTION_PHONE, wrap);
-
- return new MergeCursor(new Cursor[] { translated, phoneCursor });
- } else {
- return phoneCursor;
- }
-
- }
-
- @Override
- public CharSequence phoneTypeToString( Context mContext, int type, CharSequence label ) {
- return Phone.getTypeLabel(mContext.getResources(), type, label);
- }
-
- @Override
- public Intent getIntentForContactSelection() {
- Intent intent = new Intent(Intent.ACTION_PICK);
- intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
- return intent;
- }
-
- private long getContactIdFromLookupUri(Context context, Uri uri) {
- Cursor cursor = null;
-
- try {
- cursor = context.getContentResolver().query(uri, new String[] {ContactsContract.Contacts._ID}, null, null, null);
-
- if (cursor != null && cursor.moveToFirst())
- return cursor.getLong(0);
- else
- return -1;
-
- } finally {
- if (cursor != null)
- cursor.close();
- }
- }
-
- private ArrayList getRawContactIds(Context context, long contactId) {
- Cursor cursor = null;
- ArrayList rawContactIds = new ArrayList();
-
- try {
- cursor = context.getContentResolver().query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID},
- RawContacts.CONTACT_ID + " = ?", new String[] {contactId+""},
- null);
-
- if (cursor == null)
- return rawContactIds;
-
- while (cursor.moveToNext()) {
- rawContactIds.add(Long.valueOf(cursor.getLong(0)));
- }
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- return rawContactIds;
- }
-
- @Override
- public void insertIdentityKey(Context context, Uri uri, IdentityKey identityKey) {
- long contactId = getContactIdFromLookupUri(context, uri);
- Log.w("ContactAccessorNewApi", "Got contact ID: " + contactId + " from uri: " + uri.toString());
- ArrayList rawContactIds = getRawContactIds(context, contactId);
-
- for (long rawContactId : rawContactIds) {
- Log.w("ContactAccessorNewApi", "Inserting data for raw contact id: " + rawContactId);
- ContentValues contentValues = new ContentValues();
- contentValues.put(Data.RAW_CONTACT_ID, rawContactId);
- contentValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
- contentValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
- contentValues.put(Im.CUSTOM_PROTOCOL, "TextSecure-IdentityKey");
- contentValues.put(Im.DATA, Base64.encodeBytes(identityKey.serialize()));
-
- context.getContentResolver().insert(Data.CONTENT_URI, contentValues);
- }
- }
-
- @Override
- public IdentityKey importIdentityKey(Context context, Uri uri) {
- long contactId = getContactIdFromLookupUri(context, uri);
- String selection = Im.CONTACT_ID + " = ? AND " + Im.PROTOCOL + " = ? AND " + Im.CUSTOM_PROTOCOL + " = ?";
- String[] selectionArgs = new String[] {contactId+"", Im.PROTOCOL_CUSTOM+"", "TextSecure-IdentityKey"};
-
- Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, null, selection, selectionArgs, null);
-
- try {
- if (cursor != null && cursor.moveToFirst()) {
- String data = cursor.getString(cursor.getColumnIndexOrThrow(Im.DATA));
-
- if (data != null)
- return new IdentityKey(Base64.decode(data), 0);
-
- }
- } catch (InvalidKeyException e) {
- Log.w("ContactAccessorNewApi", e);
- return null;
- } catch (IOException e) {
- Log.w("ContactAccessorNewApi", e);
- return null;
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- return null;
- }
-
- @Override
- public String getNameFromContact(Context context, Uri uri) {
- Cursor cursor = null;
-
- try {
- cursor = context.getContentResolver().query(uri, new String[] {Contacts.DISPLAY_NAME}, null, null, null);
-
- if (cursor != null && cursor.moveToFirst())
- return cursor.getString(0);
-
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- return null;
- }
-
- private String getMobileNumberForId(Context context, long id) {
- Cursor cursor = null;
-
- try {
- cursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ? AND " + Phone.TYPE + " = ?",
- new String[] {id+"", Phone.TYPE_MOBILE+""}, null);
-
- if (cursor != null && cursor.moveToFirst())
- return cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER));
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- return null;
- }
-
- @Override
- public NameAndNumber getNameAndNumberFromContact(Context context, Uri uri) {
- Log.w("ContactAccessorNewApi", "Get name and number from: " + uri.toString());
- Cursor cursor = null;
-
- try {
- NameAndNumber results = new NameAndNumber();
- cursor = context.getContentResolver().query(uri, new String[] {Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null);
-
- if (cursor != null && cursor.moveToFirst()) {
- results.name = cursor.getString(1);
- results.number = getMobileNumberForId(context, cursor.getLong(0));
- return results;
- }
-
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- return null;
- }
-
- @Override
- public CursorLoader getCursorLoaderForContactsWithNumbers(Context context) {
- Uri uri = ContactsContract.Contacts.CONTENT_URI;
- String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
-
- return new CursorLoader(context, uri, null, selection, null,
- ContactsContract.Contacts.DISPLAY_NAME + " ASC");
- }
-
- @Override
- public Cursor getCursorForContactsWithNumbers(Context context) {
- Uri uri = ContactsContract.Contacts.CONTENT_URI;
- String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
-
- return context.getContentResolver().query(uri, null, selection, null,
- ContactsContract.Contacts.DISPLAY_NAME + " ASC");
- }
-
- private ContactData getContactData(Context context, String displayName, long id) {
- ContactData contactData = new ContactData();
- contactData.id = id;
- contactData.name = displayName;
- contactData.numbers = new LinkedList();
-
- Cursor numberCursor = null;
-
- try {
- numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ?",
- new String[] {contactData.id + ""}, null);
-
- while (numberCursor != null && numberCursor.moveToNext())
- contactData.numbers.add(new NumberData(Phone.getTypeLabel(context.getResources(),
- numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE)),
- numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL))).toString(),
- numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.NUMBER))));
- } finally {
- if (numberCursor != null)
- numberCursor.close();
- }
-
- return contactData;
-
- }
-
- @Override
- public ContactData getContactData(Context context, Cursor cursor) {
- return getContactData(context,
- cursor.getString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME)),
- cursor.getLong(cursor.getColumnIndexOrThrow(Contacts._ID)));
- }
-
- @Override
- public Cursor getCursorForContactGroups(Context context) {
- return context.getContentResolver().query(ContactsContract.Groups.CONTENT_URI, null, null, null, ContactsContract.Groups.TITLE + " ASC");
- }
-
- @Override
- public CursorLoader getCursorLoaderForContactGroups(Context context) {
- return new CursorLoader(context, ContactsContract.Groups.CONTENT_URI,
- null, null, null, ContactsContract.Groups.TITLE + " ASC");
- }
-
- @Override
- public List getGroupMembership(Context context, long groupId) {
- LinkedList contacts = new LinkedList();
- Cursor groupMembership = null;
-
- try {
- String selection = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? AND " +
- ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE + " = ?";
- String[] args = new String[] {groupId+"",
- ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE};
-
- groupMembership = context.getContentResolver().query(Data.CONTENT_URI, null, selection, args, null);
-
- while (groupMembership != null && groupMembership.moveToNext()) {
- String displayName = groupMembership.getString(groupMembership.getColumnIndexOrThrow(Data.DISPLAY_NAME));
- long contactId = groupMembership.getLong(groupMembership.getColumnIndexOrThrow(Data.CONTACT_ID));
-
- contacts.add(getContactData(context, displayName, contactId));
- }
- } finally {
- if (groupMembership != null)
- groupMembership.close();
- }
-
- return contacts;
- }
-
- @Override
- public GroupData getGroupData(Context context, Cursor cursor) {
- GroupData groupData = new GroupData();
- groupData.id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.Groups._ID));
- groupData.name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Groups.TITLE));
-
- return groupData;
- }
-
- @Override
- public String getNameForNumber(Context context, String number) {
- Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
- Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
-
- try {
- if (cursor != null && cursor.moveToFirst())
- return cursor.getString(cursor.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME));
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- return null;
- }
-
- @Override
- public Uri getContactsUri() {
- return ContactsContract.Contacts.CONTENT_URI;
- }
-
-}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactIdentityManager.java b/src/org/thoughtcrime/securesms/contacts/ContactIdentityManager.java
new file mode 100644
index 0000000000..d829d3be6b
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/contacts/ContactIdentityManager.java
@@ -0,0 +1,28 @@
+package org.thoughtcrime.securesms.contacts;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+
+import java.util.List;
+
+public abstract class ContactIdentityManager {
+
+ public static ContactIdentityManager getInstance(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ return new ContactIdentityManagerICS(context);
+ else
+ return new ContactIdentityManagerGingerbread(context);
+ }
+
+ protected final Context context;
+
+ public ContactIdentityManager(Context context) {
+ this.context = context.getApplicationContext();
+ }
+
+ public abstract Uri getSelfIdentityUri();
+ public abstract boolean isSelfIdentityAutoDetected();
+ public abstract List getSelfIdentityRawContactIds();
+
+}
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactIdentityManagerGingerbread.java b/src/org/thoughtcrime/securesms/contacts/ContactIdentityManagerGingerbread.java
new file mode 100644
index 0000000000..5f4793ecb0
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/contacts/ContactIdentityManagerGingerbread.java
@@ -0,0 +1,137 @@
+package org.thoughtcrime.securesms.contacts;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.preference.PreferenceManager;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.RawContacts;
+import android.telephony.TelephonyManager;
+
+import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class ContactIdentityManagerGingerbread extends ContactIdentityManager {
+
+ public ContactIdentityManagerGingerbread(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Uri getSelfIdentityUri() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ String contactUriString = preferences.getString(ApplicationPreferencesActivity.IDENTITY_PREF, null);
+
+ if (hasLocalNumber()) {
+ return getContactUriForNumber(getLocalNumber());
+ } else if (contactUriString != null) {
+ return Uri.parse(contactUriString);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isSelfIdentityAutoDetected() {
+ return hasLocalNumber() && getContactUriForNumber(getLocalNumber()) != null;
+ }
+
+ @Override
+ public List getSelfIdentityRawContactIds() {
+ long selfIdentityContactId = getSelfIdentityContactId();
+
+ if (selfIdentityContactId == -1)
+ return null;
+
+ Cursor cursor = null;
+ ArrayList rawContactIds = new ArrayList();
+
+ try {
+ cursor = context.getContentResolver().query(RawContacts.CONTENT_URI,
+ new String[] {RawContacts._ID},
+ RawContacts.CONTACT_ID + " = ?",
+ new String[] {selfIdentityContactId+""},
+ null);
+
+ if (cursor == null || cursor.getCount() == 0)
+ return null;
+
+ while (cursor.moveToNext()) {
+ rawContactIds.add(Long.valueOf(cursor.getLong(0)));
+ }
+
+ return rawContactIds;
+
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
+
+ private Uri getContactUriForNumber(String number) {
+ String[] PROJECTION = new String[] {
+ PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.LOOKUP_KEY,
+ PhoneLookup._ID,
+ };
+
+ Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+ Cursor cursor = null;
+
+ try {
+ cursor = context.getContentResolver().query(uri, PROJECTION, null, null, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ return Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ private long getSelfIdentityContactId() {
+ Uri contactUri = getSelfIdentityUri();
+
+ if (contactUri == null)
+ return -1;
+
+ Cursor cursor = null;
+
+ try {
+ cursor = context.getContentResolver().query(contactUri,
+ new String[] {ContactsContract.Contacts._ID},
+ null, null, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ } else {
+ return -1;
+ }
+
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
+
+ private String getLocalNumber() {
+ return ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE))
+ .getLine1Number();
+ }
+
+ private boolean hasLocalNumber() {
+ String number = getLocalNumber();
+ return (number != null) && (number.trim().length() > 0);
+ }
+
+
+
+}
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactIdentityManagerICS.java b/src/org/thoughtcrime/securesms/contacts/ContactIdentityManagerICS.java
new file mode 100644
index 0000000000..c8652ef6b5
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/contacts/ContactIdentityManagerICS.java
@@ -0,0 +1,78 @@
+package org.thoughtcrime.securesms.contacts;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+
+import java.util.LinkedList;
+import java.util.List;
+
+class ContactIdentityManagerICS extends ContactIdentityManager {
+
+ public ContactIdentityManagerICS(Context context) {
+ super(context);
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public Uri getSelfIdentityUri() {
+ String[] PROJECTION = new String[] {
+ PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.LOOKUP_KEY,
+ PhoneLookup._ID,
+ };
+
+ Cursor cursor = null;
+
+ try {
+ cursor = context.getContentResolver().query(ContactsContract.Profile.CONTENT_URI,
+ PROJECTION, null, null, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ return Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isSelfIdentityAutoDetected() {
+ return true;
+ }
+
+ @Override
+ public List getSelfIdentityRawContactIds() {
+ List results = new LinkedList();
+
+ String[] PROJECTION = new String[] {
+ ContactsContract.Profile._ID
+ };
+
+ Cursor cursor = null;
+
+ try {
+ cursor = context.getContentResolver().query(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI,
+ PROJECTION, null, null, null);
+
+ if (cursor == null || cursor.getCount() == 0)
+ return null;
+
+ while (cursor.moveToNext()) {
+ results.add(cursor.getLong(0));
+ }
+
+ return results;
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
+}