/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.appsearch.contactsindexer; import static android.os.Process.INVALID_UID; import android.annotation.BinderThread; import android.annotation.NonNull; import android.app.appsearch.AppSearchEnvironment; import android.app.appsearch.AppSearchEnvironmentFactory; import android.app.appsearch.util.LogUtil; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.os.CancellationSignal; import android.os.PatternMatcher; import android.os.UserHandle; import android.provider.ContactsContract; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import com.android.server.LocalManagerRegistry; import com.android.server.SystemService; import com.android.server.appsearch.indexer.IndexerLocalService; import java.io.File; import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Objects; /** * Manages the per device-user ContactsIndexer instance to index CP2 contacts into AppSearch. * *
This class is thread-safe.
*
* @hide
*/
public final class ContactsIndexerManagerService extends SystemService {
static final String TAG = "ContactsIndexerManagerS";
private static final String DEFAULT_CONTACTS_PROVIDER_PACKAGE_NAME =
"com.android.providers.contacts";
private final Context mContext;
private final ContactsIndexerConfig mContactsIndexerConfig;
private final LocalService mLocalService;
// Sparse array of ContactsIndexerUserInstance indexed by the device-user ID.
private final Map Contacts indexer syncs on-device contacts from ContactsProvider (CP2) denoted by {@link
* android.provider.ContactsContract.Contacts#AUTHORITY} into the AppSearch "builtin:Person"
* corpus under the "android" package name. The default package which hosts CP2 is
* "com.android.providers.contacts" but it could be different on OEM devices. Since the Android
* package that hosts CP2 is different from the package name that "owns" the builtin:Person
* corpus in AppSearch, clearing the CP2 package data doesn't automatically clear the
* builtin:Person corpus in AppSearch.
*
* This broadcast receiver allows contacts indexer to listen to events which indicate that
* CP2 data was cleared and force a full sync of CP2 contacts into AppSearch.
*/
private class ContactsProviderChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
try {
Objects.requireNonNull(context);
Objects.requireNonNull(intent);
switch (intent.getAction()) {
case Intent.ACTION_PACKAGE_CHANGED:
case Intent.ACTION_PACKAGE_DATA_CLEARED:
String packageName = intent.getData().getSchemeSpecificPart();
if (LogUtil.DEBUG) {
Log.v(TAG, "Received package data cleared event for " + packageName);
}
if (!mContactsProviderPackageName.equals(packageName)) {
return;
}
int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
if (uid == INVALID_UID) {
Log.w(TAG, "uid is missing in the intent: " + intent);
return;
}
mLocalService.doUpdateForUser(
UserHandle.getUserHandleForUid(uid), new CancellationSignal());
break;
default:
Log.w(TAG, "Received unknown intent: " + intent);
}
} catch (RuntimeException e) {
Slog.wtf(TAG, "ContactsProviderChangedReceiver.onReceive() failed ", e);
}
}
}
public class LocalService implements IndexerLocalService {
/** Runs a full update for the user. */
@Override
public void doUpdateForUser(
@NonNull UserHandle userHandle, @NonNull CancellationSignal signal) {
Objects.requireNonNull(signal);
synchronized (mContactsIndexersLocked) {
ContactsIndexerUserInstance instance = mContactsIndexersLocked.get(userHandle);
if (instance != null) {
instance.doFullUpdateAsync(signal);
}
}
}
}
}