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

Commit cbd431df authored by Walter Jang's avatar Walter Jang
Browse files

Move finding primary/writable raw contacts to KindSectionDataList (E16)

This will help us since editing the 1) name and 2) photo, and 3) finding
the primary account used to sort editors so that new fields get added
to the primary account all do something similar to find the
ValuesDelta that changes are written too.

We can also enforce that only the same DataKind/mime-type are added
to a KindSectionDataList and passed to a KindSectionView.

Bug 24509375
Bug 23589603

Change-Id: I2fc603c3cea2fb7f10a96c8592688ccf3deef56a
parent 655ad1a2
Loading
Loading
Loading
Loading
+6 −6
Original line number Diff line number Diff line
@@ -145,7 +145,7 @@ public class CompactKindSectionView extends LinearLayout {
        }
    }

    private List<KindSectionData> mKindSectionDataList;
    private KindSectionDataList mKindSectionDataList;
    private ViewIdGenerator mViewIdGenerator;
    private CompactRawContactsEditorView.Listener mListener;

@@ -225,15 +225,14 @@ public class CompactKindSectionView extends LinearLayout {
     * Empty name editors are never added and at least one structured name editor is always
     * displayed, even if it is empty.
     */
    public void setState(List<KindSectionData> kindSectionDataList,
    public void setState(KindSectionDataList kindSectionDataList,
            ViewIdGenerator viewIdGenerator, CompactRawContactsEditorView.Listener listener) {
        mKindSectionDataList = kindSectionDataList;
        mViewIdGenerator = viewIdGenerator;
        mListener = listener;

        // Set the icon using the first DataKind
        final DataKind dataKind = mKindSectionDataList.isEmpty()
                ? null : mKindSectionDataList.get(0).getDataKind();
        final DataKind dataKind = mKindSectionDataList.getDataKind();
        if (dataKind != null) {
            mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(),
                    dataKind.mimeType));
@@ -379,9 +378,10 @@ public class CompactKindSectionView extends LinearLayout {
     * then the entire section is hidden.
     */
    public void updateEmptyEditors(boolean shouldAnimate) {
        final boolean isNameKindSection = mKindSectionDataList.get(0).isNameDataKind();
        final boolean isNameKindSection = StructuredName.CONTENT_ITEM_TYPE.equals(
                mKindSectionDataList.getMimeType());
        final boolean isGroupKindSection = GroupMembership.CONTENT_ITEM_TYPE.equals(
                mKindSectionDataList.get(0).getDataKind().mimeType);
                mKindSectionDataList.getMimeType());

        if (isNameKindSection) {
            // The name kind section is always visible
+37 −154
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.contacts.editor;

import com.android.contacts.R;
import com.android.contacts.common.ContactsUtils;
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.model.RawContactDelta;
import com.android.contacts.common.model.RawContactDeltaList;
@@ -28,7 +27,6 @@ import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.model.dataitem.DataKind;
import com.android.contacts.common.util.AccountsListAdapter;
import com.android.contacts.common.util.MaterialColorMapUtils;
import com.android.contacts.util.ContactPhotoUtils;
import com.android.contacts.util.UiClosables;

import android.content.ContentUris;
@@ -69,7 +67,6 @@ import android.widget.LinearLayout;
import android.widget.ListPopupWindow;
import android.widget.TextView;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -79,7 +76,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

@@ -193,13 +189,13 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O

    /** Used to sort entire kind sections. */
    private static final class KindSectionDataMapEntryComparator implements
            Comparator<Map.Entry<String,List<KindSectionData>>> {
            Comparator<Map.Entry<String,KindSectionDataList>> {

        final MimeTypeComparator mMimeTypeComparator = new MimeTypeComparator();

        @Override
        public int compare(Map.Entry<String, List<KindSectionData>> entry1,
                Map.Entry<String, List<KindSectionData>> entry2) {
        public int compare(Map.Entry<String, KindSectionDataList> entry1,
                Map.Entry<String, KindSectionDataList> entry2) {
            if (entry1 == entry2) return 0;
            if (entry1 == null) return -1;
            if (entry2 == null) return 1;
@@ -376,7 +372,7 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O
    private boolean mIsUserProfile;
    private AccountWithDataSet mPrimaryAccount;
    private RawContactDelta mPrimaryRawContactDelta;
    private Map<String,List<KindSectionData>> mKindSectionDataMap = new HashMap<>();
    private Map<String,KindSectionDataList> mKindSectionDataMap = new HashMap<>();

    // Account header
    private View mAccountHeaderContainer;
@@ -400,6 +396,7 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O
    private View mMoreFields;

    private boolean mIsExpanded;

    private long mPhotoRawContactId;
    private ValuesDelta mPhotoValuesDelta;

@@ -660,12 +657,22 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O
            if (mListener != null) mListener.onBindEditorsFailed();
            return;
        }
        parseRawContactDeltas(rawContactDeltas, mPrimaryAccount);
        parseRawContactDeltas(rawContactDeltas);
        if (mKindSectionDataMap.isEmpty()) {
            elog("No kind section data parsed from RawContactDelta(s)");
            if (mListener != null) mListener.onBindEditorsFailed();
            return;
        }
        mPrimaryRawContactDelta = mKindSectionDataMap.get(StructuredName.CONTENT_ITEM_TYPE)
                .getEntryToWrite(mPrimaryAccount, mHasNewContact).first.getRawContactDelta();
        if (mPrimaryRawContactDelta != null) {
            RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
                    mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
                    StructuredName.CONTENT_ITEM_TYPE);
            RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
                    mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
                    Photo.CONTENT_ITEM_TYPE);
        }

        // Setup the view
        addAccountInfo(rawContactDeltas);
@@ -679,43 +686,7 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O
        if (mListener != null) mListener.onEditorsBound();
    }

    private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas,
            AccountWithDataSet primaryAccount) {
        if (primaryAccount != null) {
            // Use the first writable contact that matches the primary account
            for (RawContactDelta rawContactDelta : rawContactDeltas) {
                if (!rawContactDelta.isVisible()) continue;
                final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
                if (accountType == null || !accountType.areContactsWritable()) continue;
                if (matchesAccount(primaryAccount, rawContactDelta)) {
                    vlog("parse: matched primary account raw contact");
                    mPrimaryRawContactDelta = rawContactDelta;
                    break;
                }
            }
        }
        if (mPrimaryRawContactDelta == null) {
            // Fall back to the first writable raw contact
            for (RawContactDelta rawContactDelta : rawContactDeltas) {
                if (!rawContactDelta.isVisible()) continue;
                final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
                if (accountType != null && accountType.areContactsWritable()) {
                    vlog("parse: falling back to the first writable raw contact as primary");
                    mPrimaryRawContactDelta = rawContactDelta;
                    break;
                }
            }
        }

        if (mPrimaryRawContactDelta != null) {
            RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
                    mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
                    StructuredName.CONTENT_ITEM_TYPE);
            RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
                    mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
                    Photo.CONTENT_ITEM_TYPE);
        }

    private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
        // Build the kind section data list map
        vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
        for (int j = 0; j < rawContactDeltas.size(); j++) {
@@ -743,7 +714,7 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O
                }

                final List<KindSectionData> kindSectionDataList =
                        getKindSectionDataList(mimeType);
                        getOrCreateKindSectionDataList(mimeType);
                final KindSectionData kindSectionData =
                        new KindSectionData(accountType, dataKind, rawContactDelta);
                kindSectionDataList.add(kindSectionData);
@@ -760,27 +731,18 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O
        }
    }

    private List<KindSectionData> getKindSectionDataList(String mimeType) {
    private List<KindSectionData> getOrCreateKindSectionDataList(String mimeType) {
        // Put structured names and nicknames together
        mimeType = Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
                ? StructuredName.CONTENT_ITEM_TYPE : mimeType;
        List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
        KindSectionDataList kindSectionDataList = mKindSectionDataMap.get(mimeType);
        if (kindSectionDataList == null) {
            kindSectionDataList = new ArrayList<>();
            kindSectionDataList = new KindSectionDataList();
            mKindSectionDataMap.put(mimeType, kindSectionDataList);
        }
        return kindSectionDataList;
    }

    /** Whether the given RawContactDelta belong to the given account. */
    private boolean matchesAccount(AccountWithDataSet accountWithDataSet,
            RawContactDelta rawContactDelta) {
        if (accountWithDataSet == null) return false;
        return Objects.equals(accountWithDataSet.name, rawContactDelta.getAccountName())
                && Objects.equals(accountWithDataSet.type, rawContactDelta.getAccountType())
                && Objects.equals(accountWithDataSet.dataSet, rawContactDelta.getDataSet());
    }

    private void addAccountInfo(RawContactDeltaList rawContactDeltas) {
        if (mPrimaryRawContactDelta == null) {
            mAccountHeaderContainer.setVisibility(View.GONE);
@@ -961,123 +923,44 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O

    private void addPhotoView() {
        // Get the kind section data and values delta that we will display in the photo view
        Pair<KindSectionData,ValuesDelta> pair = getPrimaryPhotoKindSectionData(mPhotoId);
        if (pair == null) {
        final KindSectionDataList kindSectionDataList =
                mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
        final Pair<KindSectionData,ValuesDelta> photoToDisplay =
                kindSectionDataList.getEntryToDisplay(mPhotoId);
        if (photoToDisplay == null) {
            wlog("photo: no kind section data parsed");
            mPhotoView.setReadOnly(true);
            mPhotoView.setVisibility(View.GONE);
            return;
        }

        // Set the photo view
        final ValuesDelta primaryValuesDelta = pair.second;
        mPhotoView.setPhoto(primaryValuesDelta, mMaterialPalette);
        mPhotoView.setPhoto(photoToDisplay.second, mMaterialPalette);

        // Find the raw contact ID and values delta that will be written when the photo is edited
        final KindSectionData primaryKindSectionData = pair.first;
        if (mHasNewContact && mPrimaryRawContactDelta != null
                && !primaryKindSectionData.getValuesDeltas().isEmpty()) {
            // If we're editing a read-only contact we want to display the photo from the
            // read-only contact in a photo editor view, but update the new raw contact
            // that was created.
            mPhotoRawContactId = mPrimaryRawContactDelta.getRawContactId();
            mPhotoValuesDelta = primaryKindSectionData.getValuesDeltas().get(0);
            mPhotoView.setReadOnly(false);
            return;
        }
        if (primaryKindSectionData.getAccountType().areContactsWritable() &&
                !primaryKindSectionData.getValuesDeltas().isEmpty()) {
            mPhotoRawContactId = primaryKindSectionData.getRawContactDelta().getRawContactId();
            mPhotoValuesDelta = primaryKindSectionData.getValuesDeltas().get(0);
            mPhotoView.setReadOnly(false);
            return;
        }

        final KindSectionData writableKindSectionData = getFirstWritablePhotoKindSectionData();
        if (writableKindSectionData == null
                || writableKindSectionData.getValuesDeltas().isEmpty()) {
        final Pair<KindSectionData,ValuesDelta> photoToWrite = kindSectionDataList.getEntryToWrite(
                mPrimaryAccount, mHasNewContact);
        if (photoToWrite == null) {
            mPhotoView.setReadOnly(true);
            return;
        }
        mPhotoRawContactId = writableKindSectionData.getRawContactDelta().getRawContactId();
        mPhotoValuesDelta = writableKindSectionData.getValuesDeltas().get(0);
        mPhotoView.setReadOnly(false);
    }

    private Pair<KindSectionData,ValuesDelta> getPrimaryPhotoKindSectionData(long id) {
        final String mimeType = Photo.CONTENT_ITEM_TYPE;
        final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);

        KindSectionData resultKindSectionData = null;
        ValuesDelta resultValuesDelta = null;
        if (id > 0) {
            // Look for a match for the ID that was passed in
            for (KindSectionData kindSectionData : kindSectionDataList) {
                resultValuesDelta = kindSectionData.getValuesDeltaById(id);
                if (resultValuesDelta != null) {
                    vlog("photo: matched kind section data by ID");
                    resultKindSectionData = kindSectionData;
                    break;
                }
            }
        }
        if (resultKindSectionData == null) {
            // Look for a super primary photo
            for (KindSectionData kindSectionData : kindSectionDataList) {
                resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
                if (resultValuesDelta != null) {
                    wlog("photo: matched super primary kind section data");
                    resultKindSectionData = kindSectionData;
                    break;
                }
            }
        }
        if (resultKindSectionData == null) {
            // Fall back to the first non-empty value
            for (KindSectionData kindSectionData : kindSectionDataList) {
                resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
                if (resultValuesDelta != null) {
                    vlog("photo: using first non empty value");
                    resultKindSectionData = kindSectionData;
                    break;
                }
            }
        }
        if (resultKindSectionData == null || resultValuesDelta == null) {
            final List<ValuesDelta> valuesDeltaList = kindSectionDataList.get(0).getValuesDeltas();
            if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
                vlog("photo: falling back to first empty entry");
                resultValuesDelta = valuesDeltaList.get(0);
                resultKindSectionData = kindSectionDataList.get(0);
            }
        }
        return resultKindSectionData != null && resultValuesDelta != null
                ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
    }

    private KindSectionData getFirstWritablePhotoKindSectionData() {
        final String mimeType = Photo.CONTENT_ITEM_TYPE;
        final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
        for (KindSectionData kindSectionData : kindSectionDataList) {
            if (kindSectionData.getAccountType().areContactsWritable()) {
                return kindSectionData;
            }
        }
        return null;
        mPhotoRawContactId = photoToWrite.first.getRawContactDelta().getRawContactId();
        mPhotoValuesDelta = photoToWrite.second;
    }

    private void addKindSectionViews() {
        // Sort the kinds
        final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
        final TreeSet<Map.Entry<String,KindSectionDataList>> entries =
                new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
        entries.addAll(mKindSectionDataMap.entrySet());

        vlog("kind: " + entries.size() + " kindSection(s)");
        int i = -1;
        for (Map.Entry<String, List<KindSectionData>> entry : entries) {
        for (Map.Entry<String, KindSectionDataList> entry : entries) {
            i++;

            final String mimeType = entry.getKey();
            final List<KindSectionData> kindSectionDataList = entry.getValue();
            final KindSectionDataList kindSectionDataList = entry.getValue();

            // Ignore mime types that we've already handled
            if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
@@ -1109,7 +992,7 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O
    }

    private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
            List<KindSectionData> kindSectionDataList, String mimeType) {
            KindSectionDataList kindSectionDataList, String mimeType) {
        final CompactKindSectionView kindSectionView = (CompactKindSectionView)
                mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
                        /* attachToRoot =*/ false);
+188 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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.editor;

import com.android.contacts.common.model.RawContactDelta;
import com.android.contacts.common.model.ValuesDelta;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.model.dataitem.DataKind;

import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.util.Log;
import android.util.Pair;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Container for multiple {@link KindSectionData} objects.  Provides convenience methods for
 * interrogating the collection for a certain KindSectionData item (e.g. the first writable, or
 * "primary", one.  Also enforces that only items with the same DataKind/mime-type are added.
 */
public class KindSectionDataList extends ArrayList<KindSectionData> {

    private static final String TAG = "CompactEditorView";

    /**
     * Returns the mime type for all DataKinds in this List.
     */
    public String getMimeType() {
        if (isEmpty()) return null;
        final String mimeType = get(0).getDataKind().mimeType;
        // StructuredNames and Nicknames are a special case and go together under the
        // StructuredName mime type
        if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
            return StructuredName.CONTENT_ITEM_TYPE;
        }
        return mimeType;
    }

    /**
     * Returns the DataKind for all entries in this List.
     */
    public DataKind getDataKind() {
        return isEmpty() ? null : get(0).getDataKind();
    }

    /**
     * Returns the "primary" KindSectionData and ValuesDelta that should be written for this List.
     */
    public Pair<KindSectionData,ValuesDelta> getEntryToWrite(AccountWithDataSet primaryAccount,
            boolean hasNewContact) {
        // Use the first writable contact that matches the primary account
        if (primaryAccount != null && !hasNewContact) {
            for (KindSectionData kindSectionData : this) {
                if (kindSectionData.getAccountType().areContactsWritable()
                        && !kindSectionData.getValuesDeltas().isEmpty()) {
                    if (matchesAccount(primaryAccount, kindSectionData.getRawContactDelta())) {
                        return new Pair<>(kindSectionData,
                                kindSectionData.getValuesDeltas().get(0));
                    }
                }
            }
        }

        // If no writable raw contact matched the primary account, or we're editing a read-only
        // contact, just return the first writable entry.
        for (KindSectionData kindSectionData : this) {
            if (kindSectionData.getAccountType().areContactsWritable()) {
                if (!kindSectionData.getValuesDeltas().isEmpty()) {
                    return new Pair<>(kindSectionData, kindSectionData.getValuesDeltas().get(0));
                }
            }
        }

        return null;
    }

    /** Whether the given RawContactDelta belong to the given account. */
    private static boolean matchesAccount(AccountWithDataSet accountWithDataSet,
            RawContactDelta rawContactDelta) {
        if (accountWithDataSet == null) return false;
        return Objects.equals(accountWithDataSet.name, rawContactDelta.getAccountName())
                && Objects.equals(accountWithDataSet.type, rawContactDelta.getAccountType())
                && Objects.equals(accountWithDataSet.dataSet, rawContactDelta.getDataSet());
    }

    /**
     * Returns the "primary" KindSectionData and ValuesDelta that should be displayed to the user.
     */
    public Pair<KindSectionData,ValuesDelta> getEntryToDisplay(long id) {
        final String mimeType = getMimeType();
        if (mimeType == null) return null;

        KindSectionData resultKindSectionData = null;
        ValuesDelta resultValuesDelta = null;
        if (id > 0) {
            // Look for a match for the ID that was passed in
            for (KindSectionData kindSectionData : this) {
                resultValuesDelta = kindSectionData.getValuesDeltaById(id);
                if (resultValuesDelta != null) {
                    vlog(mimeType + ": matched kind section data by ID");
                    resultKindSectionData = kindSectionData;
                    break;
                }
            }
        }
        if (resultKindSectionData == null) {
            // Look for a super primary entry
            for (KindSectionData kindSectionData : this) {
                resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
                if (resultValuesDelta != null) {
                    vlog(mimeType + ": matched super primary kind section data");
                    resultKindSectionData = kindSectionData;
                    break;
                }
            }
        }
        if (resultKindSectionData == null) {
            // Fall back to the first non-empty value
            for (KindSectionData kindSectionData : this) {
                resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
                if (resultValuesDelta != null) {
                    vlog(mimeType + ": using first non empty value");
                    resultKindSectionData = kindSectionData;
                    break;
                }
            }
        }
        if (resultKindSectionData == null || resultValuesDelta == null) {
            final List<ValuesDelta> valuesDeltaList = get(0).getValuesDeltas();
            if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
                vlog(mimeType + ": falling back to first empty entry");
                resultValuesDelta = valuesDeltaList.get(0);
                resultKindSectionData = get(0);
            }
        }
        return resultKindSectionData != null && resultValuesDelta != null
                ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
    }

    @Override
    public boolean add(KindSectionData kindSectionData) {
        if (kindSectionData == null) throw new NullPointerException();

        // Enforce that only entries of the same type are added to this list
        final String listMimeType = getMimeType();
        if (listMimeType != null) {
            final String newEntryMimeType = kindSectionData.getDataKind().mimeType;
            if (isNameMimeType(listMimeType)) {
                if (!isNameMimeType(newEntryMimeType)) {
                    throw new IllegalArgumentException(
                            "Can't add " + newEntryMimeType + " to list with type " + listMimeType);
                }
            } else if (!listMimeType.equals(newEntryMimeType)) {
                throw new IllegalArgumentException(
                        "Can't add " + newEntryMimeType + " to list with type " + listMimeType);
            }
        }
        return super.add(kindSectionData);
    }

    // StructuredNames and Nicknames are a special case and go together
    private static boolean isNameMimeType(String mimeType) {
        return StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
                || Nickname.CONTENT_ITEM_TYPE.equals(mimeType);
    }

    private static void vlog(String message) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, message);
        }
    }
}