/*
*
* Panbox - encryption for cloud storage
* Copyright (C) 2014-2015 by Fraunhofer SIT and Sirrix AG
*
* 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 <http://www.gnu.org/licenses/>.
*
* Additonally, third party code may be provided with notices and open source
* licenses from communities and third parties that govern the use of those
* portions, and any licenses granted hereunder do not alter any rights and
* obligations you may have under such open source licenses, however, the
* disclaimer of warranty and limitation of liability provisions of the GPLv3
* will apply to all the product.
*
*/
package org.panbox.mobile.android.identitymgmt;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map.Entry;
import org.panbox.core.crypto.CryptCore;
import org.panbox.core.identitymgmt.AbstractAddressbookManager;
import org.panbox.core.identitymgmt.AbstractIdentity;
import org.panbox.core.identitymgmt.CloudProviderInfo;
import org.panbox.core.identitymgmt.PanboxContact;
import org.panbox.core.identitymgmt.exceptions.ContactExistsException;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContacts.Entity;
import android.provider.ContactsContract.Settings;
import android.text.format.DateFormat;
import android.util.Base64;
public class AddressbookManagerAndroid extends AbstractAddressbookManager {
private final String accountName = "Panbox";
private final String accountType = "org.panbox";
private final CharSequence dateFormat = "yyyy-MM-dd hh:mm:ss";
private ContentResolver cr = null;
// private Context context = null;
private AccountManager am = null;
private Account panboxAccount = null;
public AddressbookManagerAndroid(Context context,
ContentResolver contentResolver) {
this.cr = contentResolver;
// this.context = context;
am = AccountManager.get(context);
panboxAccount = new Account(accountName, accountType);
am.addAccountExplicitly(panboxAccount, null, null);
ContentProviderClient client = contentResolver
.acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
ContentValues values = new ContentValues();
values.put(ContactsContract.Groups.ACCOUNT_NAME, accountName);
values.put(Groups.ACCOUNT_TYPE, accountType);
values.put(Settings.UNGROUPED_VISIBLE, true);
values.put(Settings.SHOULD_SYNC, false);
try {
client.insert(
Settings.CONTENT_URI
.buildUpon()
.appendQueryParameter(
ContactsContract.CALLER_IS_SYNCADAPTER,
"true").build(), values);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void init() {
// TODO Auto-generated method stub
}
@Override
public void loadContacts(AbstractIdentity identity) {
Uri rawUri = RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType)
.build();
LinkedList<Integer> deletedContacts = new LinkedList<Integer>();
Cursor items = null;
try {
items = cr.query(rawUri, null, null, null, null);
if (items.getCount() > 0) {
items.moveToFirst();
do {
// System.out.print("Load(raw): Creating contact: ");
PanboxContact contact = new PanboxContact();
int idxID = items
.getColumnIndex(ContactsContract.RawContacts._ID);
int id = items.getInt(idxID);
contact.setID(id); // set contact ID to its id in the
// RAW_CONTACTS table
// System.out.println("RawID: " + id);
int idxDeleted = items
.getColumnIndex(ContactsContract.RawContacts.DELETED);
int deleted = items.getInt(idxDeleted);
if (deleted == 1) {
deletedContacts.add(id);
} else {
Uri rawContactUri = ContentUris.withAppendedId(
RawContacts.CONTENT_URI, id);
Uri entityUri = Uri.withAppendedPath(rawContactUri,
Entity.CONTENT_DIRECTORY);
Cursor c = cr.query(entityUri, new String[] {
RawContacts.SOURCE_ID, Entity.DATA_ID,
Entity.MIMETYPE, Entity.DATA1, Entity.DATA2,
Entity.DATA3 }, null, null, null);
try {
while (c.moveToNext()) {
// String sourceId = c.getString(0);
if (!c.isNull(1)) {
long dataID = c.getLong(1);
String mimeType = c.getString(2);
String data = c.getString(3);
String data2 = c.getString(4);
String data3 = c.getString(5);
if ("vnd.android.cursor.item/panbox-sign-cert"
.equals(mimeType)) {
X509Certificate sigCert = CryptCore
.createCertificateFromBytes(Base64
.decode(data,
Base64.DEFAULT));
// System.out.println("Cert: " +
// sigCert.toString());
contact.setCertSign(sigCert);
} else if ("vnd.android.cursor.item/panbox-enc-cert"
.equals(mimeType)) {
X509Certificate encCert = CryptCore
.createCertificateFromBytes(Base64
.decode(data,
Base64.DEFAULT));
// System.out.println("Cert: " +
// encCert.toString());
contact.setCertEnc(encCert);
} else if ("vnd.android.cursor.item/panbox-trustLevel"
.equals(mimeType)) {
contact.setTrustLevel(Integer
.parseInt(data));
} else if (CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
.equals(mimeType)) {
contact.setFirstName(data2);
contact.setName(data3);
} else if (CommonDataKinds.Email.CONTENT_ITEM_TYPE
.equals(mimeType)) {
contact.setEmail(data);
} else if ("vnd.android.cursor.item/panbox-cpi"
.equals(mimeType)) {
CloudProviderInfo cpi = new CloudProviderInfo(
data, data2);
cpi.setId((int) dataID); // set
// cloudprovider
// id
// to its id
// in DATA
// TABLE
contact.addCloudProvider(cpi);
}
}
}
} finally {
c.close();
}
// System.out.println("Add contact to addressbook: " +
// contact);
try {
identity.getAddressbook().addContact(contact);
} catch (ContactExistsException e) {
// should not happen here, otherwise DB is corrupted
}
}
} while (items.moveToNext());
}
} finally {
if (null != items) {
items.close();
}
}
// remove contacts marked as deleted (due to other apps, i.e. default
// contacts app of android
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
for (int id : deletedContacts) {
Uri rawContactUri = ContentUris.withAppendedId(
RawContacts.CONTENT_URI, id);
ops.add(ContentProviderOperation.newDelete(
rawContactUri
.buildUpon()
.appendQueryParameter(
ContactsContract.CALLER_IS_SYNCADAPTER,
"true").build()).build());
}
try {
cr.applyBatch(ContactsContract.AUTHORITY, ops);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (OperationApplicationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void persistContacts(Collection<PanboxContact> contacts,
int identityKey) {
// we ignore the identityKey id because all contacts of account type
// org.panbox belong to our id
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
for (PanboxContact contact : contacts) {
long rawContactIDinDB = contact.getID();
System.out.println("Rawcontact in DB: " + rawContactIDinDB);
if (rawContactIDinDB > 0) // Update contact
{
System.out.println("contact exists: updating it");
ops.add(ContentProviderOperation
.newUpdate(Data.CONTENT_URI)
.withSelection(
Data.RAW_CONTACT_ID + "=? and " + Data.MIMETYPE
+ "=?",
new String[] {
String.valueOf(rawContactIDinDB),
StructuredName.CONTENT_ITEM_TYPE })
.withValue(
StructuredName.DISPLAY_NAME,
contact.getFirstName() + " "
+ contact.getName())
.withValue(StructuredName.FAMILY_NAME,
contact.getName())
.withValue(StructuredName.GIVEN_NAME,
contact.getFirstName()).build());
ops.add(ContentProviderOperation
.newUpdate(Data.CONTENT_URI)
.withSelection(
Data.RAW_CONTACT_ID + "=? and " + Data.MIMETYPE
+ "=?",
new String[] {
String.valueOf(rawContactIDinDB),
Email.CONTENT_ITEM_TYPE })
.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Email.ADDRESS, contact.getEmail()).build());
ops.add(ContentProviderOperation
.newUpdate(Data.CONTENT_URI)
.withSelection(
Data.RAW_CONTACT_ID + "=? and " + Data.MIMETYPE
+ "=?",
new String[] {
String.valueOf(rawContactIDinDB),
"vnd.android.cursor.item/panbox-trustLevel" })
.withValue(Data.MIMETYPE,
"vnd.android.cursor.item/panbox-trustLevel")
.withValue(Data.DATA1, contact.getTrustLevel())
.withValue(Data.DATA2,
"TrustLevel: " + contact.getTrustLevel())
.build());
try {
ops.add(ContentProviderOperation
.newUpdate(Data.CONTENT_URI)
.withSelection(
Data.RAW_CONTACT_ID + "=? and "
+ Data.MIMETYPE + "=?",
new String[] {
String.valueOf(rawContactIDinDB),
"vnd.android.cursor.item/panbox-sign-cert" })
.withValue(Data.MIMETYPE,
"vnd.android.cursor.item/panbox-sign-cert")
.withValue(
Data.DATA1,
Base64.encodeToString(contact.getCertSign()
.getEncoded(), Base64.DEFAULT))
.withValue(
Data.DATA2,
"SignCert: "
+ DateFormat.format(dateFormat,
contact.getCertSign()
.getNotAfter()))
.build());
ops.add(ContentProviderOperation
.newUpdate(Data.CONTENT_URI)
.withSelection(
Data.RAW_CONTACT_ID + "=? and "
+ Data.MIMETYPE + "=?",
new String[] {
String.valueOf(rawContactIDinDB),
"vnd.android.cursor.item/panbox-enc-cert" })
.withValue(Data.MIMETYPE,
"vnd.android.cursor.item/panbox-enc-cert")
.withValue(
Data.DATA1,
Base64.encodeToString(contact.getCertEnc()
.getEncoded(), Base64.DEFAULT))
.withValue(
Data.DATA2,
"EncCert: "
+ DateFormat.format(dateFormat,
contact.getCertEnc()
.getNotAfter()))
.build());
} catch (CertificateEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// update cloudprovider information
for (Entry<String, CloudProviderInfo> cpiEntry : contact
.getCloudProviders().entrySet()) {
CloudProviderInfo cpi = cpiEntry.getValue();
if (cpi.getId() > 0) // update
{
ops.add(ContentProviderOperation
.newUpdate(Data.CONTENT_URI)
.withSelection(
Data.RAW_CONTACT_ID + "=? and "
+ Data.MIMETYPE + "=?",
new String[] {
String.valueOf(cpi.getId()),
"vnd.android.cursor.item/panbox-cpi" })
.withValue(Data.MIMETYPE,
"vnd.android.cursor.item/panbox-cpi")
.withValue(Data.DATA1, cpiEntry.getKey())
.withValue(Data.DATA2,
cpiEntry.getValue().getUsername())
.withValue(
Data.DATA3,
"CPI: "
+ cpiEntry.getKey()
+ " ("
+ cpiEntry.getValue()
.getUsername() + ")")
.build());
} else {
// insert
ops.add(ContentProviderOperation
.newInsert(Data.CONTENT_URI)
.withValue(Data.RAW_CONTACT_ID,
rawContactIDinDB)
.withValue(Data.MIMETYPE,
"vnd.android.cursor.item/panbox-cpi")
.withValue(Data.DATA1, cpiEntry.getKey())
.withValue(Data.DATA2,
cpiEntry.getValue().getUsername())
.withValue(
Data.DATA3,
"CPI: "
+ cpiEntry.getKey()
+ " ("
+ cpiEntry.getValue()
.getUsername() + ")")
.build());
}
}
// walk trough db cloudprovider and remove those that are not
// part of the contact anymore
Uri rawContactUri = ContentUris.withAppendedId(
RawContacts.CONTENT_URI, rawContactIDinDB);
Uri entityUri = Uri.withAppendedPath(rawContactUri,
Entity.CONTENT_DIRECTORY);
Cursor c = cr.query(entityUri, new String[] { Entity.DATA_ID,
Entity.MIMETYPE }, Entity.MIMETYPE + "=?",
new String[] { "vnd.android.cursor.item/panbox-cpi" },
null);
while (c.moveToNext()) {
boolean found = false;
long dataID = c.getLong(0);
for (Entry<String, CloudProviderInfo> cpiEntry : contact
.getCloudProviders().entrySet()) {
CloudProviderInfo cpi = cpiEntry.getValue();
if (cpi.getId() == dataID) {
found = true;
break;
}
}
if (!found) {
ops.add(ContentProviderOperation.newDelete(
ContentUris.withAppendedId(Data.CONTENT_URI,
dataID)).build());
}
}
c.close();
try {
ContentProviderResult[] res = cr.applyBatch(
ContactsContract.AUTHORITY, ops);
if (res.length > 0) {
// long contactID = ContentUris.parseId(res[0].uri);
// contact.setID((int)contactID);
// System.out.println("updated contact with ID: " +
// contactID);
System.out.println("updated existing contact");
}
ops.clear();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (OperationApplicationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else { // insert
int rawContactInsertIndex = ops.size();
ops.add(ContentProviderOperation
.newInsert(RawContacts.CONTENT_URI)
.withValue(RawContacts.ACCOUNT_TYPE, accountType)
.withValue(RawContacts.ACCOUNT_NAME, accountName)
.build());
ops.add(ContentProviderOperation
.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID,
rawContactInsertIndex)
.withValue(Data.MIMETYPE,
StructuredName.CONTENT_ITEM_TYPE)
// .withValue(Data.IS_READ_ONLY, 1)
.withValue(
StructuredName.DISPLAY_NAME,
contact.getFirstName() + " "
+ contact.getName())
.withValue(StructuredName.GIVEN_NAME,
contact.getFirstName())
.withValue(StructuredName.FAMILY_NAME,
contact.getName()).build());
ops.add(ContentProviderOperation
.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID,
rawContactInsertIndex)
// .withValue(Data.IS_READ_ONLY, 1)
.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Email.ADDRESS, contact.getEmail()).build());
ops.add(ContentProviderOperation
.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID,
rawContactInsertIndex)
.withValue(Data.MIMETYPE,
"vnd.android.cursor.item/panbox-trustLevel")
.withValue(Data.DATA1, contact.getTrustLevel())
.withValue(Data.DATA2,
"TrustLevel: " + contact.getTrustLevel())
.build());
try {
ops.add(ContentProviderOperation
.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID,
rawContactInsertIndex)
.withValue(Data.MIMETYPE,
"vnd.android.cursor.item/panbox-sign-cert")
.withValue(
Data.DATA1,
Base64.encodeToString(contact.getCertSign()
.getEncoded(), Base64.DEFAULT))
.withValue(
Data.DATA2,
"SignCert: "
+ DateFormat.format(dateFormat,
contact.getCertSign()
.getNotAfter()))
.build());
ops.add(ContentProviderOperation
.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID,
rawContactInsertIndex)
.withValue(Data.MIMETYPE,
"vnd.android.cursor.item/panbox-enc-cert")
.withValue(
Data.DATA1,
Base64.encodeToString(contact.getCertEnc()
.getEncoded(), Base64.DEFAULT))
.withValue(
Data.DATA2,
"EncCert: "
+ DateFormat.format(dateFormat,
contact.getCertEnc()
.getNotAfter()))
.build());
} catch (CertificateEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// store cloudprovider information
for (Entry<String, CloudProviderInfo> cpiEntry : contact
.getCloudProviders().entrySet()) {
ops.add(ContentProviderOperation
.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID,
rawContactInsertIndex)
.withValue(Data.MIMETYPE,
"vnd.android.cursor.item/panbox-cpi")
.withValue(Data.DATA1, cpiEntry.getKey())
.withValue(Data.DATA2,
cpiEntry.getValue().getUsername())
.withValue(
Data.DATA3,
"CPI: " + cpiEntry.getKey() + " ("
+ cpiEntry.getValue().getUsername()
+ ")").build());
}
try {
ContentProviderResult[] res = cr.applyBatch(
ContactsContract.AUTHORITY, ops);
if (res.length > 0) {
long contactID = ContentUris.parseId(res[0].uri);
contact.setID((int) contactID);
// System.out.println("added contact with ID: " +
// contactID);
}
ops.clear();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (OperationApplicationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
ops.clear();
// walk through raw contacts in db and delete those that are not
// part of the identity's addressbook anymore
Uri rawUri = RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType)
.build();
Cursor items = null;
try {
items = cr.query(rawUri, null, null, null, null);
if (items.getCount() > 0) {
items.moveToFirst();
do {
int idxID = items
.getColumnIndex(ContactsContract.RawContacts._ID);
int id = items.getInt(idxID);
boolean found = false;
for (PanboxContact contact : contacts) {
if (contact.getID() == id) {
found = true;
break;
}
}
if (!found) {
Uri rawContactUri = ContentUris.withAppendedId(
RawContacts.CONTENT_URI, id);
ops.add(ContentProviderOperation
.newDelete(
rawContactUri
.buildUpon()
.appendQueryParameter(
ContactsContract.CALLER_IS_SYNCADAPTER,
"true").build())
.build());
}
} while (items.moveToNext());
}
} finally {
if(items != null)
{
items.close();
}
}
try {
cr.applyBatch(ContactsContract.AUTHORITY, ops);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (OperationApplicationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}