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 Original line Diff line number Diff line
@@ -1490,7 +1490,8 @@ public class PeopleActivity extends ContactsDrawerActivity implements


        if (getSupportActionBar() != null) {
        if (getSupportActionBar() != null) {
            String actionBarTitle;
            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);
                actionBarTitle = getString(R.string.account_phone);
            } else if (!TextUtils.isEmpty(filter.accountName)) {
            } else if (!TextUtils.isEmpty(filter.accountName)) {
                actionBarTitle = getActionBarTitleForAccount(filter);
                actionBarTitle = getActionBarTitleForAccount(filter);
+7 −3
Original line number Original line Diff line number Diff line
@@ -93,6 +93,12 @@ public final class ContactListFilter implements Comparable<ContactListFilter>, P
                /* accountType= */ null, /* accountName= */ null, /* dataSet= */ null, icon);
                /* 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
     * Whether the given {@link ContactListFilter} has a filter type that should be displayed as
     * the default contacts list view.
     * the default contacts list view.
@@ -333,10 +339,8 @@ public final class ContactListFilter implements Comparable<ContactListFilter>, P
    }
    }


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


@@ -39,6 +39,9 @@ import java.util.Set;
 */
 */
public class DeviceLocalAccountLocator {
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
    @VisibleForTesting
    static String[] PROJECTION = new String[] {
    static String[] PROJECTION = new String[] {
            ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE,
            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_TYPE = 1;
    private static final int COL_DATA_SET = 2;
    private static final int COL_DATA_SET = 2;



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


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


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

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


    public List<AccountWithDataSet> getDeviceLocalAccounts() {
    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<>();
        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 {
        try {
            addAccountsFromCursor(cursor, accounts);
        } finally {
            cursor.close();
        }
    }

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


            if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
            if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
                    mAccountTypeFactory, type)) {
                    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
    @VisibleForTesting
    public String getSelection() {
    public String[] getSelectionArgs() {
        final StringBuilder sb = new StringBuilder();
        return mSelectionArgs;
        sb.append(ContactsContract.RawContacts.DELETED).append(" =0 AND (")
    }

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

        return sb.toString();
        return sb.toString();
    }
    }


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

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


        final Cursor c = context.getContentResolver().query(RAW_CONTACTS_URI_LIMIT_1,
        final Cursor c = context.getContentResolver().query(RAW_CONTACTS_URI_LIMIT_1,
                ID_PROJECTION, selection, args, null);
                ID_PROJECTION, selection, args, null);
+15 −6
Original line number Original line Diff line number Diff line
@@ -22,7 +22,9 @@ import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.text.TextUtils;
import android.util.Log;
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.ContactListFilter;
import com.android.contacts.common.list.ContactListFilterController;
import com.android.contacts.common.list.ContactListFilterController;
import com.android.contacts.common.model.AccountTypeManager;
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.AccountType;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contactsbind.ObjectFactory;
import com.google.common.collect.Lists;
import com.google.common.collect.Lists;


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


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


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


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