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