Add fields to AccountType for the new "invite" feature

- Add two attributes, inviteContactActivity and inviteContactActionLabel,
to the contacts.xml file, in addition to the existing ones such as
editContactActivity.

e.g.
<ContactsAccountType
    xmlns:android="http://schemas.android.com/apk/res/android"
    inviteContactActivity="com....editor.InviteContactActivity"
    inviteContactActionLabel="@string/invite_action_label">

- Add two methods to AccountType to obtain the new attributes.
(getInviteContactActionLabel() will return a localized string)

- Add AccountTypeManager.getInvitableAccountTypes() which returns
AccountType's which define the invite activity and have one or more account.

Change-Id: I545b47a7e4957765684314870739085ea0355a98
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 563658a..70aa430 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -18,6 +18,7 @@
 
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
+import com.google.common.annotations.VisibleForTesting;
 
 import android.accounts.Account;
 import android.content.ContentValues;
@@ -45,6 +46,8 @@
  * In the future this may be inflated from XML defined by a data source.
  */
 public abstract class AccountType {
+    private static final String TAG = "AccountType";
+
     /**
      * The {@link RawContacts#ACCOUNT_TYPE} these constraints apply to.
      */
@@ -92,14 +95,51 @@
         return null;
     }
 
+    /**
+     * Returns an optional custom invite contact activity. The activity class should reside
+     * in the sync adapter package as determined by {@link #resPackageName}.
+     */
+    public String getInviteContactActivityClassName() {
+        return null;
+    }
+
     public CharSequence getDisplayLabel(Context context) {
-        if (this.titleRes != -1 && this.summaryResPackageName != null) {
+        return getResourceText(context, summaryResPackageName, titleRes, accountType);
+    }
+
+    /**
+     * @return resource ID for the "invite contact" action label, or -1 if not defined.
+     */
+    protected int getInviteContactActionResId(Context conext) {
+        return -1;
+    }
+
+    /**
+     * Returns an optional custom label for the "invite contact" action, which will be shown on
+     * the contact card.  (If not defined, returns null.)
+     */
+    public CharSequence getInviteContactActionLabel(Context context) {
+        return getResourceText(context, summaryResPackageName, getInviteContactActionResId(context),
+                null);
+    }
+
+    /**
+     * Return a string resource loaded from the given package (or the current package
+     * if {@code packageName} is null), unless {@code resId} is -1, in which case it returns
+     * {@code defaultValue}.
+     *
+     * (The behavior is undefined if the resource or package doesn't exist.)
+     */
+    @VisibleForTesting
+    static CharSequence getResourceText(Context context, String packageName, int resId,
+            String defaultValue) {
+        if (resId != -1 && packageName != null) {
             final PackageManager pm = context.getPackageManager();
-            return pm.getText(this.summaryResPackageName, this.titleRes, null);
-        } else if (this.titleRes != -1) {
-            return context.getText(this.titleRes);
+            return pm.getText(packageName, resId, null);
+        } else if (resId != -1) {
+            return context.getText(resId);
         } else {
-            return this.accountType;
+            return defaultValue;
         }
     }
 
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index 58195a1..ea2568f 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -18,6 +18,7 @@
 
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.i18n.phonenumbers.PhoneNumberUtil;
 
 import android.accounts.Account;
@@ -39,13 +40,16 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.ContactsContract;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -80,6 +84,12 @@
     public abstract AccountType getAccountType(String accountType);
 
     /**
+     * @return Unmodifiable map from account type strings to {@link AccountType}s which support
+     * the "invite" feature and have one or more account.
+     */
+    public abstract Map<String, AccountType> getInvitableAccountTypes();
+
+    /**
      * Find the best {@link DataKind} matching the requested
      * {@link AccountType#accountType} and {@link DataKind#mimeType}. If no
      * direct match found, we try searching {@link FallbackAccountType}.
@@ -101,6 +111,8 @@
     private ArrayList<Account> mAccounts = Lists.newArrayList();
     private ArrayList<Account> mWritableAccounts = Lists.newArrayList();
     private HashMap<String, AccountType> mAccountTypes = Maps.newHashMap();
+    private Map<String, AccountType> mInvitableAccountTypes = Collections.unmodifiableMap(
+            new HashMap<String, AccountType>());
 
     private static final int MESSAGE_LOAD_DATA = 0;
     private static final int MESSAGE_PROCESS_BROADCAST_INTENT = 1;
@@ -308,6 +320,7 @@
             mAccountTypes = accountTypes;
             mAccounts = allAccounts;
             mWritableAccounts = writableAccounts;
+            mInvitableAccountTypes = findInvitableAccountTypes(mContext, allAccounts, accountTypes);
         }
 
         Log.i(TAG, "Loaded meta-data for " + mAccountTypes.size() + " account types, "
@@ -381,4 +394,34 @@
             return type != null ? type : mFallbackAccountType;
         }
     }
+
+    @Override
+    public Map<String, AccountType> getInvitableAccountTypes() {
+        return mInvitableAccountTypes;
+    }
+
+    /**
+     * Return all {@link AccountType}s with at least one account which supports "invite", i.e.
+     * its {@link AccountType#getInviteContactActivityClassName()} is not empty.
+     */
+    @VisibleForTesting
+    static Map<String, AccountType> findInvitableAccountTypes(Context context,
+            Collection<Account> accounts, Map<String, AccountType> accountTypes) {
+        HashMap<String, AccountType> result = Maps.newHashMap();
+        for (Account account : accounts) {
+            AccountType type = accountTypes.get(account.type);
+            if (type == null) continue; // just in case
+            if (result.containsKey(type.accountType)) continue;
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Type " + type.accountType
+                        + " inviteClass=" + type.getInviteContactActivityClassName()
+                        + " inviteAction=" + type.getInviteContactActionLabel(context));
+            }
+            if (!TextUtils.isEmpty(type.getInviteContactActivityClassName())) {
+                result.put(type.accountType, type);
+            }
+        }
+        return Collections.unmodifiableMap(result);
+    }
 }
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index 9ce6fd9..4e6add0 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -16,16 +16,21 @@
 
 package com.android.contacts.model;
 
