1 /*
2  * Copyright (C) 2011 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 
17 package com.android.contacts.model.account;
18 
19 import android.accounts.Account;
20 import android.content.ContentProviderOperation;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.net.Uri;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.provider.BaseColumns;
27 import android.provider.ContactsContract;
28 import android.provider.ContactsContract.RawContacts;
29 import android.text.TextUtils;
30 
31 import com.android.contacts.model.AccountTypeManager;
32 import com.android.contacts.preference.ContactsPreferences;
33 import com.google.common.base.Objects;
34 import com.google.common.collect.Lists;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.regex.Pattern;
39 
40 /**
41  * Wrapper for an account that includes a data set (which may be null).
42  */
43 public class AccountWithDataSet implements Parcelable {
44     private static final String STRINGIFY_SEPARATOR = "\u0001";
45     private static final String ARRAY_STRINGIFY_SEPARATOR = "\u0002";
46 
47     private static final Pattern STRINGIFY_SEPARATOR_PAT =
48             Pattern.compile(Pattern.quote(STRINGIFY_SEPARATOR));
49     private static final Pattern ARRAY_STRINGIFY_SEPARATOR_PAT =
50             Pattern.compile(Pattern.quote(ARRAY_STRINGIFY_SEPARATOR));
51 
52     public final String name;
53     public final String type;
54     public final String dataSet;
55     private final AccountTypeWithDataSet mAccountTypeWithDataSet;
56 
57     private static final String[] ID_PROJECTION = new String[] {BaseColumns._ID};
58     private static final Uri RAW_CONTACTS_URI_LIMIT_1 = RawContacts.CONTENT_URI.buildUpon()
59             .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, "1").build();
60 
61     public static final String LOCAL_ACCOUNT_SELECTION = RawContacts.ACCOUNT_TYPE + " IS NULL AND "
62             + RawContacts.ACCOUNT_NAME + " IS NULL AND "
63             + RawContacts.DATA_SET + " IS NULL";
64 
AccountWithDataSet(String name, String type, String dataSet)65     public AccountWithDataSet(String name, String type, String dataSet) {
66         this.name = emptyToNull(name);
67         this.type = emptyToNull(type);
68         this.dataSet = emptyToNull(dataSet);
69         mAccountTypeWithDataSet = AccountTypeWithDataSet.get(type, dataSet);
70     }
71 
emptyToNull(String text)72     private static final String emptyToNull(String text) {
73         return TextUtils.isEmpty(text) ? null : text;
74     }
75 
AccountWithDataSet(Parcel in)76     public AccountWithDataSet(Parcel in) {
77         this.name = in.readString();
78         this.type = in.readString();
79         this.dataSet = in.readString();
80         mAccountTypeWithDataSet = AccountTypeWithDataSet.get(type, dataSet);
81     }
82 
isNullAccount()83     public boolean isNullAccount() {
84         return name == null && type == null && dataSet == null;
85     }
86 
getNullAccount()87     public static AccountWithDataSet getNullAccount() {
88         return new AccountWithDataSet(null, null, null);
89     }
90 
getAccountOrNull()91     public Account getAccountOrNull() {
92         if (name != null && type != null) {
93             return new Account(name, type);
94         }
95         return null;
96     }
97 
describeContents()98     public int describeContents() {
99         return 0;
100     }
101 
writeToParcel(Parcel dest, int flags)102     public void writeToParcel(Parcel dest, int flags) {
103         dest.writeString(name);
104         dest.writeString(type);
105         dest.writeString(dataSet);
106     }
107 
108     // For Parcelable
109     public static final Creator<AccountWithDataSet> CREATOR = new Creator<AccountWithDataSet>() {
110         public AccountWithDataSet createFromParcel(Parcel source) {
111             return new AccountWithDataSet(source);
112         }
113 
114         public AccountWithDataSet[] newArray(int size) {
115             return new AccountWithDataSet[size];
116         }
117     };
118 
getAccountTypeWithDataSet()119     public AccountTypeWithDataSet getAccountTypeWithDataSet() {
120         return mAccountTypeWithDataSet;
121     }
122 
123     /**
124      * Return {@code true} if this account has any contacts in the database.
125      * Touches DB.  Don't use in the UI thread.
126      */
hasData(Context context)127     public boolean hasData(Context context) {
128         String selection;
129         String[] args = null;
130         if (isNullAccount()) {
131             selection = LOCAL_ACCOUNT_SELECTION;
132         } else {
133             final String BASE_SELECTION =
134                     RawContacts.ACCOUNT_TYPE + " = ?" + " AND " + RawContacts.ACCOUNT_NAME + " = ?";
135             if (TextUtils.isEmpty(dataSet)) {
136                 selection = BASE_SELECTION + " AND " + RawContacts.DATA_SET + " IS NULL";
137                 args = new String[] {type, name};
138             } else {
139                 selection = BASE_SELECTION + " AND " + RawContacts.DATA_SET + " = ?";
140                 args = new String[] {type, name, dataSet};
141             }
142         }
143         selection += " AND " + RawContacts.DELETED + "=0";
144 
145         final Cursor c = context.getContentResolver().query(RAW_CONTACTS_URI_LIMIT_1,
146                 ID_PROJECTION, selection, args, null);
147         if (c == null) return false;
148         try {
149             return c.moveToFirst();
150         } finally {
151             c.close();
152         }
153     }
154 
equals(Object obj)155     public boolean equals(Object obj) {
156         if (obj instanceof AccountWithDataSet) {
157             AccountWithDataSet other = (AccountWithDataSet) obj;
158             return Objects.equal(name, other.name)
159                     && Objects.equal(type, other.type)
160                     && Objects.equal(dataSet, other.dataSet);
161         }
162         return false;
163     }
164 
hashCode()165     public int hashCode() {
166         int result = 17;
167         result = 31 * result + (name != null ? name.hashCode() : 0);
168         result = 31 * result + (type != null ? type.hashCode() : 0);
169         result = 31 * result + (dataSet != null ? dataSet.hashCode() : 0);
170         return result;
171     }
172 
toString()173     public String toString() {
174         return "AccountWithDataSet {name=" + name + ", type=" + type + ", dataSet=" + dataSet + "}";
175     }
176 
addStringified(StringBuilder sb, AccountWithDataSet account)177     private static StringBuilder addStringified(StringBuilder sb, AccountWithDataSet account) {
178         if (!TextUtils.isEmpty(account.name)) sb.append(account.name);
179         sb.append(STRINGIFY_SEPARATOR);
180         if (!TextUtils.isEmpty(account.type)) sb.append(account.type);
181         sb.append(STRINGIFY_SEPARATOR);
182         if (!TextUtils.isEmpty(account.dataSet)) sb.append(account.dataSet);
183 
184         return sb;
185     }
186 
187     /**
188      * Pack the instance into a string.
189      */
stringify()190     public String stringify() {
191         return addStringified(new StringBuilder(), this).toString();
192     }
193 
194     /**
195      * Returns a {@link ContentProviderOperation} that will create a RawContact in this account
196      */
newRawContactOperation()197     public ContentProviderOperation newRawContactOperation() {
198         final ContentProviderOperation.Builder builder =
199                 ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
200                         .withValue(RawContacts.ACCOUNT_NAME, name)
201                         .withValue(RawContacts.ACCOUNT_TYPE, type);
202         if (dataSet != null) {
203             builder.withValue(RawContacts.DATA_SET, dataSet);
204         }
205         return builder.build();
206     }
207 
208     /**
209      * Unpack a string created by {@link #stringify}.
210      *
211      * @throws IllegalArgumentException if it's an invalid string.
212      */
unstringify(String s)213     public static AccountWithDataSet unstringify(String s) {
214         final String[] array = STRINGIFY_SEPARATOR_PAT.split(s, 3);
215         if (array.length < 3) {
216             throw new IllegalArgumentException("Invalid string " + s);
217         }
218         return new AccountWithDataSet(array[0], array[1],
219                 TextUtils.isEmpty(array[2]) ? null : array[2]);
220     }
221 
222     /**
223      * Pack a list of {@link AccountWithDataSet} into a string.
224      */
stringifyList(List<AccountWithDataSet> accounts)225     public static String stringifyList(List<AccountWithDataSet> accounts) {
226         final StringBuilder sb = new StringBuilder();
227 
228         for (AccountWithDataSet account : accounts) {
229             if (sb.length() > 0) {
230                 sb.append(ARRAY_STRINGIFY_SEPARATOR);
231             }
232             addStringified(sb, account);
233         }
234 
235         return sb.toString();
236     }
237 
238     /**
239      * Unpack a list of {@link AccountWithDataSet} into a string.
240      *
241      * @throws IllegalArgumentException if it's an invalid string.
242      */
unstringifyList(String s)243     public static List<AccountWithDataSet> unstringifyList(String s) {
244         final ArrayList<AccountWithDataSet> ret = Lists.newArrayList();
245         if (TextUtils.isEmpty(s)) {
246             return ret;
247         }
248 
249         final String[] array = ARRAY_STRINGIFY_SEPARATOR_PAT.split(s);
250 
251         for (int i = 0; i < array.length; i++) {
252             ret.add(unstringify(array[i]));
253         }
254 
255         return ret;
256     }
257 }
258 
259