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