1f953664dc17dca23bd724bd64f89189c16c83263Chris Wren/*
2f953664dc17dca23bd724bd64f89189c16c83263Chris Wren* Copyright (C) 2014 The Android Open Source Project
3f953664dc17dca23bd724bd64f89189c16c83263Chris Wren*
4f953664dc17dca23bd724bd64f89189c16c83263Chris Wren* Licensed under the Apache License, Version 2.0 (the "License");
5f953664dc17dca23bd724bd64f89189c16c83263Chris Wren* you may not use this file except in compliance with the License.
6f953664dc17dca23bd724bd64f89189c16c83263Chris Wren* You may obtain a copy of the License at
7f953664dc17dca23bd724bd64f89189c16c83263Chris Wren*
8f953664dc17dca23bd724bd64f89189c16c83263Chris Wren*      http://www.apache.org/licenses/LICENSE-2.0
9f953664dc17dca23bd724bd64f89189c16c83263Chris Wren*
10f953664dc17dca23bd724bd64f89189c16c83263Chris Wren* Unless required by applicable law or agreed to in writing, software
11f953664dc17dca23bd724bd64f89189c16c83263Chris Wren* distributed under the License is distributed on an "AS IS" BASIS,
12f953664dc17dca23bd724bd64f89189c16c83263Chris Wren* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f953664dc17dca23bd724bd64f89189c16c83263Chris Wren* See the License for the specific language governing permissions and
14f953664dc17dca23bd724bd64f89189c16c83263Chris Wren* limitations under the License.
15f953664dc17dca23bd724bd64f89189c16c83263Chris Wren*/
16f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
17f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenpackage com.android.server.notification;
18f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
19f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.app.Notification;
20f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.content.Context;
21da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wrenimport android.content.pm.PackageManager;
2299f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wrenimport android.database.ContentObserver;
23f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.database.Cursor;
24f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.net.Uri;
2512aeda802ed91a49977a22166319ce74a3352e30Christoph Studerimport android.os.AsyncTask;
26f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.os.Bundle;
2799f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wrenimport android.os.Handler;
28da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wrenimport android.os.UserHandle;
29f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.provider.ContactsContract;
30f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.provider.ContactsContract.Contacts;
31f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.provider.Settings;
32f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.text.TextUtils;
33da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wrenimport android.util.ArrayMap;
3422f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynoldsimport android.util.ArraySet;
35da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wrenimport android.util.Log;
36f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.util.LruCache;
37f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport android.util.Slog;
38f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
39f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport java.util.ArrayList;
4022f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynoldsimport java.util.Arrays;
41f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenimport java.util.LinkedList;
4222f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynoldsimport java.util.List;
43da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wrenimport java.util.Map;
4422f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynoldsimport java.util.Set;
4512aeda802ed91a49977a22166319ce74a3352e30Christoph Studerimport java.util.concurrent.Semaphore;
4612aeda802ed91a49977a22166319ce74a3352e30Christoph Studerimport java.util.concurrent.TimeUnit;
47f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
4866dfcc50dff82e50cd64d14b9aeea1c795876364Chris Wrenimport android.os.SystemClock;
4966dfcc50dff82e50cd64d14b9aeea1c795876364Chris Wren
50f953664dc17dca23bd724bd64f89189c16c83263Chris Wren/**
51f953664dc17dca23bd724bd64f89189c16c83263Chris Wren * This {@link NotificationSignalExtractor} attempts to validate
52f953664dc17dca23bd724bd64f89189c16c83263Chris Wren * people references. Also elevates the priority of real people.
5392af372fc60f59c628a7169d4a506e9c834c097aChris Wren *
5492af372fc60f59c628a7169d4a506e9c834c097aChris Wren * {@hide}
55f953664dc17dca23bd724bd64f89189c16c83263Chris Wren */
56f953664dc17dca23bd724bd64f89189c16c83263Chris Wrenpublic class ValidateNotificationPeople implements NotificationSignalExtractor {
5712aeda802ed91a49977a22166319ce74a3352e30Christoph Studer    // Using a shorter log tag since setprop has a limit of 32chars on variable name.
5812aeda802ed91a49977a22166319ce74a3352e30Christoph Studer    private static final String TAG = "ValidateNoPeople";
591a5dad867419e76928c84f0f4fdc4feacce43bbdChris Wren    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);;
6012aeda802ed91a49977a22166319ce74a3352e30Christoph Studer    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
61f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
62f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    private static final boolean ENABLE_PEOPLE_VALIDATOR = true;
63f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    private static final String SETTING_ENABLE_PEOPLE_VALIDATOR =
64f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            "validate_notification_people_enabled";
6544d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren    private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.STARRED };
66f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    private static final int MAX_PEOPLE = 10;
67f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    private static final int PEOPLE_CACHE_SIZE = 200;
68f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
6999f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren    /** Indicates that the notification does not reference any valid contacts. */
7099f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren    static final float NONE = 0f;
7199f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren
7299f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren    /**
7399f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren     * Affinity will be equal to or greater than this value on notifications
7499f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren     * that reference a valid contact.
7599f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren     */
7699f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren    static final float VALID_CONTACT = 0.5f;
7799f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren
7899f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren    /**
7999f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren     * Affinity will be equal to or greater than this value on notifications
8099f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren     * that reference a starred contact.
8199f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren     */
8299f963ea04d7a86219ece00c356f3f6bce33b6d6Chris Wren    static final float STARRED_CONTACT = 1f;
83f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
84f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    protected boolean mEnabled;
85da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    private Context mBaseContext;
86f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
87f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    // maps raw person handle to resolved person object
88f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    private LruCache<String, LookupResult> mPeopleCache;
89da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    private Map<Integer, Context> mUserToContextMap;
9099f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren    private Handler mHandler;
9199f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren    private ContentObserver mObserver;
9299f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren    private int mEvictionCount;
935eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren    private NotificationUsageStats mUsageStats;
94f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
955eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren    public void initialize(Context context, NotificationUsageStats usageStats) {
96da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        if (DEBUG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
97da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        mUserToContextMap = new ArrayMap<>();
98da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        mBaseContext = context;
995eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren        mUsageStats = usageStats;
100da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
101da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt(
102da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                mBaseContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1);
10399f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren        if (mEnabled) {
10499f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren            mHandler = new Handler();
10599f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren            mObserver = new ContentObserver(mHandler) {
10699f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren                @Override
10799f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren                public void onChange(boolean selfChange, Uri uri, int userId) {
10899f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren                    super.onChange(selfChange, uri, userId);
10999f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren                    if (DEBUG || mEvictionCount % 100 == 0) {
1101a5dad867419e76928c84f0f4fdc4feacce43bbdChris Wren                        if (VERBOSE) Slog.i(TAG, "mEvictionCount: " + mEvictionCount);
11199f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren                    }
11299f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren                    mPeopleCache.evictAll();
11399f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren                    mEvictionCount++;
11499f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren                }
11599f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren            };
11699f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren            mBaseContext.getContentResolver().registerContentObserver(Contacts.CONTENT_URI, true,
11799f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren                    mObserver, UserHandle.USER_ALL);
11899f4c7d0c9c9c29ced22da0a8af4d1a04b0ef186Chris Wren        }
119da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    }
120da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren
121da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    public RankingReconsideration process(NotificationRecord record) {
122da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        if (!mEnabled) {
1231a5dad867419e76928c84f0f4fdc4feacce43bbdChris Wren            if (VERBOSE) Slog.i(TAG, "disabled");
124da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            return null;
125da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        }
126da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        if (record == null || record.getNotification() == null) {
1271a5dad867419e76928c84f0f4fdc4feacce43bbdChris Wren            if (VERBOSE) Slog.i(TAG, "skipping empty notification");
128da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            return null;
129da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        }
130da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        if (record.getUserId() == UserHandle.USER_ALL) {
1311a5dad867419e76928c84f0f4fdc4feacce43bbdChris Wren            if (VERBOSE) Slog.i(TAG, "skipping global notification");
132da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            return null;
133da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        }
134da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        Context context = getContextAsUser(record.getUser());
135da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        if (context == null) {
1361a5dad867419e76928c84f0f4fdc4feacce43bbdChris Wren            if (VERBOSE) Slog.i(TAG, "skipping notification that lacks a context");
137da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            return null;
138da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        }
139da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        return validatePeople(context, record);
140da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    }
141da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren
142da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    @Override
143da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    public void setConfig(RankingConfig config) {
144da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        // ignore: config has no relevant information yet.
145da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    }
146da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren
14712aeda802ed91a49977a22166319ce74a3352e30Christoph Studer    /**
14812aeda802ed91a49977a22166319ce74a3352e30Christoph Studer     * @param extras extras of the notification with EXTRA_PEOPLE populated
14912aeda802ed91a49977a22166319ce74a3352e30Christoph Studer     * @param timeoutMs timeout in milliseconds to wait for contacts response
15012aeda802ed91a49977a22166319ce74a3352e30Christoph Studer     * @param timeoutAffinity affinity to return when the timeout specified via
15112aeda802ed91a49977a22166319ce74a3352e30Christoph Studer     *                        <code>timeoutMs</code> is hit
15212aeda802ed91a49977a22166319ce74a3352e30Christoph Studer     */
15312aeda802ed91a49977a22166319ce74a3352e30Christoph Studer    public float getContactAffinity(UserHandle userHandle, Bundle extras, int timeoutMs,
15412aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            float timeoutAffinity) {
1557381daa0b99ef5beb224ffd2544a156af40e78d1Chris Wren        if (DEBUG) Slog.d(TAG, "checking affinity for " + userHandle);
156da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        if (extras == null) return NONE;
157da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        final String key = Long.toString(System.nanoTime());
158da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        final float[] affinityOut = new float[1];
159da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        Context context = getContextAsUser(userHandle);
160da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        if (context == null) {
161da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            return NONE;
162da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        }
16322f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds        final PeopleRankingReconsideration prr =
16422f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds                validatePeople(context, key, extras, null, affinityOut);
165da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        float affinity = affinityOut[0];
16612aeda802ed91a49977a22166319ce74a3352e30Christoph Studer
167da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        if (prr != null) {
16812aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            // Perform the heavy work on a background thread so we can abort when we hit the
16912aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            // timeout.
17012aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            final Semaphore s = new Semaphore(0);
17112aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
17212aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                @Override
17312aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                public void run() {
17412aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                    prr.work();
17512aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                    s.release();
17612aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                }
17712aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            });
17812aeda802ed91a49977a22166319ce74a3352e30Christoph Studer
17912aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            try {
18012aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                if (!s.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
18112aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                    Slog.w(TAG, "Timeout while waiting for affinity: " + key + ". "
18212aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                            + "Returning timeoutAffinity=" + timeoutAffinity);
18312aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                    return timeoutAffinity;
18412aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                }
18512aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            } catch (InterruptedException e) {
18612aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                Slog.w(TAG, "InterruptedException while waiting for affinity: " + key + ". "
18712aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                        + "Returning affinity=" + affinity, e);
18812aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                return affinity;
18912aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            }
19012aeda802ed91a49977a22166319ce74a3352e30Christoph Studer
191da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            affinity = Math.max(prr.getContactAffinity(), affinity);
192da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        }
193da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        return affinity;
194da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    }
195da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren
196da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    private Context getContextAsUser(UserHandle userHandle) {
197da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        Context context = mUserToContextMap.get(userHandle.getIdentifier());
198da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        if (context == null) {
199da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            try {
200da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                context = mBaseContext.createPackageContextAsUser("android", 0, userHandle);
201da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                mUserToContextMap.put(userHandle.getIdentifier(), context);
202da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            } catch (PackageManager.NameNotFoundException e) {
203da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                Log.e(TAG, "failed to create package context for lookups", e);
204da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            }
205da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        }
206da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        return context;
207da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    }
208da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren
209da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    private RankingReconsideration validatePeople(Context context,
210da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            final NotificationRecord record) {
2112b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        final String key = record.getKey();
2122b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        final Bundle extras = record.getNotification().extras;
2132b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        final float[] affinityOut = new float[1];
21422f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds        final PeopleRankingReconsideration rr =
21522f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds                validatePeople(context, key, extras, record.getPeopleOverride(), affinityOut);
2165eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren        final float affinity = affinityOut[0];
2175eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren        record.setContactAffinity(affinity);
2185eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren        if (rr == null) {
2195eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren            mUsageStats.registerPeopleAffinity(record, affinity > NONE, affinity == STARRED_CONTACT,
2205eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren                    true /* cached */);
2215eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren        } else {
2225eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren            rr.setRecord(record);
2235eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren        }
2242b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        return rr;
2252b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock    }
2262b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock
227da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras,
22822f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds            List<String> peopleOverride, float[] affinityOut) {
22966dfcc50dff82e50cd64d14b9aeea1c795876364Chris Wren        long start = SystemClock.elapsedRealtime();
230f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        float affinity = NONE;
231f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        if (extras == null) {
232f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            return null;
233f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
23422f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds        final Set<String> people = new ArraySet<>(peopleOverride);
23522f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds        final String[] notificationPeople = getExtraPeople(extras);
23622f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds        if (notificationPeople != null ) {
23722f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds            people.addAll(Arrays.asList(getExtraPeople(extras)));
238f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
239f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
2401a5dad867419e76928c84f0f4fdc4feacce43bbdChris Wren        if (VERBOSE) Slog.i(TAG, "Validating: " + key + " for " + context.getUserId());
241f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        final LinkedList<String> pendingLookups = new LinkedList<String>();
24222f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds        int personIdx = 0;
24322f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds        for (String handle : people) {
244f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            if (TextUtils.isEmpty(handle)) continue;
245f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
246f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            synchronized (mPeopleCache) {
247da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                final String cacheKey = getCacheKey(context.getUserId(), handle);
248da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                LookupResult lookupResult = mPeopleCache.get(cacheKey);
249f953664dc17dca23bd724bd64f89189c16c83263Chris Wren                if (lookupResult == null || lookupResult.isExpired()) {
250f953664dc17dca23bd724bd64f89189c16c83263Chris Wren                    pendingLookups.add(handle);
251f953664dc17dca23bd724bd64f89189c16c83263Chris Wren                } else {
2529ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                    if (DEBUG) Slog.d(TAG, "using cached lookupResult");
253f953664dc17dca23bd724bd64f89189c16c83263Chris Wren                }
254f953664dc17dca23bd724bd64f89189c16c83263Chris Wren                if (lookupResult != null) {
255f953664dc17dca23bd724bd64f89189c16c83263Chris Wren                    affinity = Math.max(affinity, lookupResult.getAffinity());
256f953664dc17dca23bd724bd64f89189c16c83263Chris Wren                }
257f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            }
25822f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds            if (++personIdx == MAX_PEOPLE) {
25922f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds                break;
26022f02b3e4acd7c6983f4d4d58b85069d5ec920abJulia Reynolds            }
261f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
262f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
263f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        // record the best available data, so far:
2642b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        affinityOut[0] = affinity;
265f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
266f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        if (pendingLookups.isEmpty()) {
2671a5dad867419e76928c84f0f4fdc4feacce43bbdChris Wren            if (VERBOSE) Slog.i(TAG, "final affinity: " + affinity);
268f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            return null;
269f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
270f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
2712b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        if (DEBUG) Slog.d(TAG, "Pending: future work scheduled for: " + key);
272da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        return new PeopleRankingReconsideration(context, key, pendingLookups);
273da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    }
274da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren
275da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    private String getCacheKey(int userId, String handle) {
276da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        return Integer.toString(userId) + ":" + handle;
277f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    }
278f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
27992af372fc60f59c628a7169d4a506e9c834c097aChris Wren    // VisibleForTesting
28092af372fc60f59c628a7169d4a506e9c834c097aChris Wren    public static String[] getExtraPeople(Bundle extras) {
281fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren        Object people = extras.get(Notification.EXTRA_PEOPLE);
282fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren        if (people instanceof String[]) {
283fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            return (String[]) people;
284f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
285f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
286fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren        if (people instanceof ArrayList) {
287fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            ArrayList arrayList = (ArrayList) people;
288fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren
289fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            if (arrayList.isEmpty()) {
290fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                return null;
291fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            }
292fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren
293fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            if (arrayList.get(0) instanceof String) {
294fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                ArrayList<String> stringArray = (ArrayList<String>) arrayList;
295fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                return stringArray.toArray(new String[stringArray.size()]);
296fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            }
297fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren
298fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            if (arrayList.get(0) instanceof CharSequence) {
299fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                ArrayList<CharSequence> charSeqList = (ArrayList<CharSequence>) arrayList;
300fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                final int N = charSeqList.size();
301fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                String[] array = new String[N];
302fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                for (int i = 0; i < N; i++) {
303fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                    array[i] = charSeqList.get(i).toString();
304fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                }
305fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                return array;
306fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            }
307fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren
308fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            return null;
309f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
310f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
311fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren        if (people instanceof String) {
312fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            String[] array = new String[1];
313fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            array[0] = (String) people;
314fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            return array;
315f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
316fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren
317fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren        if (people instanceof char[]) {
318fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            String[] array = new String[1];
319fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            array[0] = new String((char[]) people);
320fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            return array;
321f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
322f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
323fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren        if (people instanceof CharSequence) {
324fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            String[] array = new String[1];
325fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            array[0] = ((CharSequence) people).toString();
326fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            return array;
327f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
328f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
329fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren        if (people instanceof CharSequence[]) {
330fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            CharSequence[] charSeqArray = (CharSequence[]) people;
331f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            final int N = charSeqArray.length;
332fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            String[] array = new String[N];
333f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            for (int i = 0; i < N; i++) {
334fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren                array[i] = charSeqArray[i].toString();
335f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            }
336fb69da323b97ae4712054d37fb2f7d54cddb7dbdChris Wren            return array;
337f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
338f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
339f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        return null;
340f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    }
341f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
342da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    private LookupResult resolvePhoneContact(Context context, final String number) {
34344d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren        Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
34444d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren                Uri.encode(number));
345da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        return searchContacts(context, phoneUri);
34644d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren    }
34744d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren
348da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    private LookupResult resolveEmailContact(Context context, final String email) {
34944d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren        Uri numberUri = Uri.withAppendedPath(
35044d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren                ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI,
35144d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren                Uri.encode(email));
352da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        return searchContacts(context, numberUri);
353f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    }
354f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
355da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren    private LookupResult searchContacts(Context context, Uri lookupUri) {
35644d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren        LookupResult lookupResult = new LookupResult();
357f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        Cursor c = null;
358f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        try {
359da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            c = context.getContentResolver().query(lookupUri, LOOKUP_PROJECTION, null, null, null);
3609ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer            if (c == null) {
3619ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                Slog.w(TAG, "Null cursor from contacts query.");
3629ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                return lookupResult;
363f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            }
3649ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer            while (c.moveToNext()) {
3659ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                lookupResult.mergeContact(c);
3669ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer            }
3679ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer        } catch (Throwable t) {
368f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
369f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        } finally {
370f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            if (c != null) {
371f953664dc17dca23bd724bd64f89189c16c83263Chris Wren                c.close();
372f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            }
373f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
374f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        return lookupResult;
375f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    }
376f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
377f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    private static class LookupResult {
378f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000;  // 1hr
379f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
380f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        private final long mExpireMillis;
3819ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer        private float mAffinity = NONE;
382f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
38344d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren        public LookupResult() {
384f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS;
385f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
386f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
3879ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer        public void mergeContact(Cursor cursor) {
3889ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer            mAffinity = Math.max(mAffinity, VALID_CONTACT);
3899ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer
3909ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer            // Contact ID
3919ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer            int id;
39244d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren            final int idIdx = cursor.getColumnIndex(Contacts._ID);
39344d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren            if (idIdx >= 0) {
3949ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                id = cursor.getInt(idIdx);
3959ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                if (DEBUG) Slog.d(TAG, "contact _ID is: " + id);
39644d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren            } else {
3979ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                id = -1;
3989ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                Slog.i(TAG, "invalid cursor: no _ID");
39944d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren            }
4009ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer
4019ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer            // Starred
40244d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren            final int starIdx = cursor.getColumnIndex(Contacts.STARRED);
40344d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren            if (starIdx >= 0) {
4049ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                boolean isStarred = cursor.getInt(starIdx) != 0;
4059ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                if (isStarred) {
4069ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                    mAffinity = Math.max(mAffinity, STARRED_CONTACT);
4079ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                }
4089ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer                if (DEBUG) Slog.d(TAG, "contact STARRED is: " + isStarred);
40944d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren            } else {
41044d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren                if (DEBUG) Slog.d(TAG, "invalid cursor: no STARRED");
41144d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren            }
41244d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren        }
41344d81a4b86f73ecf79861bc576c90b8f8e4ba761Chris Wren
4149ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer        private boolean isExpired() {
415f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            return mExpireMillis < System.currentTimeMillis();
416f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
417f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
4189ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer        private boolean isInvalid() {
4199ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer            return mAffinity == NONE || isExpired();
420f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
421f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
422f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        public float getAffinity() {
423f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            if (isInvalid()) {
424f953664dc17dca23bd724bd64f89189c16c83263Chris Wren                return NONE;
425f953664dc17dca23bd724bd64f89189c16c83263Chris Wren            }
4269ffa50096f5ad980bdaef4d214ad1a835b9b2991Christoph Studer            return mAffinity;
427f953664dc17dca23bd724bd64f89189c16c83263Chris Wren        }
428f953664dc17dca23bd724bd64f89189c16c83263Chris Wren    }
4292b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock
4302b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock    private class PeopleRankingReconsideration extends RankingReconsideration {
4312b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        private final LinkedList<String> mPendingLookups;
432da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        private final Context mContext;
4332b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock
4342b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        private float mContactAffinity = NONE;
4355eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren        private NotificationRecord mRecord;
4362b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock
437da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren        private PeopleRankingReconsideration(Context context, String key, LinkedList<String> pendingLookups) {
4382b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock            super(key);
439da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren            mContext = context;
4402b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock            mPendingLookups = pendingLookups;
4412b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        }
4422b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock
4432b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        @Override
4442b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        public void work() {
44566dfcc50dff82e50cd64d14b9aeea1c795876364Chris Wren            long start = SystemClock.elapsedRealtime();
4461a5dad867419e76928c84f0f4fdc4feacce43bbdChris Wren            if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
44712aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            long timeStartMs = System.currentTimeMillis();
4482b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock            for (final String handle: mPendingLookups) {
4492b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                LookupResult lookupResult = null;
4502b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                final Uri uri = Uri.parse(handle);
4512b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                if ("tel".equals(uri.getScheme())) {
4522b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                    if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
453da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                    lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart());
4542b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                } else if ("mailto".equals(uri.getScheme())) {
4552b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                    if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
456da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                    lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
4572b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
4582b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                    if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
459da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                    lookupResult = searchContacts(mContext, uri);
4602b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                } else {
4612b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                    lookupResult = new LookupResult();  // invalid person for the cache
4622b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                    Slog.w(TAG, "unsupported URI " + handle);
4632b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                }
4642b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                if (lookupResult != null) {
4652b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                    synchronized (mPeopleCache) {
466da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                        final String cacheKey = getCacheKey(mContext.getUserId(), handle);
467da4bd209cffad7e47a4bc6e9f02c4bfc333d3d8dChris Wren                        mPeopleCache.put(cacheKey, lookupResult);
4682b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                    }
469f37dae7b60280279a100fb7870122f4e495c81ecChris Wren                    if (DEBUG) Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
4702b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                    mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
471f37dae7b60280279a100fb7870122f4e495c81ecChris Wren                } else {
472f37dae7b60280279a100fb7870122f4e495c81ecChris Wren                    if (DEBUG) Slog.d(TAG, "lookupResult is null");
4732b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock                }
4742b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock            }
47512aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            if (DEBUG) {
47612aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                Slog.d(TAG, "Validation finished in " + (System.currentTimeMillis() - timeStartMs) +
47712aeda802ed91a49977a22166319ce74a3352e30Christoph Studer                        "ms");
47812aeda802ed91a49977a22166319ce74a3352e30Christoph Studer            }
479723aa768f6d5b1a6696b9ac46c039d1b5be73dbfChris Wren
4805eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren            if (mRecord != null) {
4815eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren                mUsageStats.registerPeopleAffinity(mRecord, mContactAffinity > NONE,
4825eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren                        mContactAffinity == STARRED_CONTACT, false /* cached */);
4835eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren            }
4842b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        }
4852b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock
4862b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        @Override
4872b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        public void applyChangesLocked(NotificationRecord operand) {
4882b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock            float affinityBound = operand.getContactAffinity();
4892b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock            operand.setContactAffinity(Math.max(mContactAffinity, affinityBound));
4901a5dad867419e76928c84f0f4fdc4feacce43bbdChris Wren            if (VERBOSE) Slog.i(TAG, "final affinity: " + operand.getContactAffinity());
4912b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        }
4922b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock
4932b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        public float getContactAffinity() {
4942b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock            return mContactAffinity;
4952b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock        }
4965eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren
4975eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren        public void setRecord(NotificationRecord record) {
4985eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren            mRecord = record;
4995eab2b72afe5b20dc66c237b1cceedfc09de2d52Chris Wren        }
5002b122f4c2e691f0319e4f9ea5873989792bb56a6John Spurlock    }
501f953664dc17dca23bd724bd64f89189c16c83263Chris Wren}
502f953664dc17dca23bd724bd64f89189c16c83263Chris Wren
503