Loading api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -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(); core/java/android/app/Notification.java +15 −0 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -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()])); } } /** Loading core/java/com/android/internal/notification/PeopleNotificationScorer.java 0 → 100644 +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; } } } core/res/res/values/config.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading
api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -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();
core/java/android/app/Notification.java +15 −0 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -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()])); } } /** Loading
core/java/com/android/internal/notification/PeopleNotificationScorer.java 0 → 100644 +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; } } }
core/res/res/values/config.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading