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 17 package com.android.inputmethod.latin; 18 19 import android.Manifest; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.database.ContentObserver; 23 import android.os.SystemClock; 24 import android.provider.ContactsContract.Contacts; 25 import android.util.Log; 26 27 import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener; 28 import com.android.inputmethod.latin.define.DebugFlags; 29 import com.android.inputmethod.latin.permissions.PermissionsUtil; 30 import com.android.inputmethod.latin.utils.ExecutorUtils; 31 32 import java.util.ArrayList; 33 import java.util.concurrent.atomic.AtomicBoolean; 34 35 /** 36 * A content observer that listens to updates to content provider {@link Contacts#CONTENT_URI}. 37 */ 38 public 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 ContactsContentObserver(final ContactsManager manager, final Context context)48 public ContactsContentObserver(final ContactsManager manager, final Context context) { 49 mManager = manager; 50 mContext = context; 51 } 52 registerObserver(final ContactsChangedListener listener)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 run()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 haveContentsChanged()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 unregister()133 public void unregister() { 134 mContext.getContentResolver().unregisterContentObserver(mContentObserver); 135 } 136 } 137