Merge pull request #1043 from nextcloud/fixingContactBackup

Fixing contact backup
This commit is contained in:
Mario Đanić 2017-05-31 22:16:28 +02:00 committed by GitHub
commit 037236f095
4 changed files with 278 additions and 107 deletions

View file

@ -21,14 +21,22 @@
package com.owncloud.android.services;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import com.evernote.android.job.Job;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.ui.fragment.contactsbackup.ContactListFragment;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.TreeMap;
import ezvcard.Ezvcard;
import ezvcard.VCard;
@ -62,9 +70,34 @@ public class ContactsImportJob extends Job {
try {
ContactOperations operations = new ContactOperations(getContext(), accountName, accountType);
vCards.addAll(Ezvcard.parse(file).all());
Collections.sort(vCards, new ContactListFragment.VCardComparator());
Cursor cursor = getContext().getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null,
null, null, null);
for (int i = 0; i < intArray.length; i++ ){
operations.insertContact(vCards.get(intArray[i]));
TreeMap<VCard, Long> ownContactList = new TreeMap<>(new ContactListFragment.VCardComparator());
if (cursor != null && cursor.getCount() > 0) {
cursor.moveToFirst();
for (int i = 0; i < cursor.getCount(); i++) {
VCard vCard = getContactFromCursor(cursor);
if (vCard != null) {
ownContactList.put(vCard, cursor.getLong(cursor.getColumnIndex("NAME_RAW_CONTACT_ID")));
}
cursor.moveToNext();
}
}
for (int i = 0; i < intArray.length; i++) {
VCard vCard = vCards.get(intArray[i]);
if (ContactListFragment.getDisplayName(vCard).length() != 0) {
if (!ownContactList.containsKey(vCard)) {
operations.insertContact(vCard);
} else {
operations.updateContact(vCard, ownContactList.get(vCard));
}
} else {
operations.insertContact(vCard); //Insert All the contacts without name
}
}
} catch (Exception e) {
Log_OC.e(TAG, e.getMessage());
@ -72,4 +105,24 @@ public class ContactsImportJob extends Job {
return Result.SUCCESS;
}
private VCard getContactFromCursor(Cursor cursor) {
String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey);
VCard vCard = null;
try {
InputStream inputStream = getContext().getContentResolver().openInputStream(uri);
ArrayList<VCard> vCardList = new ArrayList<>();
vCardList.addAll(Ezvcard.parse(inputStream).all());
if (vCardList.size() > 0) {
vCard = vCardList.get(0);
}
} catch (IOException e) {
Log_OC.d(TAG, e.getMessage());
}
return vCard;
}
}

View file

