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.inputmethod.latin;
18
19import android.Manifest;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.database.ContentObserver;
23import android.os.SystemClock;
24import android.provider.ContactsContract.Contacts;
25import android.util.Log;
26
27import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener;
28import com.android.inputmethod.latin.define.DebugFlags;
29import com.android.inputmethod.latin.permissions.PermissionsUtil;
30import com.android.inputmethod.latin.utils.ExecutorUtils;
31
32import java.util.ArrayList;
33import java.util.concurrent.atomic.AtomicBoolean;
34
35/**
36 * A content observer that listens to updates to content provider {@link Contacts#CONTENT_URI}.
37 */
38public class ContactsContentObserver implements Runnable {
39    private static final String TAG = "ContactsContentObserver";
40
41    private final Context mContext;
42    private final ContactsManager mManager;
43    private final AtomicBoolean mRunning = new AtomicBoolean(false);
44
45    private ContentObserver mContentObserver;
46    private ContactsChangedListener mContactsChangedListener;
47
48    public ContactsContentObserver(final ContactsManager manager, final Context context) {
49        mManager = manager;
50        mContext = context;
51    }
52
53    public void registerObserver(final ContactsChangedListener listener) {
54        if (!PermissionsUtil.checkAllPermissionsGranted(
55                mContext, Manifest.permission.READ_CONTACTS)) {
56            Log.i(TAG, "No permission to read contacts. Not registering the observer.");
57            // do nothing if we do not have the permission to read contacts.
58            return;
59        }
60
61        if (DebugFlags.DEBUG_ENABLED) {
62            Log.d(TAG, "registerObserver()");
63        }
64        mContactsChangedListener = listener;
65        mContentObserver = new ContentObserver(null /* handler */) {
66            @Override
67            public void onChange(boolean self) {
68                ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD)
69                        .execute(ContactsContentObserver.this);
70            }
71        };
72        final ContentResolver contentResolver = mContext.getContentResolver();
73        contentResolver.registerContentObserver(Contacts.CONTENT_URI, true, mContentObserver);
74    }
75
76    @Override
77    public void run() {
78        if (!PermissionsUtil.checkAllPermissionsGranted(
79                mContext, Manifest.permission.READ_CONTACTS)) {
80            Log.i(TAG, "No permission to read contacts. Not updating the contacts.");
81            unregister();
82            return;
83        }
84
85        if (!mRunning.compareAndSet(false /* expect */, true /* update */)) {
86            if (DebugFlags.DEBUG_ENABLED) {
87                Log.d(TAG, "run() : Already running. Don't waste time checking again.");
88            }
89            return;
90        }
91        if (haveContentsChanged()) {
92            if (DebugFlags.DEBUG_ENABLED) {
93                Log.d(TAG, "run() : Contacts have changed. Notifying listeners.");
94            }
95            mContactsChangedListener.onContactsChange();
96        }
97        mRunning.set(false);
98    }
99
100    boolean haveContentsChanged() {
101        if (!PermissionsUtil.checkAllPermissionsGranted(
102                mContext, Manifest.permission.READ_CONTACTS)) {
103            Log.i(TAG, "No permission to read contacts. Marking contacts as not changed.");
104            return false;
105        }
106
107        final long startTime = SystemClock.uptimeMillis();
108        final int contactCount = mManager.getContactCount();
109        if (contactCount > ContactsDictionaryConstants.MAX_CONTACTS_PROVIDER_QUERY_LIMIT) {
110            // If there are too many contacts then return false. In this rare case it is impossible
111            // to include all of them anyways and the cost of rebuilding the dictionary is too high.
112            // TODO: Sort and check only the most recent contacts?
113            return false;
114        }
115        if (contactCount != mManager.getContactCountAtLastRebuild()) {
116            if (DebugFlags.DEBUG_ENABLED) {
117                Log.d(TAG, "haveContentsChanged() : Count changed from "
118                        + mManager.getContactCountAtLastRebuild() + " to " + contactCount);
119            }
120            return true;
121        }
122        final ArrayList<String> names = mManager.getValidNames(Contacts.CONTENT_URI);
123        if (names.hashCode() != mManager.getHashCodeAtLastRebuild()) {
124            return true;
125        }
126        if (DebugFlags.DEBUG_ENABLED) {
127            Log.d(TAG, "haveContentsChanged() : No change detected in "
128                    + (SystemClock.uptimeMillis() - startTime) + " ms)");
129        }
130        return false;
131    }
132
133    public void unregister() {
134        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
135    }
136}
137