+import com.google.common.annotations.VisibleForTesting;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
@@ -48,9 +53,14 @@
 
     private static final String ATTR_EDIT_CONTACT_ACTIVITY = "editContactActivity";
     private static final String ATTR_CREATE_CONTACT_ACTIVITY = "createContactActivity";
+    private static final String ATTR_INVITE_CONTACT_ACTIVITY = "inviteContactActivity";
+    private static final String ATTR_INVITE_CONTACT_ACTION_LABEL = "inviteContactActionLabel";
 
     private String mEditContactActivityClassName;
     private String mCreateContactActivityClassName;
+    private String mInviteContactActivity;
+    private String mInviteActionLabelAttribute;
+    private int mInviteActionLabelResId;
 
     public ExternalAccountType(Context context, String resPackageName) {
         this.resPackageName = resPackageName;
@@ -71,6 +81,9 @@
             }
         }
 
+        mInviteActionLabelResId = resolveExternalResId(context, mInviteActionLabelAttribute,
+                summaryResPackageName, ATTR_INVITE_CONTACT_ACTION_LABEL);
+
         // Bring in name and photo from fallback source, which are non-optional
         addDataKindStructuredName(context);
         addDataKindDisplayName(context);
@@ -93,6 +106,16 @@
         return mCreateContactActivityClassName;
     }
 