@ -31,6 +31,7 @@ import android.content.IntentFilter;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
@ -53,6 +54,9 @@ import android.widget.CheckedTextView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.util.support.PersistableBundleCompat;
@ -86,7 +90,8 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import ezvcard.Ezvcard;
import ezvcard.VCard;
import ezvcard.property.StructuredName;
import static com.owncloud.android.ui.fragment.contactsbackup.ContactListFragment.getDisplayName;
/**
* This fragment shows all contacts from a file and allows to import them.
@ -108,8 +113,26 @@ public class ContactListFragment extends FileFragment {
@BindView(R.id.contactlist_restore_selected)
public Button restoreContacts;
@BindView(R.id.empty_list_view_text)
public TextView emptyContentMessage;
@BindView(R.id.empty_list_view_headline)
public TextView emptyContentHeadline;
@BindView(R.id.empty_list_icon)
public ImageView emptyContentIcon;
@BindView(R.id.empty_list_progress)
public ProgressBar emptyContentProgressBar;
@BindView(R.id.empty_list_container)
public RelativeLayout emptyListContainer;
private ContactListAdapter contactListAdapter;
private Account account;
private ArrayList<VCard> vCards = new ArrayList<>();
private OCFile ocFile;
public static ContactListFragment newInstance(OCFile file, Account account) {
ContactListFragment frag = new ContactListFragment();
@ -147,47 +170,41 @@ public class ContactListFragment extends FileFragment {
}
contactsPreferenceActivity.setDrawerIndicatorEnabled(false);
ArrayList<VCard> vCards = new ArrayList<>();
recyclerView = (RecyclerView) view.findViewById(R.id.contactlist_recyclerview);
try {
OCFile ocFile = getArguments().getParcelable(FILE_NAME);
setFile(ocFile);
account = getArguments().getParcelable(ACCOUNT);
if (!ocFile.isDown()) {
Intent i = new Intent(getContext(), FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
i.putExtra(FileDownloader.EXTRA_FILE, ocFile);
getContext().startService(i);
// Listen for download messages
IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.getDownloadAddedMessage());
downloadIntentFilter.addAction(FileDownloader.getDownloadFinishMessage());
DownloadFinishReceiver mDownloadFinishReceiver = new DownloadFinishReceiver();
getContext().registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
} else {
File file = new File(ocFile.getStoragePath());
Collections.sort(vCards, new Comparator<VCard>() {
@Override
public int compare(VCard o1, VCard o2) {
if (o1.getFormattedName() != null && o2.getFormattedName() != null) {
return o1.getFormattedName().getValue().compareTo(o2.getFormattedName().getValue());
} else {
if (o1.getFormattedName() == null) {
return 1; // Send the contact to the end of the list
} else {
return -1;
}
}
}
});
vCards.addAll(Ezvcard.parse(file).all());
if (savedInstanceState == null) {
contactListAdapter = new ContactListAdapter(getContext(), vCards);
} else {
Set<Integer> checkedItems = new HashSet<>();
int[] itemsArray = savedInstanceState.getIntArray(CHECKED_ITEMS_ARRAY_KEY);
for (int i = 0; i < itemsArray.length; i++) {
checkedItems.add(itemsArray[i]);
}
} catch (IOException e) {
Log_OC.e(TAG, "Error processing contacts file!", e);
if (checkedItems.size() > 0) {
onMessageEvent(new VCardToggleEvent(true));
}
contactListAdapter = new ContactListAdapter(getContext(), vCards, checkedItems);
}
recyclerView.setAdapter(contactListAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
ocFile = getArguments().getParcelable(FILE_NAME);
setFile(ocFile);
account = getArguments().getParcelable(ACCOUNT);
if (!ocFile.isDown()) {
Intent i = new Intent(getContext(), FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
i.putExtra(FileDownloader.EXTRA_FILE, ocFile);
getContext().startService(i);
// Listen for download messages
IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.getDownloadAddedMessage());
downloadIntentFilter.addAction(FileDownloader.getDownloadFinishMessage());
DownloadFinishReceiver mDownloadFinishReceiver = new DownloadFinishReceiver();
getContext().registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
} else {
loadContactsTask.execute();
}
restoreContacts.setOnClickListener(new View.OnClickListener() {
@ -200,26 +217,6 @@ public class ContactListFragment extends FileFragment {
}
});
recyclerView = (RecyclerView) view.findViewById(R.id.contactlist_recyclerview);
if (savedInstanceState == null) {
contactListAdapter = new ContactListAdapter(getContext(), vCards);
} else {
Set<Integer> checkedItems = new HashSet<>();
int[] itemsArray = savedInstanceState.getIntArray(CHECKED_ITEMS_ARRAY_KEY);
if (itemsArray != null) {
for (int item : itemsArray) {
checkedItems.add(item);
}
}
if (checkedItems.size() > 0) {
onMessageEvent(new VCardToggleEvent(true));
}
contactListAdapter = new ContactListAdapter(getContext(), vCards, checkedItems);
}
recyclerView.setAdapter(contactListAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
return view;
}
@ -260,6 +257,9 @@ public class ContactListFragment extends FileFragment {
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
if (loadContactsTask != null) {
loadContactsTask.cancel(true);
}
super.onStop();
}
@ -286,6 +286,14 @@ public class ContactListFragment extends FileFragment {
return retval;
}
private void setLoadingMessage() {
emptyContentHeadline.setText(R.string.file_list_loading);
emptyContentMessage.setText("");
emptyContentIcon.setVisibility(View.GONE);
emptyContentProgressBar.setVisibility(View.VISIBLE);
}
private void setSelectAllMenuItem(MenuItem selectAll, boolean checked) {
selectAll.setChecked(checked);
if (checked) {
@ -476,22 +484,72 @@ public class ContactListFragment extends FileFragment {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equalsIgnoreCase(FileDownloader.getDownloadFinishMessage())){
if (intent.getAction().equalsIgnoreCase(FileDownloader.getDownloadFinishMessage())) {
String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
FileDataStorageManager storageManager = new FileDataStorageManager(account,
getContext().getContentResolver());
OCFile ocFile = storageManager.getFileByPath(downloadedRemotePath);
File file = new File(ocFile.getStoragePath());
try {
contactListAdapter.replaceVCards(Ezvcard.parse(file).all());
} catch (IOException e) {
Log_OC.e(TAG, "IO Exception: " + file.getAbsolutePath());
}
ocFile = storageManager.getFileByPath(downloadedRemotePath);
loadContactsTask.execute();
}
}
}
public static class VCardComparator implements Comparator<VCard> {
@Override
public int compare(VCard o1, VCard o2) {
String contac1 = getDisplayName(o1);
String contac2 = getDisplayName(o2);
return contac1.compareToIgnoreCase(contac2);
}
}
private AsyncTask loadContactsTask = new AsyncTask() {
@Override
protected void onPreExecute() {
setLoadingMessage();
}
@Override
protected Object doInBackground(Object[] params) {
if (!isCancelled()) {
File file = new File(ocFile.getStoragePath());
try {
vCards.addAll(Ezvcard.parse(file).all());
Collections.sort(vCards, new VCardComparator());
} catch (IOException e) {
Log_OC.e(TAG, "IO Exception: " + file.getAbsolutePath());
return false;
}
return true;
}
return false;
}
@Override
protected void onPostExecute(Object o) {
if (!isCancelled()) {
emptyListContainer.setVisibility(View.GONE);
contactListAdapter.replaceVCards(vCards);
}
}
};
public static String getDisplayName(VCard vCard) {
if (vCard.getFormattedName() != null) {
return vCard.getFormattedName().getValue();
} else if (vCard.getTelephoneNumbers() != null && vCard.getTelephoneNumbers().size() > 0) {
return vCard.getTelephoneNumbers().get(0).getText();
} else if (vCard.getEmails() != null && vCard.getEmails().size() > 0) {
return vCard.getEmails().get(0).getValue();
}
return "";
}
}
class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.ContactItemViewHolder> {
@ -561,15 +619,8 @@ class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.Contac
} else {
holder.getName().setChecked(false);
}
// name
StructuredName name = vcard.getStructuredName();
if (name != null) {
String first = (name.getGiven() == null) ? "" : name.getGiven() + " ";
String last = (name.getFamily() == null) ? "" : name.getFamily();
holder.getName().setText(String.format("%s%s", first, last));
} else {
holder.getName().setText("");
}
holder.getName().setText(getDisplayName(vcard));
// photo
if (vcard.getPhotos().size() > 0) {
@ -641,4 +692,5 @@ class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.Contac
notifyDataSetChanged();
}
}

View file

@ -141,6 +141,59 @@ public class ContactOperations {
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
}
public void updateContact(VCard vcard, Long key) throws RemoteException, OperationApplicationException {
List<NonEmptyContentValues> contentValues = new ArrayList<NonEmptyContentValues>();
convertName(contentValues, vcard);
convertNickname(contentValues, vcard);
convertPhones(contentValues, vcard);
convertEmails(contentValues, vcard);
convertAddresses(contentValues, vcard);
convertIms(contentValues, vcard);
// handle Android Custom fields..This is only valid for Android generated Vcards. As the Android would
// generate NickName, ContactEvents other than Birthday and RelationShip with this "X-ANDROID-CUSTOM" name
convertCustomFields(contentValues, vcard);
// handle Iphone kinda of group properties. which are grouped together.
convertGroupedProperties(contentValues, vcard);
convertBirthdays(contentValues, vcard);
convertWebsites(contentValues, vcard);
convertNotes(contentValues, vcard);
convertPhotos(contentValues, vcard);
convertOrganization(contentValues, vcard);
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(contentValues.size());
ContentValues cv = account.getContentValues();
//ContactsContract.RawContact.CONTENT_URI needed to add account, backReference is also not needed
long contactID = key;
ContentProviderOperation operation;
for (NonEmptyContentValues values : contentValues) {
cv = values.getContentValues();
if (cv.size() == 0) {
continue;
}
String mimeType = cv.getAsString("mimetype");
cv.remove("mimetype");
//@formatter:off
operation =
ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.RAW_CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ? ", new String[]{"" + contactID, "" + mimeType})
.withValues(cv)
.build();
//@formatter:on
operations.add(operation);
}
// Executing all the insert operations as a single database transaction
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
}
private void convertName(List<NonEmptyContentValues> contentValues, VCard vcard) {
NonEmptyContentValues values = new NonEmptyContentValues(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);

View file

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
Nextcloud Android client application
Copyright (C) 2017 Tobias Kaminsky
@ -18,39 +17,53 @@
You should have received a copy of the GNU Affero General Public
License along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/contactlist_recyclerview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:choiceMode="multipleChoice"/>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
<LinearLayout
android:id="@+id/contactlist_restore_selected_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical"
android:visibility="gone">
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
<android.support.v7.widget.RecyclerView
android:id="@+id/contactlist_recyclerview"
android:layout_width="match_parent"
android:layout_height="1dp"
android:src="@drawable/uploader_list_separator"/>
android:layout_height="0dp"
android:layout_weight="1"
android:choiceMode="multipleChoice" />
<android.support.v7.widget.AppCompatButton
android:id="@+id/contactlist_restore_selected"
style="@style/Button.Borderless"
<LinearLayout
android:id="@+id/contactlist_restore_selected_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/contaclist_restore_selected"/>
android:background="@color/white"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="match_parent"
android:layout_height="1dp"
android:src="@drawable/uploader_list_separator" />
<android.support.v7.widget.AppCompatButton
android:id="@+id/contactlist_restore_selected"
style="@style/Button.Borderless"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/contaclist_restore_selected" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<RelativeLayout
android:id="@+id/empty_list_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<include layout="@layout/empty_list" />
</RelativeLayout>
</RelativeLayout>