1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.contacts.editor; 17 18 import com.android.contacts.common.model.RawContactDelta; 19 import com.android.contacts.common.model.ValuesDelta; 20 import com.android.contacts.common.model.account.AccountWithDataSet; 21 import com.android.contacts.common.model.dataitem.DataKind; 22 import com.android.contacts.common.model.RawContactModifier; 23 24 import android.util.Log; 25 import android.util.Pair; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Objects; 30 31 /** 32 * Container for multiple {@link KindSectionData} objects. Provides convenience methods for 33 * interrogating the collection for a certain KindSectionData item (e.g. the first writable, or 34 * "primary", one. Also enforces that only items with the same DataKind/mime-type are added. 35 */ 36 public class KindSectionDataList extends ArrayList<KindSectionData> { 37 38 private static final String TAG = CompactRawContactsEditorView.TAG; 39 40 /** 41 * Returns the mime type for all DataKinds in this List. 42 */ getMimeType()43 public String getMimeType() { 44 if (isEmpty()) return null; 45 return get(0).getDataKind().mimeType; 46 } 47 48 /** 49 * Returns the DataKind for all entries in this List. 50 */ getDataKind()51 public DataKind getDataKind() { 52 return isEmpty() ? null : get(0).getDataKind(); 53 } 54 55 /** 56 * Returns the primary KindSectionData and ValuesDelta that should be written for this List. 57 */ getEntryToWrite(long id, AccountWithDataSet primaryAccount, boolean isUserProfile)58 public Pair<KindSectionData,ValuesDelta> getEntryToWrite(long id, 59 AccountWithDataSet primaryAccount, boolean isUserProfile) { 60 final String mimeType = getMimeType(); 61 if (mimeType == null) return null; 62 63 if (!isUserProfile) { 64 if (id > 0) { 65 // Look for a match for the ID that was passed in 66 for (KindSectionData kindSectionData : this) { 67 if (kindSectionData.getAccountType().areContactsWritable()) { 68 final ValuesDelta valuesDelta = kindSectionData.getValuesDeltaById(id); 69 if (valuesDelta != null) { 70 vlog(mimeType + ": matched kind section data to write by ID"); 71 return new Pair<>(kindSectionData, valuesDelta); 72 } 73 } 74 } 75 } 76 77 // Look for a super primary entry 78 for (KindSectionData kindSectionData : this) { 79 if (kindSectionData.getAccountType().areContactsWritable()) { 80 final ValuesDelta valuesDelta = kindSectionData.getSuperPrimaryValuesDelta(); 81 if (valuesDelta != null) { 82 vlog(mimeType + ": matched kind section data to write by super primary"); 83 return new Pair<>(kindSectionData, valuesDelta); 84 } 85 } 86 } 87 88 // Use the first writable contact that matches the primary account 89 if (primaryAccount != null) { 90 for (KindSectionData kindSectionData : this) { 91 if (kindSectionData.getAccountType().areContactsWritable()) { 92 if (matchesAccount(primaryAccount, kindSectionData.getRawContactDelta()) 93 && !kindSectionData.getValuesDeltas().isEmpty()) { 94 vlog(mimeType + ": matched kind section data to write by primary " + 95 "account"); 96 return new Pair<>(kindSectionData, 97 kindSectionData.getValuesDeltas().get(0)); 98 } 99 } 100 } 101 } 102 } 103 104 // Just return the first writable entry. 105 for (KindSectionData kindSectionData : this) { 106 if (kindSectionData.getAccountType().areContactsWritable()) { 107 // Create an entry if necessary 108 RawContactModifier.ensureKindExists(kindSectionData.getRawContactDelta(), 109 kindSectionData.getAccountType(), mimeType); 110 111 if (!kindSectionData.getValuesDeltas().isEmpty()) { 112 vlog(mimeType + ": falling back to first kind section data to write"); 113 return new Pair<>(kindSectionData, kindSectionData.getValuesDeltas().get(0)); 114 } 115 } 116 } 117 118 wlog(mimeType+ ": no writable kind section data found"); 119 return null; 120 } 121 122 /** Whether the given RawContactDelta belong to the given account. */ matchesAccount(AccountWithDataSet accountWithDataSet, RawContactDelta rawContactDelta)123 private static boolean matchesAccount(AccountWithDataSet accountWithDataSet, 124 RawContactDelta rawContactDelta) { 125 return Objects.equals(accountWithDataSet.name, rawContactDelta.getAccountName()) 126 && Objects.equals(accountWithDataSet.type, rawContactDelta.getAccountType()) 127 && Objects.equals(accountWithDataSet.dataSet, rawContactDelta.getDataSet()); 128 } 129 130 /** 131 * Returns the KindSectionData and ValuesDelta that should be displayed to the user. 132 */ getEntryToDisplay(long id)133 public Pair<KindSectionData,ValuesDelta> getEntryToDisplay(long id) { 134 final String mimeType = getMimeType(); 135 if (mimeType == null) return null; 136 137 if (id > 0) { 138 // Look for a match for the ID that was passed in 139 for (KindSectionData kindSectionData : this) { 140 final ValuesDelta valuesDelta = kindSectionData.getValuesDeltaById(id); 141 if (valuesDelta != null) { 142 vlog(mimeType + ": matched kind section data to display by ID"); 143 return new Pair<>(kindSectionData, valuesDelta); 144 } 145 } 146 } 147 // Look for a super primary entry 148 for (KindSectionData kindSectionData : this) { 149 final ValuesDelta valuesDelta = kindSectionData.getSuperPrimaryValuesDelta(); 150 if (valuesDelta != null) { 151 vlog(mimeType + ": matched kind section data to display by super primary"); 152 return new Pair<>(kindSectionData, valuesDelta); 153 } 154 } 155 156 // Fall back to the first non-empty value 157 for (KindSectionData kindSectionData : this) { 158 final ValuesDelta valuesDelta = kindSectionData.getFirstNonEmptyValuesDelta(); 159 if (valuesDelta != null) { 160 vlog(mimeType + ": using first non empty value to display"); 161 return new Pair<>(kindSectionData, valuesDelta); 162 } 163 } 164 165 for (KindSectionData kindSectionData : this) { 166 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas(); 167 if (!valuesDeltaList.isEmpty()) { 168 vlog(mimeType + ": falling back to first empty entry to display"); 169 final ValuesDelta valuesDelta = valuesDeltaList.get(0); 170 return new Pair<>(kindSectionData, valuesDelta); 171 } 172 } 173 174 wlog(mimeType + ": no kind section data found to display"); 175 return null; 176 } 177 178 @Override add(KindSectionData kindSectionData)179 public boolean add(KindSectionData kindSectionData) { 180 if (kindSectionData == null) throw new NullPointerException(); 181 182 // Enforce that only entries of the same type are added to this list 183 final String listMimeType = getMimeType(); 184 if (listMimeType != null) { 185 final String newEntryMimeType = kindSectionData.getDataKind().mimeType; 186 if (!listMimeType.equals(newEntryMimeType)) { 187 throw new IllegalArgumentException( 188 "Can't add " + newEntryMimeType + " to list with type " + listMimeType); 189 } 190 } 191 return super.add(kindSectionData); 192 } 193 wlog(String message)194 private static void wlog(String message) { 195 if (Log.isLoggable(TAG, Log.WARN)) { 196 Log.w(TAG, message); 197 } 198 } 199 vlog(String message)200 private static void vlog(String message) { 201 if (Log.isLoggable(TAG, Log.VERBOSE)) { 202 Log.v(TAG, message); 203 } 204 } 205 } 206