14084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani/*
24084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani * Copyright (C) 2014 The Android Open Source Project
34084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani *
44084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani * Licensed under the Apache License, Version 2.0 (the "License");
54084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani * you may not use this file except in compliance with the License.
64084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani * You may obtain a copy of the License at
74084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani *
84084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani *      http://www.apache.org/licenses/LICENSE-2.0
94084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani *
104084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani * Unless required by applicable law or agreed to in writing, software
114084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani * distributed under the License is distributed on an "AS IS" BASIS,
124084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani * See the License for the specific language governing permissions and
144084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani * limitations under the License.
154084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani */
164084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
174084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matanipackage com.android.inputmethod.latin;
184084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
19604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheikimport android.Manifest;
204084fa5caeee09ef7993957c5e922dab14c57f3fJatin Mataniimport android.content.ContentResolver;
214084fa5caeee09ef7993957c5e922dab14c57f3fJatin Mataniimport android.content.Context;
224084fa5caeee09ef7993957c5e922dab14c57f3fJatin Mataniimport android.database.ContentObserver;
234084fa5caeee09ef7993957c5e922dab14c57f3fJatin Mataniimport android.os.SystemClock;
244084fa5caeee09ef7993957c5e922dab14c57f3fJatin Mataniimport android.provider.ContactsContract.Contacts;
254084fa5caeee09ef7993957c5e922dab14c57f3fJatin Mataniimport android.util.Log;
264084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
274084fa5caeee09ef7993957c5e922dab14c57f3fJatin Mataniimport com.android.inputmethod.latin.ContactsManager.ContactsChangedListener;
28541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovicimport com.android.inputmethod.latin.define.DebugFlags;
29604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheikimport com.android.inputmethod.latin.permissions.PermissionsUtil;
304084fa5caeee09ef7993957c5e922dab14c57f3fJatin Mataniimport com.android.inputmethod.latin.utils.ExecutorUtils;
314084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
324084fa5caeee09ef7993957c5e922dab14c57f3fJatin Mataniimport java.util.ArrayList;
33705b118672ce5680257b31c565c520e95fd8e298Dan Zivkovicimport java.util.concurrent.atomic.AtomicBoolean;
344084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
354084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani/**
360b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic * A content observer that listens to updates to content provider {@link Contacts#CONTENT_URI}.
374084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani */
380b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovicpublic class ContactsContentObserver implements Runnable {
39541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic    private static final String TAG = "ContactsContentObserver";
404084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
414084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani    private final Context mContext;
424084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani    private final ContactsManager mManager;
43604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik    private final AtomicBoolean mRunning = new AtomicBoolean(false);
444084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
45705b118672ce5680257b31c565c520e95fd8e298Dan Zivkovic    private ContentObserver mContentObserver;
46705b118672ce5680257b31c565c520e95fd8e298Dan Zivkovic    private ContactsChangedListener mContactsChangedListener;
47705b118672ce5680257b31c565c520e95fd8e298Dan Zivkovic
484084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani    public ContactsContentObserver(final ContactsManager manager, final Context context) {
494084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        mManager = manager;
504084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        mContext = context;
514084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani    }
524084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
534084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani    public void registerObserver(final ContactsChangedListener listener) {
54604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        if (!PermissionsUtil.checkAllPermissionsGranted(
55604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik                mContext, Manifest.permission.READ_CONTACTS)) {
56604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik            Log.i(TAG, "No permission to read contacts. Not registering the observer.");
57604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik            // do nothing if we do not have the permission to read contacts.
58604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik            return;
59604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        }
60604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik
61541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic        if (DebugFlags.DEBUG_ENABLED) {
62541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic            Log.d(TAG, "registerObserver()");
634084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        }
640b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic        mContactsChangedListener = listener;
650b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic        mContentObserver = new ContentObserver(null /* handler */) {
664084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani            @Override
674084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani            public void onChange(boolean self) {
68eaa710d4aaac75ff2b7e29608d004fe7662b392eDan Zivkovic                ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD)
69eaa710d4aaac75ff2b7e29608d004fe7662b392eDan Zivkovic                        .execute(ContactsContentObserver.this);
704084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani            }
714084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        };
724084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        final ContentResolver contentResolver = mContext.getContentResolver();
730b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic        contentResolver.registerContentObserver(Contacts.CONTENT_URI, true, mContentObserver);
744084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani    }
754084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
760b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic    @Override
770b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic    public void run() {
78604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        if (!PermissionsUtil.checkAllPermissionsGranted(
79604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik                mContext, Manifest.permission.READ_CONTACTS)) {
80604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik            Log.i(TAG, "No permission to read contacts. Not updating the contacts.");
81604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik            unregister();
82604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik            return;
83604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        }
84604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik
85604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        if (!mRunning.compareAndSet(false /* expect */, true /* update */)) {
86541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic            if (DebugFlags.DEBUG_ENABLED) {
87705b118672ce5680257b31c565c520e95fd8e298Dan Zivkovic                Log.d(TAG, "run() : Already running. Don't waste time checking again.");
88705b118672ce5680257b31c565c520e95fd8e298Dan Zivkovic            }
89705b118672ce5680257b31c565c520e95fd8e298Dan Zivkovic            return;
90705b118672ce5680257b31c565c520e95fd8e298Dan Zivkovic        }
910b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic        if (haveContentsChanged()) {
92541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic            if (DebugFlags.DEBUG_ENABLED) {
93705b118672ce5680257b31c565c520e95fd8e298Dan Zivkovic                Log.d(TAG, "run() : Contacts have changed. Notifying listeners.");
940b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic            }
950b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic            mContactsChangedListener.onContactsChange();
960b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic        }
97604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        mRunning.set(false);
984084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani    }
994084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
10001b023730ee3d86d60016c21915608376c724442Jatin Matani    boolean haveContentsChanged() {
101604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        if (!PermissionsUtil.checkAllPermissionsGranted(
102604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik                mContext, Manifest.permission.READ_CONTACTS)) {
103604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik            Log.i(TAG, "No permission to read contacts. Marking contacts as not changed.");
104604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik            return false;
105604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        }
106604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik
1074084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        final long startTime = SystemClock.uptimeMillis();
1084084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        final int contactCount = mManager.getContactCount();
10944a175732dc4b872515f978b986ef7b357fe2f00Tom Ouyang        if (contactCount > ContactsDictionaryConstants.MAX_CONTACTS_PROVIDER_QUERY_LIMIT) {
1104084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani            // If there are too many contacts then return false. In this rare case it is impossible
1114084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani            // to include all of them anyways and the cost of rebuilding the dictionary is too high.
11244a175732dc4b872515f978b986ef7b357fe2f00Tom Ouyang            // TODO: Sort and check only the most recent contacts?
1134084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani            return false;
1144084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        }
1154084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        if (contactCount != mManager.getContactCountAtLastRebuild()) {
116541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic            if (DebugFlags.DEBUG_ENABLED) {
117541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic                Log.d(TAG, "haveContentsChanged() : Count changed from "
118541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic                        + mManager.getContactCountAtLastRebuild() + " to " + contactCount);
1194084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani            }
1204084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani            return true;
1214084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        }
1224084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        final ArrayList<String> names = mManager.getValidNames(Contacts.CONTENT_URI);
1234084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        if (names.hashCode() != mManager.getHashCodeAtLastRebuild()) {
1244084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani            return true;
1254084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        }
126541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic        if (DebugFlags.DEBUG_ENABLED) {
127541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic            Log.d(TAG, "haveContentsChanged() : No change detected in "
128541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic                    + (SystemClock.uptimeMillis() - startTime) + " ms)");
1294084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        }
1304084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani        return false;
1314084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani    }
1324084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani
1334084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani    public void unregister() {
1340b03f13cabec84d2d841fde47ce9fec0d531b6a1Dan Zivkovic        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
1354084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani    }
1364084fa5caeee09ef7993957c5e922dab14c57f3fJatin Matani}
137