+    @Override
+    public String getInviteContactActivityClassName() {
+        return mInviteContactActivity;
+    }
+
+    @Override
+    protected int getInviteContactActionResId(Context context) {
+        return mInviteActionLabelResId;
+    }
+
     /**
      * Inflate this {@link AccountType} from the given parser. This may only
      * load details matching the publicly-defined schema.
@@ -121,10 +144,18 @@
             int attributeCount = parser.getAttributeCount();
             for (int i = 0; i < attributeCount; i++) {
                 String attr = parser.getAttributeName(i);
+                String value = parser.getAttributeValue(i);
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, attr + "=" + value);
+                }
                 if (ATTR_EDIT_CONTACT_ACTIVITY.equals(attr)) {
-                    mEditContactActivityClassName = parser.getAttributeValue(i);
+                    mEditContactActivityClassName = value;
                 } else if (ATTR_CREATE_CONTACT_ACTIVITY.equals(attr)) {
-                    mCreateContactActivityClassName = parser.getAttributeValue(i);
+                    mCreateContactActivityClassName = value;
+                } else if (ATTR_INVITE_CONTACT_ACTIVITY.equals(attr)) {
+                    mInviteContactActivity = value;
+                } else if (ATTR_INVITE_CONTACT_ACTION_LABEL.equals(attr)) {
+                    mInviteActionLabelAttribute = value;
                 } else {
                     Log.e(TAG, "Unsupported attribute " + attr);
                 }
@@ -172,6 +203,7 @@
                 }
 
                 addKind(kind);
+                a.recycle();
             }
         } catch (XmlPullParserException e) {
             throw new IllegalStateException("Problem reading XML", e);
@@ -189,4 +221,41 @@
     public int getSideBarColor(Context context) {
         return 0xff6d86b4;
     }
+
+    /**
+     * Takes a string in the "@xxx/yyy" format and return the resource ID for the resource in
+     * the resource package.
+     *
+     * If the argument is in the invalid format or isn't a resource name, it returns -1.
+     *
+     * @param context context
+     * @param resourceName Resource name in the "@xxx/yyy" format, e.g. "@string/invite_lavbel"
+     * @param packageName name of the package containing the resource.
+     * @param xmlAttributeName attribute name which the resource came from.  Used for logging.
+     */
+    @VisibleForTesting
+    static int resolveExternalResId(Context context, String resourceName,
+            String packageName, String xmlAttributeName) {
+        if (TextUtils.isEmpty(resourceName)) {
+            return -1; // Empty text is okay.
+        }
+        if (resourceName.charAt(0) != '@') {
+            Log.e(TAG, xmlAttributeName + " must be a resource name beginnig with '@'");
+            return -1;
+        }
+        final String name = resourceName.substring(1);
+        final Resources res;
+        try {
+             res = context.getPackageManager().getResourcesForApplication(packageName);
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Unable to load package " + packageName);
+            return -1;
+        }
+        final int resId = res.getIdentifier(name, null, packageName);
+        if (resId == 0) {
+            Log.e(TAG, "Unable to load " + resourceName + " from package " + packageName);
+            return -1;
+        }
+        return resId;
+    }
 }
diff --git a/tests/res/values/donottranslate_strings.xml b/tests/res/values/donottranslate_strings.xml
index 9f9f5a4..df74655 100644
--- a/tests/res/values/donottranslate_strings.xml
+++ b/tests/res/values/donottranslate_strings.xml
@@ -17,6 +17,7 @@
 
     <string name="contactsIntents">Contacts Intents</string>
     <string name="result">Result returned by activity</string>
+    <string name="test_string">TEST STRING</string>
 
     <string-array name="allIntents">
         <!-- List modes -->
