Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2bb8dde4 authored by Chris Wren's avatar Chris Wren Committed by Android (Google) Code Review
Browse files

Merge "support the people extra on the notification object"

parents 2271a91c dde75307
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -4453,6 +4453,7 @@ package android.app {
    ctor public Notification.Builder(android.content.Context);
    method public android.app.Notification.Builder addAction(int, java.lang.CharSequence, android.app.PendingIntent);
    method public android.app.Notification.Builder addExtras(android.os.Bundle);
    method public android.app.Notification.Builder addPerson(java.lang.String);
    method public android.app.Notification build();
    method public android.os.Bundle getExtras();
    method public deprecated android.app.Notification getNotification();
+15 −0
Original line number Diff line number Diff line
@@ -1311,6 +1311,7 @@ public class Notification implements Parcelable
        private Notification mPublicVersion = null;
        private boolean mQuantumTheme;
        private final LegacyNotificationUtil mLegacyNotificationUtil;
        private ArrayList<String> mPeople;

        /**
         * Constructs a new Builder with the defaults:
@@ -1338,6 +1339,7 @@ public class Notification implements Parcelable
            mWhen = System.currentTimeMillis();
            mAudioStreamType = STREAM_DEFAULT;
            mPriority = PRIORITY_DEFAULT;
            mPeople = new ArrayList<String>();

            // TODO: Decide on targetSdk from calling app whether to use quantum theme.
            mQuantumTheme = true;
@@ -1722,6 +1724,16 @@ public class Notification implements Parcelable
            return this;
        }

        /**
         * Add a person that is relevant to this notification.
         *
         * @see Notification#EXTRA_PEOPLE
         */
        public Builder addPerson(String handle) {
            mPeople.add(handle);
            return this;
        }

        /**
         * Merge additional metadata into this notification.
         *
@@ -2149,6 +2161,9 @@ public class Notification implements Parcelable
            if (mLargeIcon != null) {
                extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
            }
            if (!mPeople.isEmpty()) {
                extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()]));
            }
        }

        /**
+227 −0
Original line number Diff line number Diff line
/*
* Copyright (C) 2014 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.internal.notification;

import android.app.Notification;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.LruCache;
import android.util.Slog;

/**
 * This {@link NotificationScorer} attempts to validate people references.
 * Also elevates the priority of real people.
 */
public class PeopleNotificationScorer implements NotificationScorer {
    private static final String TAG = "PeopleNotificationScorer";
    private static final boolean DBG = false;

    private static final boolean ENABLE_PEOPLE_SCORER = true;
    private static final String SETTING_ENABLE_PEOPLE_SCORER = "people_scorer_enabled";
    private static final String[] LOOKUP_PROJECTION = { Contacts._ID };
    private static final int MAX_PEOPLE = 10;
    private static final int PEOPLE_CACHE_SIZE = 200;
    // see NotificationManagerService
    private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;

    protected boolean mEnabled;
    private Context mContext;

    // maps raw person handle to resolved person object
    private LruCache<String, LookupResult> mPeopleCache;

    private float findMaxContactScore(Bundle extras) {
        if (extras == null) {
            return 0f;
        }

        final String[] people = extras.getStringArray(Notification.EXTRA_PEOPLE);
        if (people == null || people.length == 0) {
            return 0f;
        }

        float rank = 0f;
        for (int personIdx = 0; personIdx < people.length && personIdx < MAX_PEOPLE; personIdx++) {
            final String handle = people[personIdx];
            if (TextUtils.isEmpty(handle)) continue;

            LookupResult lookupResult = mPeopleCache.get(handle);
            if (lookupResult == null || lookupResult.isExpired()) {
                final Uri uri = Uri.parse(handle);
                if ("tel".equals(uri.getScheme())) {
                    if (DBG) Slog.w(TAG, "checking telephone URI: " + handle);
                    lookupResult = lookupPhoneContact(handle, uri.getSchemeSpecificPart());
                } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
                    if (DBG) Slog.w(TAG, "checking lookup URI: " + handle);
                    lookupResult = resolveContactsUri(handle, uri);
                } else {
                    if (DBG) Slog.w(TAG, "unsupported URI " + handle);
                }
            } else {
                if (DBG) Slog.w(TAG, "using cached lookupResult: " + lookupResult.mId);
            }
            if (lookupResult != null) {
                rank = Math.max(rank, lookupResult.getRank());
            }
        }
        return rank;
    }

    private LookupResult lookupPhoneContact(final String handle, final String number) {
        LookupResult lookupResult = null;
        Cursor c = null;
        try {
            Uri numberUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
                    Uri.encode(number));
            c = mContext.getContentResolver().query(numberUri, LOOKUP_PROJECTION, null, null, null);
            if (c != null && c.getCount() > 0) {
                c.moveToFirst();
                final int idIdx = c.getColumnIndex(Contacts._ID);
                final int id = c.getInt(idIdx);
                if (DBG) Slog.w(TAG, "is valid: " + id);
                lookupResult = new LookupResult(id);
            }
        } catch(Throwable t) {
            Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
        } finally {
            if (c != null) {
                c.close();
            }
        }
        if (lookupResult == null) {
            lookupResult = new LookupResult(LookupResult.INVALID_ID);
        }
        mPeopleCache.put(handle, lookupResult);
        return lookupResult;
    }

    private LookupResult resolveContactsUri(String handle, final Uri personUri) {
        LookupResult lookupResult = null;
        Cursor c = null;
        try {
            c = mContext.getContentResolver().query(personUri, LOOKUP_PROJECTION, null, null, null);
            if (c != null && c.getCount() > 0) {
                c.moveToFirst();
                final int idIdx = c.getColumnIndex(Contacts._ID);
                final int id = c.getInt(idIdx);
                if (DBG) Slog.w(TAG, "is valid: " + id);
                lookupResult = new LookupResult(id);
            }
        } catch(Throwable t) {
            Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
        } finally {
            if (c != null) {
                c.close();
            }
        }
        if (lookupResult == null) {
            lookupResult = new LookupResult(LookupResult.INVALID_ID);
        }
        mPeopleCache.put(handle, lookupResult);
        return lookupResult;
    }

    private final static int clamp(int x, int low, int high) {
        return (x < low) ? low : ((x > high) ? high : x);
    }

    // TODO: rework this function before shipping
    private static int priorityBumpMap(int incomingScore) {
        //assumption is that scale runs from [-2*pm, 2*pm]
        int pm = NOTIFICATION_PRIORITY_MULTIPLIER;
        int theScore = incomingScore;
        // enforce input in range
        theScore = clamp(theScore, -2 * pm, 2 * pm);
        if (theScore != incomingScore) return incomingScore;
        // map -20 -> -20 and -10 -> 5 (when pm = 10)
        if (theScore <= -pm) {
            theScore += 1.5 * (theScore + 2 * pm);
        } else {
            // map 0 -> 10, 10 -> 15, 20 -> 20;
            theScore += 0.5 * (2 * pm - theScore);
        }
        if (DBG) Slog.v(TAG, "priorityBumpMap: score before: " + incomingScore
                + ", score after " + theScore + ".");
        return theScore;
    }

    @Override
    public void initialize(Context context) {
        if (DBG) Slog.v(TAG, "Initializing  " + getClass().getSimpleName() + ".");
        mContext = context;
        mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
        mEnabled = ENABLE_PEOPLE_SCORER && 1 == Settings.Global.getInt(
                mContext.getContentResolver(), SETTING_ENABLE_PEOPLE_SCORER, 0);
    }

    @Override
    public int getScore(Notification notification, int score) {
        if (notification == null || !mEnabled) {
            if (DBG) Slog.w(TAG, "empty notification? scorer disabled?");
            return score;
        }
        float contactScore = findMaxContactScore(notification.extras);
        if (contactScore > 0f) {
            if (DBG) Slog.v(TAG, "Notification references a real contact. Promoted!");
            score = priorityBumpMap(score);
        } else {
            if (DBG) Slog.v(TAG, "Notification lacks any valid contact reference. Not promoted!");
        }
        return score;
    }

    private static class LookupResult {
        private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000;  // 1hr
        public static final int INVALID_ID = -1;

        private final long mExpireMillis;
        private int mId;

        public LookupResult(int id) {
            mId = id;
            mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS;
        }

        public boolean isExpired() {
            return mExpireMillis < System.currentTimeMillis();
        }

        public boolean isInvalid() {
            return mId == INVALID_ID || isExpired();
        }

        public float getRank() {
            if (isInvalid()) {
                return 0f;
            } else {
                return 1f;  // TODO: finer grained score
            }
        }

        public LookupResult setId(int id) {
            mId = id;
            return this;
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -1370,7 +1370,7 @@
    </string-array>

    <string-array name="config_notificationScorers">
        <item>com.android.internal.notification.DemoContactNotificationScorer</item>
        <item>com.android.internal.notification.PeopleNotificationScorer</item>
    </string-array>

    <!-- Flag indicating that this device does not rotate and will always remain in its default