Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc510849 authored by Marcus Hagerott's avatar Marcus Hagerott
Browse files

Detect empty device accounts.

This queries groups and settings to find device accounts in the case that
there are no raw_contacts associated with an account

Test:
Manual test
* Remove all device contacts from LG G5
* relaunch app
* verify that "Device" doesn't show in nav drawer
* press FAB
* verify that "Device" does show in account picker in the editor
* add the contact to the device account
* verify that "Device" now shows in the nav drawer

Bug 30867780
Change-Id: I12bba7a1b5a5f37048517264cb82a599197f6d05
parent 1c6298b6
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -1490,7 +1490,8 @@ public class PeopleActivity extends ContactsDrawerActivity implements

        if (getSupportActionBar() != null) {
            String actionBarTitle;
            if (filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
            if (filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS &&
                    filter.accountName == null) {
                actionBarTitle = getString(R.string.account_phone);
            } else if (!TextUtils.isEmpty(filter.accountName)) {
                actionBarTitle = getActionBarTitleForAccount(filter);
+7 −3
Original line number Diff line number Diff line
@@ -93,6 +93,12 @@ public final class ContactListFilter implements Comparable<ContactListFilter>, P
                /* accountType= */ null, /* accountName= */ null, /* dataSet= */ null, icon);
    }

    public static ContactListFilter createDeviceContactsFilter(Drawable icon,
            AccountWithDataSet account) {
        return new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS,
                account.type, account.name, account.dataSet, icon);
    }

    /**
     * Whether the given {@link ContactListFilter} has a filter type that should be displayed as
     * the default contacts list view.
@@ -333,10 +339,8 @@ public final class ContactListFilter implements Comparable<ContactListFilter>, P
    }

    public AccountWithDataSet toAccountWithDataSet() {
        if (filterType == FILTER_TYPE_ACCOUNT) {
        if (filterType == FILTER_TYPE_ACCOUNT || filterType == FILTER_TYPE_DEVICE_CONTACTS) {
            return new AccountWithDataSet(accountName, accountType, dataSet);
        } else if (filterType == FILTER_TYPE_DEVICE_CONTACTS) {
            return AccountWithDataSet.getLocalAccount();
        } else {
            throw new IllegalStateException("Cannot create Account from filter type " +
                    filterTypeToString(filterType));
+64 −29
Original line number Diff line number Diff line
@@ -18,10 +18,10 @@ package com.android.contacts.common.model;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.annotation.VisibleForTesting;

import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;

@@ -39,6 +39,9 @@ import java.util.Set;
 */
public class DeviceLocalAccountLocator {

