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