blob: 639cc70fa2750b15c3934134beea799ec5616e54 [file] [log] [blame]
Chris Wrenf9536642014-04-17 10:01:54 -04001/*
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.server.notification;
18
Selim Cinek5390e7d2018-02-20 19:12:41 -080019import android.annotation.Nullable;
Chris Wrenf9536642014-04-17 10:01:54 -040020import android.app.Notification;
Selim Cinek9acd6732018-03-23 16:39:02 -070021import android.app.Person;
Chris Wrenf9536642014-04-17 10:01:54 -040022import android.content.Context;
Chris Wrenda4bd202014-09-04 15:53:52 -040023import android.content.pm.PackageManager;
Chris Wren99f4c7d2014-09-11 13:38:18 -040024import android.database.ContentObserver;
Chris Wrenf9536642014-04-17 10:01:54 -040025import android.database.Cursor;
26import android.net.Uri;
Christoph Studer12aeda82014-09-23 19:08:56 +020027import android.os.AsyncTask;
Chris Wrenf9536642014-04-17 10:01:54 -040028import android.os.Bundle;
Chris Wren99f4c7d2014-09-11 13:38:18 -040029import android.os.Handler;
Chris Wrenda4bd202014-09-04 15:53:52 -040030import android.os.UserHandle;
Chris Wrenf9536642014-04-17 10:01:54 -040031import android.provider.ContactsContract;
32import android.provider.ContactsContract.Contacts;
33import android.provider.Settings;
34import android.text.TextUtils;
Chris Wrenda4bd202014-09-04 15:53:52 -040035import android.util.ArrayMap;
Julia Reynolds22f02b32016-12-01 15:05:13 -050036import android.util.ArraySet;
Chris Wrenda4bd202014-09-04 15:53:52 -040037import android.util.Log;
Chris Wrenf9536642014-04-17 10:01:54 -040038import android.util.LruCache;
39import android.util.Slog;
40
Chris Wrenf9536642014-04-17 10:01:54 -040041import java.util.ArrayList;
Julia Reynolds22f02b32016-12-01 15:05:13 -050042import java.util.Arrays;
Chris Wrenf9536642014-04-17 10:01:54 -040043import java.util.LinkedList;
Julia Reynolds22f02b32016-12-01 15:05:13 -050044import java.util.List;
Chris Wrenda4bd202014-09-04 15:53:52 -040045import java.util.Map;
Julia Reynolds22f02b32016-12-01 15:05:13 -050046import java.util.Set;
Christoph Studer12aeda82014-09-23 19:08:56 +020047import java.util.concurrent.Semaphore;
48import java.util.concurrent.TimeUnit;
Chris Wrenf9536642014-04-17 10:01:54 -040049
50/**
51 * This {@link NotificationSignalExtractor} attempts to validate
52 * people references. Also elevates the priority of real people.
Chris Wren92af3722014-05-27 16:37:02 -040053 *
54 * {@hide}
Chris Wrenf9536642014-04-17 10:01:54 -040055 */
56public class ValidateNotificationPeople implements NotificationSignalExtractor {
Christoph Studer12aeda82014-09-23 19:08:56 +020057 // Using a shorter log tag since setprop has a limit of 32chars on variable name.
58 private static final String TAG = "ValidateNoPeople";
Chris Wren1a5dad82015-06-16 11:23:12 -040059 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);;
Christoph Studer12aeda82014-09-23 19:08:56 +020060 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Chris Wrenf9536642014-04-17 10:01:54 -040061
62 private static final boolean ENABLE_PEOPLE_VALIDATOR = true;
63 private static final String SETTING_ENABLE_PEOPLE_VALIDATOR =
64 "validate_notification_people_enabled";
Chris Wren44d81a42014-05-14 17:38:05 -040065 private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.STARRED };
Chris Wrenf9536642014-04-17 10:01:54 -040066 private static final int MAX_PEOPLE = 10;
67 private static final int PEOPLE_CACHE_SIZE = 200;
68
Chris Wren99f963e2014-05-28 16:52:42 -040069 /** Indicates that the notification does not reference any valid contacts. */
70 static final float NONE = 0f;
71
72 /**
73 * Affinity will be equal to or greater than this value on notifications
74 * that reference a valid contact.
75 */
76 static final float VALID_CONTACT = 0.5f;
77
78 /**
79 * Affinity will be equal to or greater than this value on notifications
80 * that reference a starred contact.
81 */
82 static final float STARRED_CONTACT = 1f;
Chris Wrenf9536642014-04-17 10:01:54 -040083
84 protected boolean mEnabled;
Chris Wrenda4bd202014-09-04 15:53:52 -040085 private Context mBaseContext;
Chris Wrenf9536642014-04-17 10:01:54 -040086
87 // maps raw person handle to resolved person object
88 private LruCache<String, LookupResult> mPeopleCache;
Chris Wrenda4bd202014-09-04 15:53:52 -040089 private Map<Integer, Context> mUserToContextMap;
Chris Wren99f4c7d2014-09-11 13:38:18 -040090 private Handler mHandler;
91 private ContentObserver mObserver;
92 private int mEvictionCount;
Chris Wren5eab2b72015-06-16 13:56:22 -040093 private NotificationUsageStats mUsageStats;
Chris Wrenf9536642014-04-17 10:01:54 -040094
Chris Wren5eab2b72015-06-16 13:56:22 -040095 public void initialize(Context context, NotificationUsageStats usageStats) {
Chris Wrenda4bd202014-09-04 15:53:52 -040096 if (DEBUG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
97 mUserToContextMap = new ArrayMap<>();
98 mBaseContext = context;
Chris Wren5eab2b72015-06-16 13:56:22 -040099 mUsageStats = usageStats;
Chris Wrenda4bd202014-09-04 15:53:52 -0400100 mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
101 mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt(
102 mBaseContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1);
Chris Wren99f4c7d2014-09-11 13:38:18 -0400103 if (mEnabled) {
104 mHandler = new Handler();
105 mObserver = new ContentObserver(mHandler) {
106 @Override
107 public void onChange(boolean selfChange, Uri uri, int userId) {
108 super.onChange(selfChange, uri, userId);
109 if (DEBUG || mEvictionCount % 100 == 0) {
Chris Wren1a5dad82015-06-16 11:23:12 -0400110 if (VERBOSE) Slog.i(TAG, "mEvictionCount: " + mEvictionCount);
Chris Wren99f4c7d2014-09-11 13:38:18 -0400111 }
112 mPeopleCache.evictAll();
113 mEvictionCount++;
114 }
115 };
116 mBaseContext.getContentResolver().registerContentObserver(Contacts.CONTENT_URI, true,
117 mObserver, UserHandle.USER_ALL);
118 }
Chris Wrenda4bd202014-09-04 15:53:52 -0400119 }
120
121 public RankingReconsideration process(NotificationRecord record) {
122 if (!mEnabled) {
Chris Wren1a5dad82015-06-16 11:23:12 -0400123 if (VERBOSE) Slog.i(TAG, "disabled");
Chris Wrenda4bd202014-09-04 15:53:52 -0400124 return null;
125 }
126 if (record == null || record.getNotification() == null) {
Chris Wren1a5dad82015-06-16 11:23:12 -0400127 if (VERBOSE) Slog.i(TAG, "skipping empty notification");
Chris Wrenda4bd202014-09-04 15:53:52 -0400128 return null;
129 }
130 if (record.getUserId() == UserHandle.USER_ALL) {
Chris Wren1a5dad82015-06-16 11:23:12 -0400131 if (VERBOSE) Slog.i(TAG, "skipping global notification");
Chris Wrenda4bd202014-09-04 15:53:52 -0400132 return null;
133 }
134 Context context = getContextAsUser(record.getUser());
135 if (context == null) {
Chris Wren1a5dad82015-06-16 11:23:12 -0400136 if (VERBOSE) Slog.i(TAG, "skipping notification that lacks a context");
Chris Wrenda4bd202014-09-04 15:53:52 -0400137 return null;
138 }
139 return validatePeople(context, record);
140 }
141
142 @Override
143 public void setConfig(RankingConfig config) {
144 // ignore: config has no relevant information yet.
145 }
146
Julia Reynoldsc861a3d2018-02-15 10:34:49 -0500147 @Override
148 public void setZenHelper(ZenModeHelper helper) {
149
150 }
151
Christoph Studer12aeda82014-09-23 19:08:56 +0200152 /**
153 * @param extras extras of the notification with EXTRA_PEOPLE populated
154 * @param timeoutMs timeout in milliseconds to wait for contacts response
155 * @param timeoutAffinity affinity to return when the timeout specified via
156 * <code>timeoutMs</code> is hit
157 */
158 public float getContactAffinity(UserHandle userHandle, Bundle extras, int timeoutMs,
159 float timeoutAffinity) {
Chris Wren7381daa2014-09-05 11:30:32 -0400160 if (DEBUG) Slog.d(TAG, "checking affinity for " + userHandle);
Chris Wrenda4bd202014-09-04 15:53:52 -0400161 if (extras == null) return NONE;
162 final String key = Long.toString(System.nanoTime());
163 final float[] affinityOut = new float[1];
164 Context context = getContextAsUser(userHandle);
165 if (context == null) {
166 return NONE;
167 }
Julia Reynolds22f02b32016-12-01 15:05:13 -0500168 final PeopleRankingReconsideration prr =
169 validatePeople(context, key, extras, null, affinityOut);
Chris Wrenda4bd202014-09-04 15:53:52 -0400170 float affinity = affinityOut[0];
Christoph Studer12aeda82014-09-23 19:08:56 +0200171
Chris Wrenda4bd202014-09-04 15:53:52 -0400172 if (prr != null) {
Christoph Studer12aeda82014-09-23 19:08:56 +0200173 // Perform the heavy work on a background thread so we can abort when we hit the
174 // timeout.
175 final Semaphore s = new Semaphore(0);
176 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
177 @Override
178 public void run() {
179 prr.work();
180 s.release();
181 }
182 });
183
184 try {
185 if (!s.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
186 Slog.w(TAG, "Timeout while waiting for affinity: " + key + ". "
187 + "Returning timeoutAffinity=" + timeoutAffinity);
188 return timeoutAffinity;
189 }
190 } catch (InterruptedException e) {
191 Slog.w(TAG, "InterruptedException while waiting for affinity: " + key + ". "
192 + "Returning affinity=" + affinity, e);
193 return affinity;
194 }
195
Chris Wrenda4bd202014-09-04 15:53:52 -0400196 affinity = Math.max(prr.getContactAffinity(), affinity);
197 }
198 return affinity;
199 }
200
201 private Context getContextAsUser(UserHandle userHandle) {
202 Context context = mUserToContextMap.get(userHandle.getIdentifier());
203 if (context == null) {
204 try {
205 context = mBaseContext.createPackageContextAsUser("android", 0, userHandle);
206 mUserToContextMap.put(userHandle.getIdentifier(), context);
207 } catch (PackageManager.NameNotFoundException e) {
208 Log.e(TAG, "failed to create package context for lookups", e);
209 }
210 }
211 return context;
212 }
213
214 private RankingReconsideration validatePeople(Context context,
215 final NotificationRecord record) {
John Spurlock2b122f42014-08-27 16:29:47 -0400216 final String key = record.getKey();
217 final Bundle extras = record.getNotification().extras;
218 final float[] affinityOut = new float[1];
Julia Reynolds22f02b32016-12-01 15:05:13 -0500219 final PeopleRankingReconsideration rr =
220 validatePeople(context, key, extras, record.getPeopleOverride(), affinityOut);
Chris Wren5eab2b72015-06-16 13:56:22 -0400221 final float affinity = affinityOut[0];
222 record.setContactAffinity(affinity);
223 if (rr == null) {
224 mUsageStats.registerPeopleAffinity(record, affinity > NONE, affinity == STARRED_CONTACT,
225 true /* cached */);
226 } else {
227 rr.setRecord(record);
228 }
John Spurlock2b122f42014-08-27 16:29:47 -0400229 return rr;
230 }
231
Chris Wrenda4bd202014-09-04 15:53:52 -0400232 private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras,
Julia Reynolds22f02b32016-12-01 15:05:13 -0500233 List<String> peopleOverride, float[] affinityOut) {
Chris Wrenf9536642014-04-17 10:01:54 -0400234 float affinity = NONE;
Chris Wrenf9536642014-04-17 10:01:54 -0400235 if (extras == null) {
236 return null;
237 }
Julia Reynolds22f02b32016-12-01 15:05:13 -0500238 final Set<String> people = new ArraySet<>(peopleOverride);
239 final String[] notificationPeople = getExtraPeople(extras);
240 if (notificationPeople != null ) {
Selim Cinek5390e7d2018-02-20 19:12:41 -0800241 people.addAll(Arrays.asList(notificationPeople));
Chris Wrenf9536642014-04-17 10:01:54 -0400242 }
243
Chris Wren1a5dad82015-06-16 11:23:12 -0400244 if (VERBOSE) Slog.i(TAG, "Validating: " + key + " for " + context.getUserId());
Chris Wrenf9536642014-04-17 10:01:54 -0400245 final LinkedList<String> pendingLookups = new LinkedList<String>();
Julia Reynolds22f02b32016-12-01 15:05:13 -0500246 int personIdx = 0;
247 for (String handle : people) {
Chris Wrenf9536642014-04-17 10:01:54 -0400248 if (TextUtils.isEmpty(handle)) continue;
249
250 synchronized (mPeopleCache) {
Chris Wrenda4bd202014-09-04 15:53:52 -0400251 final String cacheKey = getCacheKey(context.getUserId(), handle);
252 LookupResult lookupResult = mPeopleCache.get(cacheKey);
Chris Wrenf9536642014-04-17 10:01:54 -0400253 if (lookupResult == null || lookupResult.isExpired()) {
254 pendingLookups.add(handle);
255 } else {
Christoph Studer9ffa5002014-09-26 16:57:28 +0200256 if (DEBUG) Slog.d(TAG, "using cached lookupResult");
Chris Wrenf9536642014-04-17 10:01:54 -0400257 }
258 if (lookupResult != null) {
259 affinity = Math.max(affinity, lookupResult.getAffinity());
260 }
261 }
Julia Reynolds22f02b32016-12-01 15:05:13 -0500262 if (++personIdx == MAX_PEOPLE) {
263 break;
264 }
Chris Wrenf9536642014-04-17 10:01:54 -0400265 }
266
267 // record the best available data, so far:
John Spurlock2b122f42014-08-27 16:29:47 -0400268 affinityOut[0] = affinity;
Chris Wrenf9536642014-04-17 10:01:54 -0400269
270 if (pendingLookups.isEmpty()) {
Chris Wren1a5dad82015-06-16 11:23:12 -0400271 if (VERBOSE) Slog.i(TAG, "final affinity: " + affinity);
Chris Wrenf9536642014-04-17 10:01:54 -0400272 return null;
273 }
274
John Spurlock2b122f42014-08-27 16:29:47 -0400275 if (DEBUG) Slog.d(TAG, "Pending: future work scheduled for: " + key);
Chris Wrenda4bd202014-09-04 15:53:52 -0400276 return new PeopleRankingReconsideration(context, key, pendingLookups);
277 }
278
279 private String getCacheKey(int userId, String handle) {
280 return Integer.toString(userId) + ":" + handle;
Chris Wrenf9536642014-04-17 10:01:54 -0400281 }
282
Chris Wren92af3722014-05-27 16:37:02 -0400283 // VisibleForTesting
284 public static String[] getExtraPeople(Bundle extras) {
Selim Cinek5390e7d2018-02-20 19:12:41 -0800285 String[] peopleList = getExtraPeopleForKey(extras, Notification.EXTRA_PEOPLE_LIST);
286 String[] legacyPeople = getExtraPeopleForKey(extras, Notification.EXTRA_PEOPLE);
287 return combineLists(legacyPeople, peopleList);
288 }
289
290 private static String[] combineLists(String[] first, String[] second) {
291 if (first == null) {
292 return second;
293 }
294 if (second == null) {
295 return first;
296 }
297 ArraySet<String> people = new ArraySet<>(first.length + second.length);
298 for (String person: first) {
299 people.add(person);
300 }
301 for (String person: second) {
302 people.add(person);
303 }
304 return (String[]) people.toArray();
305 }
306
307 @Nullable
308 private static String[] getExtraPeopleForKey(Bundle extras, String key) {
309 Object people = extras.get(key);
Chris Wrenfb69da32014-05-15 18:03:11 -0400310 if (people instanceof String[]) {
311 return (String[]) people;
Chris Wrenf9536642014-04-17 10:01:54 -0400312 }
313
Chris Wrenfb69da32014-05-15 18:03:11 -0400314 if (people instanceof ArrayList) {
315 ArrayList arrayList = (ArrayList) people;
316
317 if (arrayList.isEmpty()) {
318 return null;
319 }
320
321 if (arrayList.get(0) instanceof String) {
322 ArrayList<String> stringArray = (ArrayList<String>) arrayList;
323 return stringArray.toArray(new String[stringArray.size()]);
324 }
325
326 if (arrayList.get(0) instanceof CharSequence) {
327 ArrayList<CharSequence> charSeqList = (ArrayList<CharSequence>) arrayList;
328 final int N = charSeqList.size();
329 String[] array = new String[N];
330 for (int i = 0; i < N; i++) {
331 array[i] = charSeqList.get(i).toString();
332 }
333 return array;
334 }
335
Selim Cinek9acd6732018-03-23 16:39:02 -0700336 if (arrayList.get(0) instanceof Person) {
337 ArrayList<Person> list = (ArrayList<Person>) arrayList;
Selim Cineke7238dd2017-12-14 17:48:32 -0800338 final int N = list.size();
339 String[] array = new String[N];
340 for (int i = 0; i < N; i++) {
341 array[i] = list.get(i).resolveToLegacyUri();
342 }
343 return array;
344 }
345
Chris Wrenfb69da32014-05-15 18:03:11 -0400346 return null;
Chris Wrenf9536642014-04-17 10:01:54 -0400347 }
348
Chris Wrenfb69da32014-05-15 18:03:11 -0400349 if (people instanceof String) {
350 String[] array = new String[1];
351 array[0] = (String) people;
352 return array;
Chris Wrenf9536642014-04-17 10:01:54 -0400353 }
354
Chris Wrenfb69da32014-05-15 18:03:11 -0400355 if (people instanceof char[]) {
356 String[] array = new String[1];
357 array[0] = new String((char[]) people);
358 return array;
Chris Wrenf9536642014-04-17 10:01:54 -0400359 }
360
Chris Wrenfb69da32014-05-15 18:03:11 -0400361 if (people instanceof CharSequence) {
362 String[] array = new String[1];
363 array[0] = ((CharSequence) people).toString();
364 return array;
365 }
366
367 if (people instanceof CharSequence[]) {
368 CharSequence[] charSeqArray = (CharSequence[]) people;
Chris Wrenf9536642014-04-17 10:01:54 -0400369 final int N = charSeqArray.length;
Chris Wrenfb69da32014-05-15 18:03:11 -0400370 String[] array = new String[N];
Chris Wrenf9536642014-04-17 10:01:54 -0400371 for (int i = 0; i < N; i++) {
Chris Wrenfb69da32014-05-15 18:03:11 -0400372 array[i] = charSeqArray[i].toString();
Chris Wrenf9536642014-04-17 10:01:54 -0400373 }
Chris Wrenfb69da32014-05-15 18:03:11 -0400374 return array;
Chris Wrenf9536642014-04-17 10:01:54 -0400375 }
376
Chris Wrenf9536642014-04-17 10:01:54 -0400377 return null;
378 }
379
Chris Wrenda4bd202014-09-04 15:53:52 -0400380 private LookupResult resolvePhoneContact(Context context, final String number) {
Chris Wren44d81a42014-05-14 17:38:05 -0400381 Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
382 Uri.encode(number));
Chris Wrenda4bd202014-09-04 15:53:52 -0400383 return searchContacts(context, phoneUri);
Chris Wrenf9536642014-04-17 10:01:54 -0400384 }
385
Chris Wrenda4bd202014-09-04 15:53:52 -0400386 private LookupResult resolveEmailContact(Context context, final String email) {
Chris Wren44d81a42014-05-14 17:38:05 -0400387 Uri numberUri = Uri.withAppendedPath(
388 ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI,
389 Uri.encode(email));
Chris Wrenda4bd202014-09-04 15:53:52 -0400390 return searchContacts(context, numberUri);
Chris Wren44d81a42014-05-14 17:38:05 -0400391 }
392
Chris Wrenda4bd202014-09-04 15:53:52 -0400393 private LookupResult searchContacts(Context context, Uri lookupUri) {
Chris Wren44d81a42014-05-14 17:38:05 -0400394 LookupResult lookupResult = new LookupResult();
Chris Wrenf9536642014-04-17 10:01:54 -0400395 Cursor c = null;
396 try {
Chris Wrenda4bd202014-09-04 15:53:52 -0400397 c = context.getContentResolver().query(lookupUri, LOOKUP_PROJECTION, null, null, null);
Christoph Studer9ffa5002014-09-26 16:57:28 +0200398 if (c == null) {
399 Slog.w(TAG, "Null cursor from contacts query.");
400 return lookupResult;
Chris Wrenf9536642014-04-17 10:01:54 -0400401 }
Christoph Studer9ffa5002014-09-26 16:57:28 +0200402 while (c.moveToNext()) {
403 lookupResult.mergeContact(c);
404 }
405 } catch (Throwable t) {
Chris Wrenf9536642014-04-17 10:01:54 -0400406 Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
407 } finally {
408 if (c != null) {
409 c.close();
410 }
411 }
Chris Wrenf9536642014-04-17 10:01:54 -0400412 return lookupResult;
413 }
414
Chris Wrenf9536642014-04-17 10:01:54 -0400415 private static class LookupResult {
416 private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000; // 1hr
Chris Wrenf9536642014-04-17 10:01:54 -0400417
418 private final long mExpireMillis;
Christoph Studer9ffa5002014-09-26 16:57:28 +0200419 private float mAffinity = NONE;
Chris Wrenf9536642014-04-17 10:01:54 -0400420
Chris Wren44d81a42014-05-14 17:38:05 -0400421 public LookupResult() {
Chris Wrenf9536642014-04-17 10:01:54 -0400422 mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS;
423 }
424
Christoph Studer9ffa5002014-09-26 16:57:28 +0200425 public void mergeContact(Cursor cursor) {
426 mAffinity = Math.max(mAffinity, VALID_CONTACT);
427
428 // Contact ID
429 int id;
Chris Wren44d81a42014-05-14 17:38:05 -0400430 final int idIdx = cursor.getColumnIndex(Contacts._ID);
431 if (idIdx >= 0) {
Christoph Studer9ffa5002014-09-26 16:57:28 +0200432 id = cursor.getInt(idIdx);
433 if (DEBUG) Slog.d(TAG, "contact _ID is: " + id);
Chris Wren44d81a42014-05-14 17:38:05 -0400434 } else {
Christoph Studer9ffa5002014-09-26 16:57:28 +0200435 id = -1;
436 Slog.i(TAG, "invalid cursor: no _ID");
Chris Wren44d81a42014-05-14 17:38:05 -0400437 }
Christoph Studer9ffa5002014-09-26 16:57:28 +0200438
439 // Starred
Chris Wren44d81a42014-05-14 17:38:05 -0400440 final int starIdx = cursor.getColumnIndex(Contacts.STARRED);
441 if (starIdx >= 0) {
Christoph Studer9ffa5002014-09-26 16:57:28 +0200442 boolean isStarred = cursor.getInt(starIdx) != 0;
443 if (isStarred) {
444 mAffinity = Math.max(mAffinity, STARRED_CONTACT);
445 }
446 if (DEBUG) Slog.d(TAG, "contact STARRED is: " + isStarred);
Chris Wren44d81a42014-05-14 17:38:05 -0400447 } else {
448 if (DEBUG) Slog.d(TAG, "invalid cursor: no STARRED");
449 }
450 }
451
Christoph Studer9ffa5002014-09-26 16:57:28 +0200452 private boolean isExpired() {
Chris Wrenf9536642014-04-17 10:01:54 -0400453 return mExpireMillis < System.currentTimeMillis();
454 }
455
Christoph Studer9ffa5002014-09-26 16:57:28 +0200456 private boolean isInvalid() {
457 return mAffinity == NONE || isExpired();
Chris Wrenf9536642014-04-17 10:01:54 -0400458 }
459
460 public float getAffinity() {
461 if (isInvalid()) {
462 return NONE;
Chris Wrenf9536642014-04-17 10:01:54 -0400463 }
Christoph Studer9ffa5002014-09-26 16:57:28 +0200464 return mAffinity;
Chris Wrenf9536642014-04-17 10:01:54 -0400465 }
466 }
John Spurlock2b122f42014-08-27 16:29:47 -0400467
468 private class PeopleRankingReconsideration extends RankingReconsideration {
469 private final LinkedList<String> mPendingLookups;
Chris Wrenda4bd202014-09-04 15:53:52 -0400470 private final Context mContext;
John Spurlock2b122f42014-08-27 16:29:47 -0400471
Julia Reynolds7276e632018-03-29 15:22:59 -0400472 // Amount of time to wait for a result from the contacts db before rechecking affinity.
473 private static final long LOOKUP_TIME = 1000;
John Spurlock2b122f42014-08-27 16:29:47 -0400474 private float mContactAffinity = NONE;
Chris Wren5eab2b72015-06-16 13:56:22 -0400475 private NotificationRecord mRecord;
John Spurlock2b122f42014-08-27 16:29:47 -0400476
Julia Reynoldsc861a3d2018-02-15 10:34:49 -0500477 private PeopleRankingReconsideration(Context context, String key,
478 LinkedList<String> pendingLookups) {
Julia Reynolds7276e632018-03-29 15:22:59 -0400479 super(key, LOOKUP_TIME);
Chris Wrenda4bd202014-09-04 15:53:52 -0400480 mContext = context;
John Spurlock2b122f42014-08-27 16:29:47 -0400481 mPendingLookups = pendingLookups;
482 }
483
484 @Override
485 public void work() {
Chris Wren1a5dad82015-06-16 11:23:12 -0400486 if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
Christoph Studer12aeda82014-09-23 19:08:56 +0200487 long timeStartMs = System.currentTimeMillis();
John Spurlock2b122f42014-08-27 16:29:47 -0400488 for (final String handle: mPendingLookups) {
489 LookupResult lookupResult = null;
490 final Uri uri = Uri.parse(handle);
491 if ("tel".equals(uri.getScheme())) {
492 if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
Chris Wrenda4bd202014-09-04 15:53:52 -0400493 lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart());
John Spurlock2b122f42014-08-27 16:29:47 -0400494 } else if ("mailto".equals(uri.getScheme())) {
495 if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
Chris Wrenda4bd202014-09-04 15:53:52 -0400496 lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
John Spurlock2b122f42014-08-27 16:29:47 -0400497 } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
498 if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
Chris Wrenda4bd202014-09-04 15:53:52 -0400499 lookupResult = searchContacts(mContext, uri);
John Spurlock2b122f42014-08-27 16:29:47 -0400500 } else {
501 lookupResult = new LookupResult(); // invalid person for the cache
Selim Cineke7238dd2017-12-14 17:48:32 -0800502 if (!"name".equals(uri.getScheme())) {
503 Slog.w(TAG, "unsupported URI " + handle);
504 }
John Spurlock2b122f42014-08-27 16:29:47 -0400505 }
506 if (lookupResult != null) {
507 synchronized (mPeopleCache) {
Chris Wrenda4bd202014-09-04 15:53:52 -0400508 final String cacheKey = getCacheKey(mContext.getUserId(), handle);
509 mPeopleCache.put(cacheKey, lookupResult);
John Spurlock2b122f42014-08-27 16:29:47 -0400510 }
Julia Reynoldsc861a3d2018-02-15 10:34:49 -0500511 if (DEBUG) {
512 Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
513 }
John Spurlock2b122f42014-08-27 16:29:47 -0400514 mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
Chris Wrenf37dae72014-09-12 12:09:17 -0400515 } else {
516 if (DEBUG) Slog.d(TAG, "lookupResult is null");
John Spurlock2b122f42014-08-27 16:29:47 -0400517 }
518 }
Christoph Studer12aeda82014-09-23 19:08:56 +0200519 if (DEBUG) {
520 Slog.d(TAG, "Validation finished in " + (System.currentTimeMillis() - timeStartMs) +
521 "ms");
522 }
Chris Wren723aa762015-04-09 15:35:23 -0400523
Chris Wren5eab2b72015-06-16 13:56:22 -0400524 if (mRecord != null) {
525 mUsageStats.registerPeopleAffinity(mRecord, mContactAffinity > NONE,
526 mContactAffinity == STARRED_CONTACT, false /* cached */);
527 }
John Spurlock2b122f42014-08-27 16:29:47 -0400528 }
529
530 @Override
531 public void applyChangesLocked(NotificationRecord operand) {
532 float affinityBound = operand.getContactAffinity();
533 operand.setContactAffinity(Math.max(mContactAffinity, affinityBound));
Chris Wren1a5dad82015-06-16 11:23:12 -0400534 if (VERBOSE) Slog.i(TAG, "final affinity: " + operand.getContactAffinity());
John Spurlock2b122f42014-08-27 16:29:47 -0400535 }
536
537 public float getContactAffinity() {
538 return mContactAffinity;
539 }
Chris Wren5eab2b72015-06-16 13:56:22 -0400540
541 public void setRecord(NotificationRecord record) {
542 mRecord = record;
543 }
John Spurlock2b122f42014-08-27 16:29:47 -0400544 }
Chris Wrenf9536642014-04-17 10:01:54 -0400545}
546