diff --git a/tests/src/com/android/contacts/model/AccountTypeManagerTest.java b/tests/src/com/android/contacts/model/AccountTypeManagerTest.java
new file mode 100644
index 0000000..e5cf080
--- /dev/null
+++ b/tests/src/com/android/contacts/model/AccountTypeManagerTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.model;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Test case for {@link AccountTypeManager}.
+ *
+ * adb shell am instrument -w -e class com.android.contacts.model.AccountTypeManagerTest \
+       com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+public class AccountTypeManagerTest extends AndroidTestCase {
+    public void testFindInvitableAccountTypes() {
+        final Context c = getContext();
+
+        // Define account types.
+        final AccountType typeA = new MockAccountType("typeA", null);
+        final AccountType typeB = new MockAccountType("typeB", null);
+        final AccountType typeC = new MockAccountType("typeC", "c");
+        final AccountType typeD = new MockAccountType("typeD", "d");
+
+        // Define users
+        final Account accountA1 = new Account("a1", typeA.accountType);
+        final Account accountC1 = new Account("c1", typeC.accountType);
+        final Account accountC2 = new Account("c2", typeC.accountType);
+        final Account accountD1 = new Account("d1", typeD.accountType);
+
+        // empty - empty
+        Map<String, AccountType> types = AccountTypeManagerImpl.findInvitableAccountTypes(c,
+                buildAccounts(), buildAccountTypes());
+        assertEquals(0, types.size());
+        try {
+            types.clear();
+            fail("Returned Map should be unmodifiable.");
+        } catch (UnsupportedOperationException ok) {
+        }
+
+        // No invite support, no accounts
+        verifyAccountTypes(
+                buildAccounts(),
+                buildAccountTypes(typeA)
+                /* empty */
+                );
+
+        // No invite support, with accounts
+        verifyAccountTypes(
+                buildAccounts(accountA1),
+                buildAccountTypes(typeA)
+                /* empty */
+                );
+
+        // With invite support, no accounts
+        verifyAccountTypes(
+                buildAccounts(),
+                buildAccountTypes(typeC)
+                /* empty */
+                );
+
+        // With invite support, 1 account
+        verifyAccountTypes(
+                buildAccounts(accountC1),
+                buildAccountTypes(typeC),
+                typeC
+                );
+
+        // With invite support, 2 account
+        verifyAccountTypes(
+                buildAccounts(accountC1, accountC2),
+                buildAccountTypes(typeC),
+                typeC
+                );
+
+        // Combinations...
+        verifyAccountTypes(
+                buildAccounts(accountA1),
+                buildAccountTypes(typeA, typeC)
+                /* empty */
+                );
+
+        verifyAccountTypes(
+                buildAccounts(accountC1, accountA1),
+                buildAccountTypes(typeA, typeC),
+                typeC
+                );
+
+        verifyAccountTypes(
+                buildAccounts(accountC1, accountA1),
+                buildAccountTypes(typeD, typeA, typeC),
+                typeC
+                );
+
+        verifyAccountTypes(
+                buildAccounts(accountC1, accountA1, accountD1),
+                buildAccountTypes(typeD, typeA, typeC),
+                typeC, typeD
+                );
+    }
+
+    /**
+     * Array of {@link AccountType} -> {@link Map}
+     */
+    private static Map<String, AccountType> buildAccountTypes(AccountType... types) {
+        final HashMap<String, AccountType> result = Maps.newHashMap();
+        for (AccountType type : types) {
+            result.put(type.accountType, type);
+        }
+        return result;
+    }
+
+    /**
+     * Array of {@link Account} -> {@link Collection}
+     */
+    private static Collection<Account> buildAccounts(Account... accounts) {
+        final ArrayList<Account> result = Lists.newArrayList();
+        for (Account account : accounts) {
+            result.add(account);
+        }
+        return result;
+    }
+
+    /**
+     * Executes {@link AccountTypeManagerImpl#findInvitableAccountTypes} and verifies the
+     * result.
+     */
+    private void verifyAccountTypes(Collection<Account> accounts,
+            Map<String, AccountType> types, AccountType... expectedTypes) {
+        Map<String, AccountType> result = AccountTypeManagerImpl.findInvitableAccountTypes(
+                getContext(), accounts, types);
+        for (AccountType type : expectedTypes) {
+            if (!result.containsKey(type.accountType)) {
+                fail("Result doesn't contain type=" + type.accountType);
+            }
+        }
+    }
+
+    private static class MockAccountType extends AccountType {
+        private final String mInviteContactActivityClassName;
+
+        public MockAccountType(String type, String inviteContactActivityClassName) {
+            accountType = type;
+            mInviteContactActivityClassName = inviteContactActivityClassName;
+        }
+
+        @Override
+        public String getInviteContactActivityClassName() {
+            return mInviteContactActivityClassName;
+        }
+
+        @Override
+        public int getHeaderColor(Context context) {
+            return 0;
+        }
+
+        @Override
+        public int getSideBarColor(Context context) {
+            return 0;
+        }
+
+        @Override
+        public boolean isGroupMembershipEditable() {
+            return false;
+        }
+    }
+}
diff --git a/tests/src/com/android/contacts/model/AccountTypeTest.java b/tests/src/com/android/contacts/model/AccountTypeTest.java
new file mode 100644
index 0000000..4898cf3
--- /dev/null
+++ b/tests/src/com/android/contacts/model/AccountTypeTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.model;
+
+import com.android.contacts.tests.R;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+/**
+ * Test case for {@link AccountType}.
+ *
+ * adb shell am instrument -w -e class com.android.contacts.model.AccountTypeTest \
+       com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+public class AccountTypeTest extends AndroidTestCase {
+    public void testGetResourceText() {
+        // In this test we use the test package itself as an external package.
+        final String packageName = getTestContext().getPackageName();
+
+        final Context c = getContext();
+        final String DEFAULT = "ABC";
+
+        // Package name null, resId -1, use the default
+        assertEquals(DEFAULT, AccountType.getResourceText(c, null, -1, DEFAULT));
+
+        // Resource ID -1, use the default
+        assertEquals(DEFAULT, AccountType.getResourceText(c, packageName, -1, DEFAULT));
+
+        // Load from an external package.  (here, we use this test package itself)
+        final int externalResID = R.string.test_string;
+        assertEquals(getTestContext().getString(externalResID),
+                AccountType.getResourceText(c, packageName, externalResID, DEFAULT));
+
+        // Load from the contacts package itself.
+        final int internalResId = com.android.contacts.R.string.sharedUserLabel;
+        assertEquals(c.getString(internalResId),
+                AccountType.getResourceText(c, null, internalResId, DEFAULT));
+    }
+
+    /**
+     * Verify if {@link AccountType#getInviteContactActionLabel} correctly gets the resource ID
+     * from {@link AccountType#getInviteContactActionResId}
+     */
+    public void testGetInviteContactActionLabel() {
+        final String packageName = getTestContext().getPackageName();
+        final Context c = getContext();
+
+        final int externalResID = R.string.test_string;
+
+        AccountType accountType = new AccountType() {
+            {
+                resPackageName = packageName;
+                summaryResPackageName = packageName;
+            }
+            @Override protected int getInviteContactActionResId(Context conext) {
+                return externalResID;
+            }
+
+            @Override public int getHeaderColor(Context context) {
+                return 0;
+            }
+
+            @Override public int getSideBarColor(Context context) {
+                return 0;
+            }
+
+            @Override public boolean isGroupMembershipEditable() {
+                return false;
+            }
+        };
+
+        assertEquals(getTestContext().getString(externalResID),
+                accountType.getInviteContactActionLabel(c));
+    }
+}
diff --git a/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java b/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java
new file mode 100644
index 0000000..e7ef496
--- /dev/null
+++ b/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.model;
+
+import com.android.contacts.tests.R;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+/**
+ * Test case for {@link ExternalAccountType}.
+ *
+ * adb shell am instrument -w -e class com.android.contacts.model.ExternalAccountTypeTest \
+       com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+public class ExternalAccountTypeTest extends AndroidTestCase {
+
+    public void testResolveExternalResId() {
+        final Context c = getContext();
+        // In this test we use the test package itself as an external package.
+        final String packageName = getTestContext().getPackageName();
+
+        // Resource name empty.
+        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, null, packageName, ""));
+        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, "", packageName, ""));
+
+        // Name doesn't begin with '@'
+        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, "x", packageName, ""));
+
+        // Invalid resource name
+        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, "@", packageName, ""));
+        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, "@a", packageName, ""));
+        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, "@a/b", packageName, ""));
+
+        // Valid resource name
+        assertEquals(R.string.test_string, ExternalAccountType.resolveExternalResId(c,
+                "@string/test_string", packageName, ""));
+    }
+}
diff --git a/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java b/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
index 2635a09..acfa7f5 100644
--- a/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
+++ b/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
@@ -17,11 +17,14 @@
 
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.google.android.collect.Maps;
 
 import android.accounts.Account;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * A mock {@link AccountTypeManager} class.
@@ -50,4 +53,9 @@
     public ArrayList<Account> getAccounts(boolean writableOnly) {
         return new ArrayList<Account>(Arrays.asList(mAccounts));
     }
+
+    @Override
+    public Map<String, AccountType> getInvitableAccountTypes() {
+        return Maps.newHashMap(); // Always returns empty
+    }
 }