summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
authorChris Wren <cwren@android.com>2014-04-17 10:01:54 -0400
committerChris Wren <cwren@android.com>2014-05-09 15:48:53 -0400
commitf953664dc17dca23bd724bd64f89189c16c83263 (patch)
tree4ed0cc919c669a57698daa00e439247781487623 /core/java
parent91da58ded2e7c0dd3d045244cc78eb0d788736a7 (diff)
downloadframeworks_base-f953664dc17dca23bd724bd64f89189c16c83263.zip
frameworks_base-f953664dc17dca23bd724bd64f89189c16c83263.tar.gz
frameworks_base-f953664dc17dca23bd724bd64f89189c16c83263.tar.bz2
notification ranking infrastructure
Added an ordered list of notifications (n.b. a complete ordering). Added a mechanism for ranking to be updated asynchronously Added onNotificationRankingUpdate to NotificationListeners Added an opaque order update object and a convenience comparator that uses it to sort notifications for listeners Repurpose scorers to be ranking preprocessors. The preprocessors will perform heavy-weight validation of the notification object and memoize the results to improve efficiency of the ranking comparator. Current internal comparator implements status quo ordering, except that notes with a valid contact sort to the top of their priority bucket. Change-Id: I7244c65944a9657df41fb313b3cb5a52e149709d
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/Notification.java4
-rw-r--r--core/java/android/service/notification/INotificationListener.aidl10
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java82
-rw-r--r--core/java/android/service/notification/NotificationOrderUpdate.aidl19
-rw-r--r--core/java/android/service/notification/NotificationOrderUpdate.java62
-rw-r--r--core/java/com/android/internal/notification/DemoContactNotificationScorer.java188
-rw-r--r--core/java/com/android/internal/notification/NotificationScorer.java27
-rw-r--r--core/java/com/android/internal/notification/PeopleNotificationScorer.java227
8 files changed, 151 insertions, 468 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index bba6caf..76a6a8e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -659,8 +659,8 @@ public class Notification implements Parcelable
/**
* @hide
- * Extra added by NotificationManagerService to indicate whether a NotificationScorer
- * modified the Notifications's score.
+ * Extra added by NotificationManagerService to indicate whether
+ * the Notifications's score has been modified.
*/
public static final String EXTRA_SCORE_MODIFIED = "android.scoreModified";
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index d4b29d8..d4919eb 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -17,11 +17,15 @@
package android.service.notification;
import android.service.notification.StatusBarNotification;
+import android.service.notification.NotificationOrderUpdate;
/** @hide */
oneway interface INotificationListener
{
- void onListenerConnected(in String[] notificationKeys);
- void onNotificationPosted(in StatusBarNotification notification);
- void onNotificationRemoved(in StatusBarNotification notification);
+ void onListenerConnected(in NotificationOrderUpdate update);
+ void onNotificationPosted(in StatusBarNotification notification,
+ in NotificationOrderUpdate update);
+ void onNotificationRemoved(in StatusBarNotification notification,
+ in NotificationOrderUpdate update);
+ void onNotificationOrderUpdate(in NotificationOrderUpdate update);
} \ No newline at end of file
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 3673f03..a94f45a 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -22,10 +22,13 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.UserHandle;
import android.util.Log;
+import java.util.Comparator;
+import java.util.HashMap;
+
/**
* A service that receives calls from the system when new notifications are posted or removed.
* <p>To extend this class, you must declare the service in your manifest file with
@@ -46,6 +49,7 @@ public abstract class NotificationListenerService extends Service {
+ "[" + getClass().getSimpleName() + "]";
private INotificationListenerWrapper mWrapper = null;
+ private String[] mNotificationKeys;
private INotificationManager mNoMan;
@@ -95,6 +99,15 @@ public abstract class NotificationListenerService extends Service {
// optional
}
+ /**
+ * Implement this method to be notified when the notification order cahnges.
+ *
+ * Call {@link #getOrderedNotificationKeys()} to retrieve the new order.
+ */
+ public void onNotificationOrderUpdate() {
+ // optional
+ }
+
private final INotificationManager getNotificationInterface() {
if (mNoMan == null) {
mNoMan = INotificationManager.Stub.asInterface(
@@ -202,7 +215,7 @@ public abstract class NotificationListenerService extends Service {
* Request the list of outstanding notifications (that is, those that are visible to the
* current user). Useful when you don't know what's already been posted.
*
- * @return An array of active notifications.
+ * @return An array of active notifications, sorted in natural order.
*/
public StatusBarNotification[] getActiveNotifications() {
return getActiveNotifications(null /*all*/);
@@ -213,7 +226,8 @@ public abstract class NotificationListenerService extends Service {
* current user). Useful when you don't know what's already been posted.
*
* @param keys A specific list of notification keys, or {@code null} for all.
- * @return An array of active notifications.
+ * @return An array of active notifications, sorted in natural order
+ * if {@code keys} is {@code null}.
*/
public StatusBarNotification[] getActiveNotifications(String[] keys) {
if (!isBound()) return null;
@@ -226,21 +240,15 @@ public abstract class NotificationListenerService extends Service {
}
/**
- * Request the list of outstanding notification keys(that is, those that are visible to the
- * current user). You can use the notification keys for subsequent retrieval via
+ * Request the list of notification keys in their current natural order.
+ * You can use the notification keys for subsequent retrieval via
* {@link #getActiveNotifications(String[]) or dismissal via
* {@link #cancelNotifications(String[]).
*
- * @return An array of active notification keys.
+ * @return An array of active notification keys, in their natural order.
*/
- public String[] getActiveNotificationKeys() {
- if (!isBound()) return null;
- try {
- return getNotificationInterface().getActiveNotificationKeysFromListener(mWrapper);
- } catch (android.os.RemoteException ex) {
- Log.v(TAG, "Unable to contact notification manager", ex);
- }
- return null;
+ public String[] getOrderedNotificationKeys() {
+ return mNotificationKeys;
}
@Override
@@ -261,28 +269,60 @@ public abstract class NotificationListenerService extends Service {
private class INotificationListenerWrapper extends INotificationListener.Stub {
@Override
- public void onNotificationPosted(StatusBarNotification sbn) {
+ public void onNotificationPosted(StatusBarNotification sbn,
+ NotificationOrderUpdate update) {
try {
- NotificationListenerService.this.onNotificationPosted(sbn);
+ // protect subclass from concurrent modifications of (@link mNotificationKeys}.
+ synchronized (mWrapper) {
+ updateNotificationKeys(update);
+ NotificationListenerService.this.onNotificationPosted(sbn);
+ }
} catch (Throwable t) {
- Log.w(TAG, "Error running onNotificationPosted", t);
+ Log.w(TAG, "Error running onOrderedNotificationPosted", t);
}
}
@Override
- public void onNotificationRemoved(StatusBarNotification sbn) {
+ public void onNotificationRemoved(StatusBarNotification sbn,
+ NotificationOrderUpdate update) {
try {
- NotificationListenerService.this.onNotificationRemoved(sbn);
+ // protect subclass from concurrent modifications of (@link mNotificationKeys}.
+ synchronized (mWrapper) {
+ updateNotificationKeys(update);
+ NotificationListenerService.this.onNotificationRemoved(sbn);
+ }
} catch (Throwable t) {
Log.w(TAG, "Error running onNotificationRemoved", t);
}
}
@Override
- public void onListenerConnected(String[] notificationKeys) {
+ public void onListenerConnected(NotificationOrderUpdate update) {
try {
- NotificationListenerService.this.onListenerConnected(notificationKeys);
+ // protect subclass from concurrent modifications of (@link mNotificationKeys}.
+ synchronized (mWrapper) {
+ updateNotificationKeys(update);
+ NotificationListenerService.this.onListenerConnected(mNotificationKeys);
+ }
} catch (Throwable t) {
Log.w(TAG, "Error running onListenerConnected", t);
}
}
+ @Override
+ public void onNotificationOrderUpdate(NotificationOrderUpdate update)
+ throws RemoteException {
+ try {
+ // protect subclass from concurrent modifications of (@link mNotificationKeys}.
+ synchronized (mWrapper) {
+ updateNotificationKeys(update);
+ NotificationListenerService.this.onNotificationOrderUpdate();
+ }
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onNotificationOrderUpdate", t);
+ }
+ }
+ }
+
+ private void updateNotificationKeys(NotificationOrderUpdate update) {
+ // TODO: avoid garbage by comparing the lists
+ mNotificationKeys = update.getOrderedKeys();
}
}
diff --git a/core/java/android/service/notification/NotificationOrderUpdate.aidl b/core/java/android/service/notification/NotificationOrderUpdate.aidl
new file mode 100644
index 0000000..5d50641
--- /dev/null
+++ b/core/java/android/service/notification/NotificationOrderUpdate.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.service.notification;
+
+parcelable NotificationOrderUpdate;
diff --git a/core/java/android/service/notification/NotificationOrderUpdate.java b/core/java/android/service/notification/NotificationOrderUpdate.java
new file mode 100644
index 0000000..20e19a3
--- /dev/null
+++ b/core/java/android/service/notification/NotificationOrderUpdate.java
@@ -0,0 +1,62 @@
+/*
+ * 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 android.service.notification;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class NotificationOrderUpdate implements Parcelable {
+ // TODO replace this with an update instead of the whole array
+ private final String[] mKeys;
+
+ /** @hide */
+ public NotificationOrderUpdate(String[] keys) {
+ this.mKeys = keys;
+ }
+
+ public NotificationOrderUpdate(Parcel in) {
+ this.mKeys = in.readStringArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStringArray(this.mKeys);
+ }
+
+ public static final Parcelable.Creator<NotificationOrderUpdate> CREATOR
+ = new Parcelable.Creator<NotificationOrderUpdate>() {
+ public NotificationOrderUpdate createFromParcel(Parcel parcel) {
+ return new NotificationOrderUpdate(parcel);
+ }
+
+ public NotificationOrderUpdate[] newArray(int size) {
+ return new NotificationOrderUpdate[size];
+ }
+ };
+
+ /**
+ * @hide
+ * @return ordered list of keys
+ */
+ String[] getOrderedKeys() {
+ return mKeys;
+ }
+}
diff --git a/core/java/com/android/internal/notification/DemoContactNotificationScorer.java b/core/java/com/android/internal/notification/DemoContactNotificationScorer.java
deleted file mode 100644
index f484724..0000000
--- a/core/java/com/android/internal/notification/DemoContactNotificationScorer.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
-* Copyright (C) 2013 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.Settings;
-import android.text.SpannableString;
-import android.util.Slog;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * This NotificationScorer bumps up the priority of notifications that contain references to the
- * display names of starred contacts. The references it picks up are spannable strings which, in
- * their entirety, match the display name of some starred contact. The magnitude of the bump ranges
- * from 0 to 15 (assuming NOTIFICATION_PRIORITY_MULTIPLIER = 10) depending on the initial score, and
- * the mapping is defined by priorityBumpMap. In a production version of this scorer, a notification
- * extra will be used to specify contact identifiers.
- */
-
-public class DemoContactNotificationScorer implements NotificationScorer {
- private static final String TAG = "DemoContactNotificationScorer";
- private static final boolean DBG = false;
-
- protected static final boolean ENABLE_CONTACT_SCORER = true;
- private static final String SETTING_ENABLE_SCORER = "contact_scorer_enabled";
- protected boolean mEnabled;
-
- // see NotificationManagerService
- private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
-
- private Context mContext;
-
- private static final List<String> RELEVANT_KEYS_LIST = Arrays.asList(
- Notification.EXTRA_INFO_TEXT, Notification.EXTRA_TEXT, Notification.EXTRA_TEXT_LINES,
- Notification.EXTRA_SUB_TEXT, Notification.EXTRA_TITLE
- );
-
- private static final String[] PROJECTION = new String[] {
- ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME
- };
-
- private static final Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI;
-
- private static List<String> extractSpannedStrings(CharSequence charSequence) {
- if (charSequence == null) return Collections.emptyList();
- if (!(charSequence instanceof SpannableString)) {
- return Arrays.asList(charSequence.toString());
- }
- SpannableString spannableString = (SpannableString)charSequence;
- // get all spans
- Object[] ssArr = spannableString.getSpans(0, spannableString.length(), Object.class);
- // spanned string sequences
- ArrayList<String> sss = new ArrayList<String>();
- for (Object spanObj : ssArr) {
- try {
- sss.add(spannableString.subSequence(spannableString.getSpanStart(spanObj),
- spannableString.getSpanEnd(spanObj)).toString());
- } catch(StringIndexOutOfBoundsException e) {
- Slog.e(TAG, "Bad indices when extracting spanned subsequence", e);
- }
- }
- return sss;
- };
-
- private static String getQuestionMarksInParens(int n) {
- StringBuilder sb = new StringBuilder("(");
- for (int i = 0; i < n; i++) {
- if (sb.length() > 1) sb.append(',');
- sb.append('?');
- }
- sb.append(")");
- return sb.toString();
- }
-
- private boolean hasStarredContact(Bundle extras) {
- if (extras == null) return false;
- ArrayList<String> qStrings = new ArrayList<String>();
- // build list to query against the database for display names.
- for (String rk: RELEVANT_KEYS_LIST) {
- if (extras.get(rk) == null) {
- continue;
- } else if (extras.get(rk) instanceof CharSequence) {
- qStrings.addAll(extractSpannedStrings((CharSequence) extras.get(rk)));
- } else if (extras.get(rk) instanceof CharSequence[]) {
- // this is intended for Notification.EXTRA_TEXT_LINES
- for (CharSequence line: (CharSequence[]) extras.get(rk)){
- qStrings.addAll(extractSpannedStrings(line));
- }
- } else {
- Slog.w(TAG, "Strange, the extra " + rk + " is of unexpected type.");
- }
- }
- if (qStrings.isEmpty()) return false;
- String[] qStringsArr = qStrings.toArray(new String[qStrings.size()]);
-
- String selection = ContactsContract.Contacts.DISPLAY_NAME + " IN "
- + getQuestionMarksInParens(qStringsArr.length) + " AND "
- + ContactsContract.Contacts.STARRED+" ='1'";
-
- Cursor c = null;
- try {
- c = mContext.getContentResolver().query(
- CONTACTS_URI, PROJECTION, selection, qStringsArr, null);
- if (c != null) return c.getCount() > 0;
- } catch(Throwable t) {
- Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
- } finally {
- if (c != null) {
- c.close();
- }
- }
- return false;
- }
-
- private final static int clamp(int x, int low, int high) {
- return (x < low) ? low : ((x > high) ? high : x);
- }
-
- 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;
- mEnabled = ENABLE_CONTACT_SCORER && 1 == Settings.Global.getInt(
- mContext.getContentResolver(), SETTING_ENABLE_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;
- }
- boolean hasStarredPriority = hasStarredContact(notification.extras);
-
- if (DBG) {
- if (hasStarredPriority) {
- Slog.v(TAG, "Notification references starred contact. Promoted!");
- } else {
- Slog.v(TAG, "Notification lacks any starred contact reference. Not promoted!");
- }
- }
- if (hasStarredPriority) score = priorityBumpMap(score);
- return score;
- }
-}
-
diff --git a/core/java/com/android/internal/notification/NotificationScorer.java b/core/java/com/android/internal/notification/NotificationScorer.java
deleted file mode 100644
index 863c08c..0000000
--- a/core/java/com/android/internal/notification/NotificationScorer.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-* Copyright (C) 2013 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;
-
-public interface NotificationScorer {
-
- public void initialize(Context context);
- public int getScore(Notification notification, int score);
-
-}
diff --git a/core/java/com/android/internal/notification/PeopleNotificationScorer.java b/core/java/com/android/internal/notification/PeopleNotificationScorer.java
deleted file mode 100644
index efb5f63..0000000
--- a/core/java/com/android/internal/notification/PeopleNotificationScorer.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
-* 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;
- }
- }
-}
-