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
17package com.android.tv.settings.util;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.content.ContentUris;
22import android.content.Context;
23import android.database.ContentObserver;
24import android.database.Cursor;
25import android.net.Uri;
26import android.provider.ContactsContract;
27import android.provider.ContactsContract.Contacts;
28import android.text.TextUtils;
29
30import com.android.tv.settings.widget.BitmapWorkerOptions;
31
32import java.util.HashMap;
33import java.util.LinkedHashSet;
34
35/**
36 * AccountImageChangeObserver class...
37 */
38public 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