    // Note this class is assuming ACCOUNT_NAME and ACCOUNT_TYPE have same values in
    // RawContacts, Groups, and Settings. This assumption simplifies the code somewhat and it
    // is true right now and unlikely to ever change.
    @VisibleForTesting
    static String[] PROJECTION = new String[] {
            ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE,
@@ -49,30 +52,59 @@ public class DeviceLocalAccountLocator {
    private static final int COL_TYPE = 1;
    private static final int COL_DATA_SET = 2;


    private final ContentResolver mResolver;
    private final DeviceLocalAccountTypeFactory mAccountTypeFactory;
    private final Set<String> mKnownAccountTypes;

    private final String mSelection;
    private final String[] mSelectionArgs;

    public DeviceLocalAccountLocator(ContentResolver contentResolver,
            DeviceLocalAccountTypeFactory factory,
            List<AccountWithDataSet> knownAccounts) {
        mResolver = contentResolver;
        mAccountTypeFactory = factory;
        mKnownAccountTypes = new HashSet<>();

        final Set<String> knownAccountTypes = new HashSet<>();
        for (AccountWithDataSet account : knownAccounts) {
            mKnownAccountTypes.add(account.type);
            knownAccountTypes.add(account.type);
        }
        mSelection = getSelection(knownAccountTypes);
        mSelectionArgs = getSelectionArgs(knownAccountTypes);
    }

    public List<AccountWithDataSet> getDeviceLocalAccounts() {
        final String[] selectionArgs = getSelectionArgs();
        final Cursor cursor = mResolver.query(ContactsContract.RawContacts.CONTENT_URI, PROJECTION,
                getSelection(), selectionArgs, null);

        final Set<AccountWithDataSet> localAccounts = new HashSet<>();

        // Many device accounts have default groups associated with them.
        addAccountsFromQuery(ContactsContract.Groups.CONTENT_URI, localAccounts);

        addAccountsFromQuery(ContactsContract.Settings.CONTENT_URI, localAccounts);

        if (localAccounts.isEmpty()) {
            // It's probably safe to assume that if one of the earlier queries found a "device"
            // account then this query isn't going to find any different device accounts.
            // We skip this query because it probably is kind of expensive (relative to the other
            // queries).
            addAccountsFromQuery(ContactsContract.RawContacts.CONTENT_URI, localAccounts);
        }

        return new ArrayList<>(localAccounts);
    }

    private void addAccountsFromQuery(Uri uri, Set<AccountWithDataSet> accounts) {
        final Cursor cursor = mResolver.query(uri, PROJECTION, mSelection, mSelectionArgs, null);

        if (cursor == null) return;

        try {
            addAccountsFromCursor(cursor, accounts);
        } finally {
            cursor.close();
        }
    }

    private void addAccountsFromCursor(Cursor cursor, Set<AccountWithDataSet> accounts) {
        while (cursor.moveToNext()) {
            final String name = cursor.getString(COL_NAME);
            final String type = cursor.getString(COL_TYPE);
@@ -80,36 +112,39 @@ public class DeviceLocalAccountLocator {

            if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
                    mAccountTypeFactory, type)) {
                    localAccounts.add(new AccountWithDataSet(name, type, dataSet));
                accounts.add(new AccountWithDataSet(name, type, dataSet));
            }
        }
        } finally {
            cursor.close();
    }

        return new ArrayList<>(localAccounts);
    @VisibleForTesting
    public String getSelection() {
        return mSelection;
    }

    @VisibleForTesting
    public String getSelection() {
        final StringBuilder sb = new StringBuilder();
        sb.append(ContactsContract.RawContacts.DELETED).append(" =0 AND (")
    public String[] getSelectionArgs() {
        return mSelectionArgs;
    }

    private static String getSelection(Set<String> knownAccountTypes) {
        final StringBuilder sb = new StringBuilder()
                .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
        if (mKnownAccountTypes.isEmpty()) {
            return sb.append(')').toString();
        if (knownAccountTypes.isEmpty()) {
            return sb.toString();
        }
        sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
        for (String ignored : mKnownAccountTypes) {
        for (String ignored : knownAccountTypes) {
            sb.append("?,");
        }
        // Remove trailing ','
        sb.deleteCharAt(sb.length() - 1).append(')').append(')');

        sb.deleteCharAt(sb.length() - 1).append(')');
        return sb.toString();
    }

    @VisibleForTesting
    public String[] getSelectionArgs() {
        return mKnownAccountTypes.toArray(new String[mKnownAccountTypes.size()]);
    private static String[] getSelectionArgs(Set<String> knownAccountTypes) {
        if (knownAccountTypes.isEmpty()) return null;

        return knownAccountTypes.toArray(new String[knownAccountTypes.size()]);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -139,6 +139,7 @@ public class AccountWithDataSet implements Parcelable {
                args = new String[] {type, name, dataSet};
            }
        }
        selection += " AND " + RawContacts.DELETED + "=0";

        final Cursor c = context.getContentResolver().query(RAW_CONTACTS_URI_LIMIT_1,
                ID_PROJECTION, selection, args, null);
+15 −6
Original line number Diff line number Diff line
@@ -22,7 +22,9 @@ import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;

@@ -31,8 +33,10 @@ import com.android.contacts.common.list.AccountFilterActivity;
import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.common.list.ContactListFilterController;
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.model.RawContact;
import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contactsbind.ObjectFactory;
import com.google.common.collect.Lists;

import java.util.ArrayList;
@@ -90,15 +94,17 @@ public class AccountFilterUtil {
     */
    public static class FilterLoader extends AsyncTaskLoader<List<ContactListFilter>> {
        private Context mContext;
        private DeviceLocalAccountTypeFactory mDeviceLocalFactory;

        public FilterLoader(Context context) {
            super(context);
            mContext = context;
            mDeviceLocalFactory = ObjectFactory.getDeviceLocalAccountTypeFactory(context);
        }

        @Override
        public List<ContactListFilter> loadInBackground() {
            return loadAccountFilters(mContext);
            return loadAccountFilters(mContext, mDeviceLocalFactory);
        }

        @Override
@@ -117,7 +123,8 @@ public class AccountFilterUtil {
        }
    }

    private static List<ContactListFilter> loadAccountFilters(Context context) {
    private static List<ContactListFilter> loadAccountFilters(Context context,
            DeviceLocalAccountTypeFactory deviceAccountTypeFactory) {
        final ArrayList<ContactListFilter> accountFilters = Lists.newArrayList();
        final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(context);
        accountTypeManager.sortAccounts(/* defaultAccount */ getDefaultAccount(context));
@@ -127,13 +134,15 @@ public class AccountFilterUtil {
        for (AccountWithDataSet account : accounts) {
            final AccountType accountType =
                    accountTypeManager.getAccountType(account.type, account.dataSet);
            if (accountType.isExtension() && !account.hasData(context)) {
                // Hide extensions with no raw_contacts.
            if ((accountType.isExtension() || DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
                    deviceAccountTypeFactory, account.type)) && !account.hasData(context)) {
                // Hide extensions and device accounts with no raw_contacts.
                continue;
            }
            final Drawable icon = accountType != null ? accountType.getDisplayIcon(context) : null;
            if (account.isLocalAccount()) {
                accountFilters.add(ContactListFilter.createDeviceContactsFilter(icon));
            if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
                    deviceAccountTypeFactory, account.type)) {
                accountFilters.add(ContactListFilter.createDeviceContactsFilter(icon, account));
            } else {
                accountFilters.add(ContactListFilter.createAccountFilter(
                        account.type, account.name, account.dataSet, icon));
Loading