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