165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane/* 265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Copyright (C) 2014 The Android Open Source Project 365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * 465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Licensed under the Apache License, Version 2.0 (the "License"); 565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * you may not use this file except in compliance with the License. 665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * You may obtain a copy of the License at 765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * 865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * http://www.apache.org/licenses/LICENSE-2.0 965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * 1065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Unless required by applicable law or agreed to in writing, software 1165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * distributed under the License is distributed on an "AS IS" BASIS, 1265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * See the License for the specific language governing permissions and 1465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * limitations under the License. 1565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */ 1665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 1765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lanepackage com.android.tv.settings.util; 1865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 1965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.accounts.Account; 2065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.accounts.AccountManager; 2165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.ContentUris; 2265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.Context; 2365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.database.ContentObserver; 2465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.database.Cursor; 2565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.net.Uri; 2665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.provider.ContactsContract; 2765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.provider.ContactsContract.Contacts; 2865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.text.TextUtils; 2965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 3065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport com.android.tv.settings.widget.BitmapWorkerOptions; 3165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 3265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport java.util.LinkedHashSet; 3365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport java.util.HashMap; 3465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 3565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane/** 3665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * AccountImageChangeObserver class... 3765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */ 3865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lanepublic class AccountImageChangeObserver { 3965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private static final String TAG = "AccountImageChangeObserver"; 4065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private static final boolean DEBUG = false; 4165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 4265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private static final String GOOGLE_ACCOUNT_TYPE = "com.google"; 4365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 4465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private static final Object sObserverInstanceLock = new Object(); 4565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private static AccountImageChangeObserver sObserver; 4665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 4765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private class ContactChangeContentObserver extends ContentObserver { 4865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private Account mWatchedAccount; 4965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private LinkedHashSet<Uri> mUrisToNotify; 5065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private Object mLock = new Object(); 5165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private Context mContext; 5265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private String mCurrentImageUri; 5365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 5465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane public ContactChangeContentObserver(Context context, Account watchedAccount) { 5565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane super(null); 5665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane mWatchedAccount = watchedAccount; 5765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane mUrisToNotify = new LinkedHashSet<Uri>(); 5865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane mContext = context; 5965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane mCurrentImageUri = AccountImageHelper.getAccountPictureUri(mContext, mWatchedAccount); 6065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 6165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 6265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane @Override 6365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane public boolean deliverSelfNotifications() { 6465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane return true; 6565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 6665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 6765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane public void addUriToNotifyList(Uri uri) { 6865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane synchronized (mLock) { 6965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane mUrisToNotify.add(uri); 7065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 7165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 7265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 7365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane @Override 7465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane public void onChange(boolean selfChange) { 7565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane String newUri = AccountImageHelper.getAccountPictureUri(mContext, mWatchedAccount); 7665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 7765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (TextUtils.equals(mCurrentImageUri, newUri)) { 7865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane // no change, no need to notify 7965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane return; 8065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 8165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 8265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane synchronized (mLock) { 8365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane for (Uri uri : mUrisToNotify) { 8465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane mContext.getContentResolver().notifyChange(uri, null); 8565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 8665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 8765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane mCurrentImageUri = newUri; 8865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 8965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 9065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 9165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 9265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private HashMap<String, ContactChangeContentObserver> mObserverMap; 9365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 9465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 9565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane /** 9665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * get the singleton AccountImageChangeObserver for the application 9765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */ 9865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane public final static AccountImageChangeObserver getInstance() { 9965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (sObserver == null) { 10065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane synchronized (sObserverInstanceLock) { 10165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (sObserver == null) { 10265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane sObserver = new AccountImageChangeObserver(); 10365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 10465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 10565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 10665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane return sObserver; 10765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 10865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 10965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane public AccountImageChangeObserver() { 11065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane mObserverMap = new HashMap<String, ContactChangeContentObserver>(); 11165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 11265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 11365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane public synchronized void registerChangeUriIfPresent(BitmapWorkerOptions options) { 11465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane Uri imageUri = options.getResourceUri(); 11565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane // Only register URIs that match the Account Image URI schema, and 11665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane // have a change notify URI specified. 11765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (imageUri != null && UriUtils.isAccountImageUri(imageUri)) { 11865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane Uri changeNotifUri = UriUtils.getAccountImageChangeNotifyUri(imageUri); 11965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane imageUri = imageUri.buildUpon().clearQuery().build(); 12065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 12165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (changeNotifUri == null) { 12265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane // No change Notiy URI specified 12365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane return; 12465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 12565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 12665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane String accountName = UriUtils.getAccountName(imageUri); 12765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane Context context = options.getContext(); 12865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 12965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (accountName != null && context != null) { 13065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane Account thisAccount = null; 13165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane for (Account account : AccountManager.get(context). 13265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane getAccountsByType(GOOGLE_ACCOUNT_TYPE)) { 13365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (account.name.equals(accountName)) { 13465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane thisAccount = account; 13565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane break; 13665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 13765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 13865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (thisAccount != null) { 13965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane ContactChangeContentObserver observer; 14065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 14165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (mObserverMap.containsKey(thisAccount.name)) { 14265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane observer = mObserverMap.get(thisAccount.name); 14365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (observer != null) { 14465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane observer.addUriToNotifyList(changeNotifUri); 14565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 14665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } else { 14765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane long contactId = getContactIdForAccount(context, thisAccount); 14865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (contactId != -1) { 14965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 15065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane contactId); 15165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane observer = new ContactChangeContentObserver(context, thisAccount); 15265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane mObserverMap.put(thisAccount.name, observer); 15365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane observer.addUriToNotifyList(changeNotifUri); 15465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane context.getContentResolver().registerContentObserver(contactUri, false, 15565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane observer); 15665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 15765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 15865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 15965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 16065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 16165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 16265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 16365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane private long getContactIdForAccount(Context context, Account account) { 16465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane // Look up this account in the contacts database. 16565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane String[] projection = new String[] { 16665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane ContactsContract.Data._ID, 16765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane ContactsContract.Data.CONTACT_ID, 16865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane ContactsContract.Data.LOOKUP_KEY 16965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane }; 17065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane String selection = 17165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane ContactsContract.CommonDataKinds.Email.ADDRESS + " LIKE ?"; 17265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane String[] selectionArgs = new String[] { account.name }; 17365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane Cursor c = null; 17465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane long contactId = -1; 17565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane String lookupKey = null; 17665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane try { 17765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane c = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, 17865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane projection, selection, selectionArgs, null); 17965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (c.moveToNext()) { 18065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane contactId = c.getLong(1); 18165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane lookupKey = c.getString(2); 18265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 18365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } finally { 18465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (c != null) { 18565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane c.close(); 18665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 18765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 18865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 18965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane if (contactId != -1 && !TextUtils.isEmpty(lookupKey)) { 19065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane return contactId; 19165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 19265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane 19365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane return -1; 19465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane } 19565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane} 196