Эх сурвалжийг харах

Migrate contacts import job to new API

Signed-off-by: Chris Narkiewicz <hello@ezaquarii.com>
Chris Narkiewicz 5 жил өмнө
parent
commit
5204ba7541

+ 1 - 0
build.gradle

@@ -191,6 +191,7 @@ android {
 
         testOptions {
             unitTests.returnDefaultValues = true
+            // comment out if you run androidTests in Android Studio experiencing test runner crashes
             execution 'ANDROIDX_TEST_ORCHESTRATOR'
             animationsDisabled true
         }

+ 59 - 0
src/androidTest/java/com/nextcloud/client/jobs/BackgroundJobManagerTest.kt

@@ -67,6 +67,7 @@ import java.util.concurrent.TimeoutException
     BackgroundJobManagerTest.ContentObserver::class,
     BackgroundJobManagerTest.PeriodicContactsBackup::class,
     BackgroundJobManagerTest.ImmediateContactsBackup::class,
+    BackgroundJobManagerTest.ImmediateContactsImport::class,
     BackgroundJobManagerTest.Tags::class
 )
 class BackgroundJobManagerTest {
@@ -307,6 +308,64 @@ class BackgroundJobManagerTest {
         }
     }
 
+    class ImmediateContactsImport : Fixture() {
+
+        private lateinit var workInfo: MutableLiveData<WorkInfo>
+        private lateinit var jobInfo: LiveData<JobInfo?>
+        private lateinit var request: OneTimeWorkRequest
+
+        @Before
+        fun setUp() {
+            val requestCaptor: KArgumentCaptor<OneTimeWorkRequest> = argumentCaptor()
+            workInfo = MutableLiveData()
+            whenever(workManager.getWorkInfoByIdLiveData(any())).thenReturn(workInfo)
+            jobInfo = backgroundJobManager.startImmediateContactsImport(
+                contactsAccountName = "name",
+                contactsAccountType = "type",
+                vCardFilePath = "/path/to/vcard/file",
+                selectedContacts = intArrayOf(1, 2, 3)
+            )
+            verify(workManager).enqueueUniqueWork(
+                any(),
+                any(),
+                requestCaptor.capture()
+            )
+            assertEquals(1, requestCaptor.allValues.size)
+            request = requestCaptor.firstValue
+        }
+
+        @Test
+        fun job_is_unique() {
+            verify(workManager).enqueueUniqueWork(
+                eq(BackgroundJobManagerImpl.JOB_IMMEDIATE_CONTACTS_IMPORT),
+                eq(ExistingWorkPolicy.KEEP),
+                argThat(IsOneTimeWorkRequest())
+            )
+        }
+
+        @Test
+        fun job_request_has_mandatory_tags() {
+            assertHasRequiredTags(request.tags, BackgroundJobManagerImpl.JOB_IMMEDIATE_CONTACTS_IMPORT)
+        }
+
+        @Test
+        @UiThreadTest
+        fun job_info_is_obtained_from_work_info() {
+            // GIVEN
+            //      work info is available
+            workInfo.value = buildWorkInfo(0)
+
+            // WHEN
+            //      job info has listener
+            jobInfo.observeForever {}
+
+            // THEN
+            //      converted value is available
+            assertNotNull(jobInfo.value)
+            assertEquals(workInfo.value?.id, jobInfo.value?.id)
+        }
+    }
+
     class Tags {
         @Test
         fun split_tag_key_and_value() {

+ 13 - 2
src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt

@@ -31,6 +31,7 @@ import com.nextcloud.client.account.UserAccountManager
 import com.nextcloud.client.core.Clock
 import com.nextcloud.client.device.DeviceInfo
 import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.logger.Logger
 import com.nextcloud.client.preferences.AppPreferences
 import com.owncloud.android.datamodel.ArbitraryDataProvider
 import com.owncloud.android.datamodel.SyncedFolderProvider
@@ -38,10 +39,10 @@ import javax.inject.Inject
 import javax.inject.Provider
 
 /**
- * This factory is responsible for creating all background jobs and for injecting
- * all jobs dependencies.
+ * This factory is responsible for creating all background jobs and for injecting job dependencies.
  */
 class BackgroundJobFactory @Inject constructor(
+    private val logger: Logger,
     private val preferences: AppPreferences,
     private val contentResolver: ContentResolver,
     private val clock: Clock,
@@ -68,6 +69,7 @@ class BackgroundJobFactory @Inject constructor(
         return when (workerClass) {
             ContentObserverWork::class -> createContentObserverJob(context, workerParameters, clock)
             ContactsBackupWork::class -> createContactsBackupWork(context, workerParameters)
+            ContactsImportWork::class -> createContactsImportWork(context, workerParameters)
             else -> null // falls back to default factory
         }
     }
@@ -102,4 +104,13 @@ class BackgroundJobFactory @Inject constructor(
             accountManager
         )
     }
+
+    private fun createContactsImportWork(context: Context, params: WorkerParameters): ContactsImportWork {
+        return ContactsImportWork(
+            context,
+            params,
+            logger,
+            contentResolver
+        )
+    }
 }

+ 20 - 1
src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt

@@ -66,10 +66,29 @@ interface BackgroundJobManager {
      * Immediately start single contacts backup job.
      * This job will launch independently from periodic contacts backup.
      *
-     * @return Job info with current status, or null if job does not exist anymore
+     * @return Job info with current status; status is null if job does not exist
      */
     fun startImmediateContactsBackup(user: User): LiveData<JobInfo?>
 
+    /**
+     * Immediately start contacts import job. Import job will be started only once.
+     * If new job is started while existing job is running - request will be ignored
+     * and currently running job will continue running.
+     *
+     * @param contactsAccountName Target contacts account name; null for local contacts
+     * @param contactsAccountType Target contacts account type; null for local contacts
+     * @param vCardFilePath Path to file containing all contact entries
+     * @param selectedContacts List of contact indices to import from [vCardFilePath] file
+     *
+     * @return Job info with current status; status is null if job does not exist
+     */
+    fun startImmediateContactsImport(
+        contactsAccountName: String?,
+        contactsAccountType: String?,
+        vCardFilePath: String,
+        selectedContacts: IntArray
+    ): LiveData<JobInfo?>
+
     fun scheduleTestJob()
     fun cancelTestJob()
 

+ 29 - 0
src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt

@@ -66,6 +66,7 @@ internal class BackgroundJobManagerImpl(
         const val JOB_CONTENT_OBSERVER = "content_observer"
         const val JOB_PERIODIC_CONTACTS_BACKUP = "periodic_contacts_backup"
         const val JOB_IMMEDIATE_CONTACTS_BACKUP = "immediate_contacts_backup"
+        const val JOB_IMMEDIATE_CONTACTS_IMPORT = "immediate_contacts_import"
         const val JOB_TEST = "test_job"
 
         const val MAX_CONTENT_TRIGGER_DELAY_MS = 1500L
@@ -225,6 +226,34 @@ internal class BackgroundJobManagerImpl(
         workManager.cancelJob(JOB_PERIODIC_CONTACTS_BACKUP, user)
     }
 
+    override fun startImmediateContactsImport(
+        contactsAccountName: String?,
+        contactsAccountType: String?,
+        vCardFilePath: String,
+        selectedContacts: IntArray
+    ): LiveData<JobInfo?> {
+
+        val data = Data.Builder()
+            .putString(ContactsImportWork.ACCOUNT_NAME, contactsAccountName)
+            .putString(ContactsImportWork.ACCOUNT_TYPE, contactsAccountType)
+            .putString(ContactsImportWork.VCARD_FILE_PATH, vCardFilePath)
+            .putIntArray(ContactsImportWork.SELECTED_CONTACTS_INDICES, selectedContacts)
+            .build()
+
+        val constraints = Constraints.Builder()
+            .setRequiresCharging(false)
+            .build()
+
+        val request = oneTimeRequestBuilder(ContactsImportWork::class, JOB_IMMEDIATE_CONTACTS_IMPORT)
+            .setInputData(data)
+            .setConstraints(constraints)
+            .build()
+
+        workManager.enqueueUniqueWork(JOB_IMMEDIATE_CONTACTS_IMPORT, ExistingWorkPolicy.KEEP, request)
+
+        return workManager.getJobInfo(request.id)
+    }
+
     override fun startImmediateContactsBackup(user: User): LiveData<JobInfo?> {
         val data = Data.Builder()
             .putString(ContactsBackupWork.ACCOUNT, user.accountName)

+ 128 - 0
src/main/java/com/nextcloud/client/jobs/ContactsImportWork.kt

@@ -0,0 +1,128 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2017 Tobias Kaminsky
+ * Copyright (C) 2017 Nextcloud GmbH.
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * 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/>.
+ */
+package com.nextcloud.client.jobs
+
+import android.content.ContentResolver
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import android.provider.ContactsContract
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.nextcloud.client.logger.Logger
+import com.owncloud.android.ui.fragment.contactsbackup.ContactListFragment
+import com.owncloud.android.ui.fragment.contactsbackup.ContactListFragment.VCardComparator
+import ezvcard.Ezvcard
+import ezvcard.VCard
+import third_parties.ezvcard_android.ContactOperations
+import java.io.File
+import java.io.IOException
+import java.util.ArrayList
+import java.util.Collections
+import java.util.TreeMap
+
+class ContactsImportWork(
+    appContext: Context,
+    params: WorkerParameters,
+    private val logger: Logger,
+    private val contentResolver: ContentResolver
+) : Worker(appContext, params) {
+
+    companion object {
+        const val TAG = "ContactsImportWork"
+        const val ACCOUNT_TYPE = "account_type"
+        const val ACCOUNT_NAME = "account_name"
+        const val VCARD_FILE_PATH = "vcard_file_path"
+        const val SELECTED_CONTACTS_INDICES = "selected_contacts_indices"
+    }
+
+    override fun doWork(): Result {
+        val vCardFilePath = inputData.getString(VCARD_FILE_PATH) ?: ""
+        val contactsAccountName = inputData.getString(ACCOUNT_NAME)
+        val contactsAccountType = inputData.getString(ACCOUNT_TYPE)
+        val selectedContactsIndices = inputData.getIntArray(SELECTED_CONTACTS_INDICES) ?: IntArray(0)
+
+        val file = File(vCardFilePath)
+        val vCards = ArrayList<VCard>()
+
+        var cursor: Cursor? = null
+        try {
+            val operations = ContactOperations(applicationContext, contactsAccountName, contactsAccountType)
+            vCards.addAll(Ezvcard.parse(file).all())
+            Collections.sort(vCards, VCardComparator())
+            cursor = contentResolver.query(
+                ContactsContract.Contacts.CONTENT_URI,
+                null,
+                null,
+                null,
+                null
+            )
+            val ownContactMap = TreeMap<VCard, Long?>(VCardComparator())
+            if (cursor != null && cursor.count > 0) {
+                cursor.moveToFirst()
+                for (i in 0 until cursor.count) {
+                    val vCard = getContactFromCursor(cursor)
+                    if (vCard != null) {
+                        ownContactMap[vCard] = cursor.getLong(cursor.getColumnIndex("NAME_RAW_CONTACT_ID"))
+                    }
+                    cursor.moveToNext()
+                }
+            }
+            for (contactIndex in selectedContactsIndices) {
+                val vCard = vCards[contactIndex]
+                if (ContactListFragment.getDisplayName(vCard).isEmpty()) {
+                    if (!ownContactMap.containsKey(vCard)) {
+                        operations.insertContact(vCard)
+                    } else {
+                        operations.updateContact(vCard, ownContactMap[vCard])
+                    }
+                } else {
+                    operations.insertContact(vCard) // Insert All the contacts without name
+                }
+            }
+        } catch (e: Exception) {
+            logger.e(TAG, "${e.message}")
+        } finally {
+            cursor?.close()
+        }
+
+        return Result.success()
+    }
+
+    private fun getContactFromCursor(cursor: Cursor): VCard? {
+        val lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY))
+        val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey)
+        var vCard: VCard? = null
+        try {
+            contentResolver.openInputStream(uri).use { inputStream ->
+                val vCardList = ArrayList<VCard>()
+                vCardList.addAll(Ezvcard.parse(inputStream).all())
+                if (vCardList.size > 0) {
+                    vCard = vCardList[0]
+                }
+            }
+        } catch (e: IOException) {
+            logger.d(TAG, "${e.message}")
+        }
+        return vCard
+    }
+}

+ 0 - 132
src/main/java/com/owncloud/android/jobs/ContactsImportJob.java

@@ -1,132 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2017 Tobias Kaminsky
- * Copyright (C) 2017 Nextcloud GmbH.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero 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 Affero General Public License for more details.
- *
- * 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/>.
- */
-
-package com.owncloud.android.jobs;
-
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-
-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 androidx.annotation.NonNull;
-import ezvcard.Ezvcard;
-import ezvcard.VCard;
-import third_parties.ezvcard_android.ContactOperations;
-
-/**
- * Job to import contacts
- */
-
-public class ContactsImportJob extends Job {
-    public static final String TAG = "ContactsImportJob";
-
-    public static final String ACCOUNT_TYPE = "account_type";
-    public static final String ACCOUNT_NAME = "account_name";
-    public static final String VCARD_FILE_PATH = "vcard_file_path";
-    public static final String CHECKED_ITEMS_ARRAY = "checked_items_array";
-
-    @NonNull
-    @Override
-    protected Result onRunJob(@NonNull Params params) {
-        PersistableBundleCompat bundle = params.getExtras();
-
-        String vCardFilePath = bundle.getString(VCARD_FILE_PATH, "");
-        String accountName = bundle.getString(ACCOUNT_NAME, "");
-        String accountType = bundle.getString(ACCOUNT_TYPE, "");
-        int[] intArray = bundle.getIntArray(CHECKED_ITEMS_ARRAY);
-
-        File file = new File(vCardFilePath);
-        ArrayList<VCard> vCards = new ArrayList<>();
-
-        Cursor cursor = null;
-        try {
-            ContactOperations operations = new ContactOperations(getContext(), accountName, accountType);
-            vCards.addAll(Ezvcard.parse(file).all());
-            Collections.sort(vCards, new ContactListFragment.VCardComparator());
-            cursor = getContext().getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null,
-                    null, null, null);
-
-            TreeMap<VCard, Long> ownContactMap = 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) {
-                        ownContactMap.put(vCard, cursor.getLong(cursor.getColumnIndex("NAME_RAW_CONTACT_ID")));
-                    }
-                    cursor.moveToNext();
-                }
-            }
-
-
-            for (int checkedItem : intArray) {
-                VCard vCard = vCards.get(checkedItem);
-                if (ContactListFragment.getDisplayName(vCard).length() != 0) {
-                    if (!ownContactMap.containsKey(vCard)) {
-                        operations.insertContact(vCard);
-                    } else {
-                        operations.updateContact(vCard, ownContactMap.get(vCard));
-                    }
-                } else {
-                    operations.insertContact(vCard); //Insert All the contacts without name
-                }
-            }
-        } catch (Exception e) {
-            Log_OC.e(TAG, e.getMessage());
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-
-        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;
-    }
-
-
-}

