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