blob: f83133a80f43457cdddd595e25dc852b371c5c3b [file] [log] [blame]
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +00001/*
2 * Copyright (C) 2021 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.server.appsearch.contactsindexer;
18
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000019import static android.os.Process.INVALID_UID;
20
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000021import android.annotation.NonNull;
22import android.annotation.UserIdInt;
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000023import android.content.BroadcastReceiver;
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000024import android.content.Context;
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000025import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.pm.PackageManager;
28import android.content.pm.ProviderInfo;
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000029import android.os.CancellationSignal;
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000030import android.os.PatternMatcher;
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000031import android.os.UserHandle;
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000032import android.provider.ContactsContract;
Xiaoyu Jin8edf1be2022-02-23 01:04:45 +000033import android.util.Log;
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000034import android.util.SparseArray;
35
Xiaoyu Jin754687a2022-03-29 16:13:58 +000036import com.android.internal.annotations.VisibleForTesting;
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000037import com.android.server.LocalManagerRegistry;
38import com.android.server.SystemService;
Xiaoyu Jin8edf1be2022-02-23 01:04:45 +000039import com.android.server.appsearch.AppSearchModule;
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000040
Xiaoyu Jin8edf1be2022-02-23 01:04:45 +000041import java.io.File;
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000042import java.util.List;
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000043import java.util.Objects;
44
45/**
46 * Manages the per device-user ContactsIndexer instance to index CP2 contacts into AppSearch.
47 *
Xiaoyu Jin8edf1be2022-02-23 01:04:45 +000048 * <p>This class is thread-safe.
Xiaoyu Jinc46f9562022-02-23 01:00:19 +000049 *
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000050 * @hide
51 */
52public final class ContactsIndexerManagerService extends SystemService {
53 static final String TAG = "ContactsIndexerManagerService";
54
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000055 private static final String DEFAULT_CONTACTS_PROVIDER_PACKAGE_NAME =
56 "com.android.providers.contacts";
57
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000058 private final Context mContext;
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000059 private final LocalService mLocalService;
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000060 // Sparse array of ContactsIndexerUserInstance indexed by the device-user ID.
61 private final SparseArray<ContactsIndexerUserInstance> mContactsIndexersLocked =
62 new SparseArray<>();
63
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000064 private String mContactsProviderPackageName;
65
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000066 /** Constructs a {@link ContactsIndexerManagerService}. */
67 public ContactsIndexerManagerService(@NonNull Context context) {
68 super(context);
69 mContext = Objects.requireNonNull(context);
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000070 mLocalService = new LocalService();
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000071 }
72
73 @Override
74 public void onStart() {
Mangesh Ghiwareddb93912022-03-19 03:16:20 +000075 mContactsProviderPackageName = getContactsProviderPackageName();
76 registerReceivers();
77 LocalManagerRegistry.addManager(LocalService.class, mLocalService);
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000078 }
79
80 @Override
Xiaoyu Jin8edf1be2022-02-23 01:04:45 +000081 public void onUserUnlocking(@NonNull TargetUser user) {
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000082 Objects.requireNonNull(user);
83 UserHandle userHandle = user.getUserHandle();
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000084 synchronized (mContactsIndexersLocked) {
Mangesh Ghiwared5c31de2022-03-01 22:51:05 +000085 int userId = userHandle.getIdentifier();
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000086 ContactsIndexerUserInstance instance = mContactsIndexersLocked.get(userId);
87 if (instance == null) {
Mangesh Ghiwared5c31de2022-03-01 22:51:05 +000088 Context userContext = mContext.createContextAsUser(userHandle, /*flags=*/ 0);
Xiaoyu Jin8edf1be2022-02-23 01:04:45 +000089 File appSearchDir = AppSearchModule.getAppSearchDir(userHandle);
90 File contactsDir = new File(appSearchDir, "contacts");
91 try {
92 instance = ContactsIndexerUserInstance.createInstance(userContext, contactsDir);
Mangesh Ghiwared5c31de2022-03-01 22:51:05 +000093 Log.d(TAG, "Created Contacts Indexer instance for user " + userHandle);
Xiaoyu Jin8edf1be2022-02-23 01:04:45 +000094 } catch (Throwable t) {
95 Log.e(TAG, "Failed to create Contacts Indexer instance for user "
Mangesh Ghiwared5c31de2022-03-01 22:51:05 +000096 + userHandle, t);
Xiaoyu Jin8edf1be2022-02-23 01:04:45 +000097 return;
98 }
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +000099 mContactsIndexersLocked.put(userId, instance);
100 }
Mangesh Ghiwared5c31de2022-03-01 22:51:05 +0000101 instance.startAsync();
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +0000102 }
103 }
104
105 @Override
106 public void onUserStopping(@NonNull TargetUser user) {
107 Objects.requireNonNull(user);
Mangesh Ghiwared5c31de2022-03-01 22:51:05 +0000108 UserHandle userHandle = user.getUserHandle();
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +0000109 synchronized (mContactsIndexersLocked) {
Mangesh Ghiwared5c31de2022-03-01 22:51:05 +0000110 int userId = userHandle.getIdentifier();
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +0000111 ContactsIndexerUserInstance instance = mContactsIndexersLocked.get(userId);
112 if (instance != null) {
113 mContactsIndexersLocked.delete(userId);
Mangesh Ghiwared5c31de2022-03-01 22:51:05 +0000114 try {
115 instance.shutdown();
116 } catch (InterruptedException e) {
117 Log.w(TAG, "Failed to shutdown contacts indexer for " + userHandle, e);
118 }
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +0000119 }
120 }
121 }
122
Mangesh Ghiwareddb93912022-03-19 03:16:20 +0000123 /**
124 * Returns the package name where the Contacts Provider is hosted.
125 */
126 private String getContactsProviderPackageName() {
127 PackageManager pm = mContext.getPackageManager();
128 List<ProviderInfo> providers = pm.queryContentProviders(/*processName=*/ null, /*uid=*/ 0,
129 PackageManager.ComponentInfoFlags.of(0));
130 for (int i = 0; i < providers.size(); i++) {
131 ProviderInfo providerInfo = providers.get(i);
132 if (ContactsContract.AUTHORITY.equals(providerInfo.authority)) {
133 return providerInfo.packageName;
134 }
135 }
136 return DEFAULT_CONTACTS_PROVIDER_PACKAGE_NAME;
137 }
138
139 /**
140 * Registers a broadcast receiver to get package changed (disabled/enabled) and package data
141 * cleared events for CP2.
142 */
143 private void registerReceivers() {
144 IntentFilter contactsProviderChangedFilter = new IntentFilter();
145 contactsProviderChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
146 contactsProviderChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
147 contactsProviderChangedFilter.addDataScheme("package");
148 contactsProviderChangedFilter.addDataSchemeSpecificPart(mContactsProviderPackageName,
149 PatternMatcher.PATTERN_LITERAL);
150 mContext.registerReceiverForAllUsers(
151 new ContactsProviderChangedReceiver(),
152 contactsProviderChangedFilter,
153 /*broadcastPermission=*/ null,
154 /*scheduler=*/ null);
155 Log.v(TAG, "Registered receiver for CP2 (package: " + mContactsProviderPackageName + ")"
156 + " data cleared events");
157 }
158
159 /**
160 * Broadcast receiver to handle CP2 changed (disabled/enabled) and package data cleared events.
161 *
162 * <p>Contacts indexer syncs on-device contacts from ContactsProvider (CP2) denoted by {@link
163 * android.provider.ContactsContract.Contacts#AUTHORITY} into the AppSearch "builtin:Person"
164 * corpus under the "android" package name. The default package which hosts CP2 is
165 * "com.android.providers.contacts" but it could be different on OEM devices. Since the Android
166 * package that hosts CP2 is different from the package name that "owns" the builtin:Person
167 * corpus in AppSearch, clearing the CP2 package data doesn't automatically clear the
168 * builtin:Person corpus in AppSearch.
169 *
170 * <p>This broadcast receiver allows contacts indexer to listen to events which indicate that
171 * CP2 data was cleared and force a full sync of CP2 contacts into AppSearch.
172 */
173 private class ContactsProviderChangedReceiver extends BroadcastReceiver {
174
175 @Override
176 public void onReceive(@NonNull Context context, @NonNull Intent intent) {
177 Objects.requireNonNull(context);
178 Objects.requireNonNull(intent);
179
180 switch (intent.getAction()) {
181 case Intent.ACTION_PACKAGE_CHANGED:
182 case Intent.ACTION_PACKAGE_DATA_CLEARED:
183 String packageName = intent.getData().getSchemeSpecificPart();
184 Log.v(TAG, "Received package data cleared event for " + packageName);
185 if (!mContactsProviderPackageName.equals(packageName)) {
186 return;
187 }
188 int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
189 if (uid == INVALID_UID) {
190 Log.w(TAG, "uid is missing in the intent: " + intent);
191 return;
192 }
193 int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
194 mLocalService.doFullUpdateForUser(userId, new CancellationSignal());
195 break;
196 default:
197 Log.w(TAG, "Received unknown intent: " + intent);
198 }
199 }
200 }
201
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +0000202 class LocalService {
Xiaoyu Jin754687a2022-03-29 16:13:58 +0000203 void doFullUpdateForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
204 Objects.requireNonNull(signal);
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +0000205 synchronized (mContactsIndexersLocked) {
206 ContactsIndexerUserInstance instance = mContactsIndexersLocked.get(userId);
207 if (instance != null) {
Mangesh Ghiwared5c31de2022-03-01 22:51:05 +0000208 instance.doFullUpdateAsync(signal);
Mangesh Ghiwareb1d721e2022-02-18 19:06:47 +0000209 }
210 }
211 }
212 }
213}