+ 0 - 2
src/main/java/com/owncloud/android/jobs/NCJobCreator.java

@@ -81,8 +81,6 @@ public class NCJobCreator implements JobCreator {
     @Override
     public Job create(@NonNull String tag) {
         switch (tag) {
-            case ContactsImportJob.TAG:
-                return new ContactsImportJob();
             case AccountRemovalJob.TAG:
                 return new AccountRemovalJob(uploadsStorageManager,
                                              accountManager,

+ 20 - 30
src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java

@@ -55,17 +55,15 @@ import android.widget.Toast;
 
 import com.bumptech.glide.request.animation.GlideAnimation;
 import com.bumptech.glide.request.target.SimpleTarget;
-import com.evernote.android.job.JobRequest;
-import com.evernote.android.job.util.support.PersistableBundleCompat;
 import com.google.android.material.snackbar.Snackbar;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.di.Injectable;
+import com.nextcloud.client.jobs.BackgroundJobManager;
 import com.nextcloud.client.network.ClientFactory;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileDownloader;
-import com.owncloud.android.jobs.ContactsImportJob;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.ui.TextDrawable;
 import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
@@ -149,6 +147,7 @@ public class ContactListFragment extends FileFragment implements Injectable {
     private OCFile ocFile;
     @Inject UserAccountManager accountManager;
     @Inject ClientFactory clientFactory;
+    @Inject BackgroundJobManager backgroundJobManager;
 
     public static ContactListFragment newInstance(OCFile file, Account account) {
         ContactListFragment frag = new ContactListFragment();
@@ -360,20 +359,11 @@ public class ContactListFragment extends FileFragment implements Injectable {
         }
     }
 
-    private void importContacts(ContactAccount account) {
-        PersistableBundleCompat bundle = new PersistableBundleCompat();
-        bundle.putString(ContactsImportJob.ACCOUNT_NAME, account.name);
-        bundle.putString(ContactsImportJob.ACCOUNT_TYPE, account.type);
-        bundle.putString(ContactsImportJob.VCARD_FILE_PATH, getFile().getStoragePath());
-        bundle.putIntArray(ContactsImportJob.CHECKED_ITEMS_ARRAY, contactListAdapter.getCheckedIntArray());
-
-        new JobRequest.Builder(ContactsImportJob.TAG)
-                .setExtras(bundle)
-                .startNow()
-                .setRequiresCharging(false)
-                .setUpdateCurrent(false)
-                .build()
-                .schedule();
+    private void importContacts(ContactsAccount account) {
+        backgroundJobManager.startImmediateContactsImport(account.name,
+                                                          account.type,
+                                                          getFile().getStoragePath(),
+                                                          contactListAdapter.getCheckedIntArray());
 
         Snackbar.make(recyclerView, R.string.contacts_preferences_import_scheduled, Snackbar.LENGTH_LONG).show();
 
@@ -391,10 +381,10 @@ public class ContactListFragment extends FileFragment implements Injectable {
     }
 
     private void getAccountForImport() {
-        final ArrayList<ContactAccount> accounts = new ArrayList<>();
+        final ArrayList<ContactsAccount> contactsAccounts = new ArrayList<>();
 
         // add local one
-        accounts.add(new ContactAccount("Local contacts", null, null));
+        contactsAccounts.add(new ContactsAccount("Local contacts", null, null));
 
         Cursor cursor = null;
         try {
@@ -409,10 +399,10 @@ public class ContactListFragment extends FileFragment implements Injectable {
                     String name = cursor.getString(cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_NAME));
                     String type = cursor.getString(cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE));
 
-                    ContactAccount account = new ContactAccount(name, name, type);
+                    ContactsAccount account = new ContactsAccount(name, name, type);
 
-                    if (!accounts.contains(account)) {
-                        accounts.add(account);
+                    if (!contactsAccounts.contains(account)) {
+                        contactsAccounts.add(account);
                     }
                 }
 
@@ -426,16 +416,16 @@ public class ContactListFragment extends FileFragment implements Injectable {
             }
         }
 
-        if (accounts.size() == SINGLE_ACCOUNT) {
-            importContacts(accounts.get(0));
+        if (contactsAccounts.size() == SINGLE_ACCOUNT) {
+            importContacts(contactsAccounts.get(0));
         } else {
-            ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, accounts);
+            ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, contactsAccounts);
             AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
             builder.setTitle(R.string.contactlist_account_chooser_title)
                     .setAdapter(adapter, new DialogInterface.OnClickListener() {
                         @Override
                         public void onClick(DialogInterface dialog, int which) {
-                            importContacts(accounts.get(which));
+                            importContacts(contactsAccounts.get(which));
                         }
                     }).show();
         }
@@ -475,12 +465,12 @@ public class ContactListFragment extends FileFragment implements Injectable {
         }
     }
 
-    private class ContactAccount {
+    private class ContactsAccount {
         private String displayName;
         private String name;
         private String type;
 
-        ContactAccount(String displayName, String name, String type) {
+        ContactsAccount(String displayName, String name, String type) {
             this.displayName = displayName;
             this.name = name;
             this.type = type;
@@ -488,8 +478,8 @@ public class ContactListFragment extends FileFragment implements Injectable {
 
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof ContactAccount) {
-                ContactAccount other = (ContactAccount) obj;
+            if (obj instanceof ContactsAccount) {
+                ContactsAccount other = (ContactsAccount) obj;
                 return this.name.equalsIgnoreCase(other.name) && this.type.equalsIgnoreCase(other.type);
             } else {
                 return false;