Explorar o código

Merge pull request #1043 from nextcloud/fixingContactBackup

Fixing contact backup
Mario Đanić %!s(int64=8) %!d(string=hai) anos
pai
achega
037236f095

+ 55 - 2
src/main/java/com/owncloud/android/services/ContactsImportJob.java

@@ -21,14 +21,22 @@
 
 
 package com.owncloud.android.services;
 package com.owncloud.android.services;
 
 
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
 import android.support.annotation.NonNull;
 import android.support.annotation.NonNull;
 
 
 import com.evernote.android.job.Job;
 import com.evernote.android.job.Job;
 import com.evernote.android.job.util.support.PersistableBundleCompat;
 import com.evernote.android.job.util.support.PersistableBundleCompat;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.ui.fragment.contactsbackup.ContactListFragment;
 
 
 import java.io.File;
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.TreeMap;
 
 
 import ezvcard.Ezvcard;
 import ezvcard.Ezvcard;
 import ezvcard.VCard;
 import ezvcard.VCard;
@@ -62,9 +70,34 @@ public class ContactsImportJob extends Job {
         try {
         try {
             ContactOperations operations = new ContactOperations(getContext(), accountName, accountType);
             ContactOperations operations = new ContactOperations(getContext(), accountName, accountType);
             vCards.addAll(Ezvcard.parse(file).all());
             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) {
         } catch (Exception e) {
             Log_OC.e(TAG, e.getMessage());
             Log_OC.e(TAG, e.getMessage());
@@ -72,4 +105,24 @@ public class ContactsImportJob extends Job {
 
 
         return Result.SUCCESS;
         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;
+    }
+
+
 }
 }

+ 123 - 71
src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java

@@ -31,6 +31,7 @@ import android.content.IntentFilter;
 import android.database.Cursor;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Handler;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract;
@@ -53,6 +54,9 @@ import android.widget.CheckedTextView;
 import android.widget.ImageView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.LinearLayout;
 import android.widget.Toast;
 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.JobRequest;
 import com.evernote.android.job.util.support.PersistableBundleCompat;
 import com.evernote.android.job.util.support.PersistableBundleCompat;
@@ -86,7 +90,8 @@ import butterknife.BindView;
 import butterknife.ButterKnife;
 import butterknife.ButterKnife;
 import ezvcard.Ezvcard;
 import ezvcard.Ezvcard;
 import ezvcard.VCard;
 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.
  * 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)
     @BindView(R.id.contactlist_restore_selected)
     public Button restoreContacts;
     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 ContactListAdapter contactListAdapter;
     private Account account;
     private Account account;
