1 /* 2 * Copyright (C) 2014 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.tv.settings.util; 18 19 import android.accounts.Account; 20 import android.accounts.AccountManager; 21 import android.content.ContentUris; 22 import android.content.Context; 23 import android.database.ContentObserver; 24 import android.database.Cursor; 25 import android.net.Uri; 26 import android.provider.ContactsContract; 27 import android.provider.ContactsContract.Contacts; 28 import android.text.TextUtils; 29 30 import com.android.tv.settings.widget.BitmapWorkerOptions; 31 32 import java.util.HashMap; 33 import java.util.LinkedHashSet; 34 35 /** 36 * AccountImageChangeObserver class... 37 */ 38 public class AccountImageChangeObserver { 39 private static final String TAG = "AccountImageChangeObserver"; 40 private static final boolean DEBUG = false; 41 42 private static final String GOOGLE_ACCOUNT_TYPE = "com.google"; 43 44 private static final Object sObserverInstanceLock = new Object(); 45 private static AccountImageChangeObserver sObserver; 46 47 private class ContactChangeContentObserver extends ContentObserver { 48 private final Account mWatchedAccount; 49 private final LinkedHashSet<Uri> mUrisToNotify; 50 private final Object mLock = new Object(); 51 private final Context mContext; 52 private String mCurrentImageUri; 53 54 public ContactChangeContentObserver(Context context, Account watchedAccount) { 55 super(null); 56 mWatchedAccount = watchedAccount; 57 mUrisToNotify = new LinkedHashSet<>(); 58 mContext = context; 59 mCurrentImageUri = AccountImageHelper.getAccountPictureUri(mContext, mWatchedAccount); 60 } 61 62 @Override 63 public boolean deliverSelfNotifications() { 64 return true; 65 } 66 67 public void addUriToNotifyList(Uri uri) { 68 synchronized (mLock) { 69 mUrisToNotify.add(uri); 70 } 71 } 72 73 @Override 74 public void onChange(boolean selfChange) { 75 String newUri = AccountImageHelper.getAccountPictureUri(mContext, mWatchedAccount); 76 77 if (TextUtils.equals(mCurrentImageUri, newUri)) { 78 // no change, no need to notify 79 return; 80 } 81 82 synchronized (mLock) { 83 for (Uri uri : mUrisToNotify) { 84 mContext.getContentResolver().notifyChange(uri, null); 85 } 86 87 mCurrentImageUri = newUri; 88 } 89 } 90 } 91 92 private final HashMap<String, ContactChangeContentObserver> mObserverMap; 93 94 95 /** 96 * get the singleton AccountImageChangeObserver for the application 97 */ 98 public static AccountImageChangeObserver getInstance() { 99 if (sObserver == null) { 100 synchronized (sObserverInstanceLock) { 101 if (sObserver == null) { 102 sObserver = new AccountImageChangeObserver(); 103 } 104 } 105 } 106 return sObserver; 107 } 108 109 public AccountImageChangeObserver() { 110 mObserverMap = new HashMap<>(); 111 } 112 113 public synchronized void registerChangeUriIfPresent(BitmapWorkerOptions options) { 114 Uri imageUri = options.getResourceUri(); 115 // Only register URIs that match the Account Image URI schema, and 116 // have a change notify URI specified. 117 if (imageUri != null && UriUtils.isAccountImageUri(imageUri)) { 118 Uri changeNotifUri = UriUtils.getAccountImageChangeNotifyUri(imageUri); 119 imageUri = imageUri.buildUpon().clearQuery().build(); 120 121 if (changeNotifUri == null) { 122 // No change Notiy URI specified 123 return; 124 } 125 126 String accountName = UriUtils.getAccountName(imageUri); 127 Context context = options.getContext(); 128 129 if (accountName != null && context != null) { 130 Account thisAccount = null; 131 for (Account account : AccountManager.get(context). 132 getAccountsByType(GOOGLE_ACCOUNT_TYPE)) { 133 if (account.name.equals(accountName)) { 134 thisAccount = account; 135 break; 136 } 137 } 138 if (thisAccount != null) { 139 ContactChangeContentObserver observer; 140 141 if (mObserverMap.containsKey(thisAccount.name)) { 142 observer = mObserverMap.get(thisAccount.name); 143 if (observer != null) { 144 observer.addUriToNotifyList(changeNotifUri); 145 } 146 } else { 147 long contactId = getContactIdForAccount(context, thisAccount); 148 if (contactId != -1) { 149 Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 150 contactId); 151 observer = new ContactChangeContentObserver(context, thisAccount); 152 mObserverMap.put(thisAccount.name, observer); 153 observer.addUriToNotifyList(changeNotifUri); 154 context.getContentResolver().registerContentObserver(contactUri, false, 155 observer); 156 } 157 } 158 } 159 } 160 } 161 } 162 163 private long getContactIdForAccount(Context context, Account account) { 164 // Look up this account in the contacts database. 165 String[] projection = new String[] { 166 ContactsContract.Data._ID, 167 ContactsContract.Data.CONTACT_ID, 168 ContactsContract.Data.LOOKUP_KEY 169 }; 170 String selection = 171 ContactsContract.CommonDataKinds.Email.ADDRESS + " LIKE ?"; 172 String[] selectionArgs = new String[] { account.name }; 173 Cursor c = null; 174 long contactId = -1; 175 String lookupKey = null; 176 try { 177 c = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, 178 projection, selection, selectionArgs, null); 179 if (c.moveToNext()) { 180 contactId = c.getLong(1); 181 lookupKey = c.getString(2); 182 } 183 } finally { 184 if (c != null) { 185 c.close(); 186 } 187 } 188 189 if (contactId != -1 && !TextUtils.isEmpty(lookupKey)) { 190 return contactId; 191 } 192 193 return -1; 194 } 195 } 196