+    private ArrayList<VCard> vCards = new ArrayList<>();
+    private OCFile ocFile;
 
 
     public static ContactListFragment newInstance(OCFile file, Account account) {
     public static ContactListFragment newInstance(OCFile file, Account account) {
         ContactListFragment frag = new ContactListFragment();
         ContactListFragment frag = new ContactListFragment();
@@ -147,59 +170,6 @@ public class ContactListFragment extends FileFragment {
         }
         }
         contactsPreferenceActivity.setDrawerIndicatorEnabled(false);
         contactsPreferenceActivity.setDrawerIndicatorEnabled(false);
 
 
-        ArrayList<VCard> vCards = new ArrayList<>();
-
-        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());
-            }
-        } catch (IOException e) {
-            Log_OC.e(TAG, "Error processing contacts file!", e);
-        }
-
-        restoreContacts.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-
-                if (checkAndAskForContactsWritePermission()) {
-                    getAccountForImport();
-                }
-            }
-        });
-
         recyclerView = (RecyclerView) view.findViewById(R.id.contactlist_recyclerview);
         recyclerView = (RecyclerView) view.findViewById(R.id.contactlist_recyclerview);
 
 
         if (savedInstanceState == null) {
         if (savedInstanceState == null) {
@@ -207,10 +177,8 @@ public class ContactListFragment extends FileFragment {
         } else {
         } else {
             Set<Integer> checkedItems = new HashSet<>();
             Set<Integer> checkedItems = new HashSet<>();
             int[] itemsArray = savedInstanceState.getIntArray(CHECKED_ITEMS_ARRAY_KEY);
             int[] itemsArray = savedInstanceState.getIntArray(CHECKED_ITEMS_ARRAY_KEY);
-            if (itemsArray != null) {
-                for (int item : itemsArray) {
-                    checkedItems.add(item);
-                }
+            for (int i = 0; i < itemsArray.length; i++) {
+                checkedItems.add(itemsArray[i]);
             }
             }
             if (checkedItems.size() > 0) {
             if (checkedItems.size() > 0) {
                 onMessageEvent(new VCardToggleEvent(true));
                 onMessageEvent(new VCardToggleEvent(true));
@@ -220,6 +188,35 @@ public class ContactListFragment extends FileFragment {
         recyclerView.setAdapter(contactListAdapter);
         recyclerView.setAdapter(contactListAdapter);
         recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
         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() {
+            @Override
+            public void onClick(View v) {
+
+                if (checkAndAskForContactsWritePermission()) {
+                    getAccountForImport();
+                }
+            }
+        });
+
         return view;
         return view;
     }
     }
 
 
@@ -260,6 +257,9 @@ public class ContactListFragment extends FileFragment {
     @Override
     @Override
     public void onStop() {
     public void onStop() {
         EventBus.getDefault().unregister(this);
         EventBus.getDefault().unregister(this);
+        if (loadContactsTask != null) {
+            loadContactsTask.cancel(true);
+        }
         super.onStop();
         super.onStop();
     }
     }
 
 
@@ -286,6 +286,14 @@ public class ContactListFragment extends FileFragment {
         return retval;
         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) {
     private void setSelectAllMenuItem(MenuItem selectAll, boolean checked) {
         selectAll.setChecked(checked);
         selectAll.setChecked(checked);
         if (checked) {
         if (checked) {
@@ -476,21 +484,71 @@ public class ContactListFragment extends FileFragment {
 
 
         @Override
         @Override
         public void onReceive(Context context, Intent intent) {
         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);
                 String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
 
 
                 FileDataStorageManager storageManager = new FileDataStorageManager(account,
                 FileDataStorageManager storageManager = new FileDataStorageManager(account,
                         getContext().getContentResolver());
                         getContext().getContentResolver());
-                OCFile ocFile = storageManager.getFileByPath(downloadedRemotePath);
-                File file = new File(ocFile.getStoragePath());
+                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 {
                 try {
-                    contactListAdapter.replaceVCards(Ezvcard.parse(file).all());
+                    vCards.addAll(Ezvcard.parse(file).all());
+                    Collections.sort(vCards, new VCardComparator());
                 } catch (IOException e) {
                 } catch (IOException e) {
                     Log_OC.e(TAG, "IO Exception: " + file.getAbsolutePath());
                     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 "";
     }
     }
 }
 }
 
 
@@ -561,15 +619,8 @@ class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.Contac
             } else {
             } else {
                 holder.getName().setChecked(false);
                 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
             // photo
             if (vcard.getPhotos().size() > 0) {
             if (vcard.getPhotos().size() > 0) {
@@ -641,4 +692,5 @@ class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.Contac
 
 
         notifyDataSetChanged();
         notifyDataSetChanged();
     }
     }
+
 }
 }

+ 53 - 0
src/main/java/third_parties/ezvcard_android/ContactOperations.java

@@ -141,6 +141,59 @@ public class ContactOperations {
         context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
         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) {
     private void convertName(List<NonEmptyContentValues> contentValues, VCard vcard) {
         NonEmptyContentValues values = new NonEmptyContentValues(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
         NonEmptyContentValues values = new NonEmptyContentValues(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
 
 

+ 40 - 27
src/main/res/layout/contactlist_fragment.xml

@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   Nextcloud Android client application
   Nextcloud Android client application
 
 
   Copyright (C) 2017 Tobias Kaminsky
   Copyright (C) 2017 Tobias Kaminsky
@@ -18,39 +17,53 @@
   You should have received a copy of the GNU Affero General Public
   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/>.
   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
     <LinearLayout
-        android:id="@+id/contactlist_restore_selected_container"
         android:layout_width="match_parent"
         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_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_width="match_parent"
             android:layout_height="wrap_content"
             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>
 
 
-</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>