From f47e51ec605fccf7fed9e50d1adc98fbd4e8b340 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Fri, 17 Apr 2015 10:02:15 -0700 Subject: More usage tracking Notification listeners can now report that a notification has been seen by the user and that package is then marked as active. Bug: 20066058 Change-Id: I336040a52c44c21fd0d78b02ec9a19d448c64b40 --- api/current.txt | 17 ++++----- api/system-current.txt | 17 ++++----- core/java/android/app/INotificationManager.aidl | 2 ++ .../notification/NotificationListenerService.java | 14 ++++++++ .../android/systemui/statusbar/BaseStatusBar.java | 21 +++++++++++ .../systemui/statusbar/phone/PhoneStatusBar.java | 2 ++ .../notification/NotificationManagerService.java | 41 ++++++++++++++++++++++ .../server/notification/NotificationRecord.java | 12 +++++++ .../android/server/usage/UsageStatsService.java | 5 ++- 9 files changed, 112 insertions(+), 19 deletions(-) diff --git a/api/current.txt b/api/current.txt index 0d96042..3901721 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16430,6 +16430,14 @@ package android.media { method public abstract void onAudioDeviceConnection(); } + public abstract interface OnAudioRecordRoutingListener { + method public abstract void onAudioRecordRouting(android.media.AudioRecord); + } + + public abstract interface OnAudioTrackRoutingListener { + method public abstract void onAudioTrackRouting(android.media.AudioTrack); + } + public final class PlaybackSettings { ctor public PlaybackSettings(); method public android.media.PlaybackSettings allowDefaults(); @@ -16448,14 +16456,6 @@ package android.media { field public static final int AUDIO_STRETCH_MODE_VOICE = 1; // 0x1 } - public abstract interface OnAudioRecordRoutingListener { - method public abstract void onAudioRecordRouting(android.media.AudioRecord); - } - - public abstract interface OnAudioTrackRoutingListener { - method public abstract void onAudioTrackRouting(android.media.AudioTrack); - } - public final class Rating implements android.os.Parcelable { method public int describeContents(); method public float getPercentRating(); @@ -28912,6 +28912,7 @@ package android.service.notification { method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap); method public final void requestInterruptionFilter(int); method public final void requestListenerHints(int); + method public final void setNotificationsShown(java.lang.String[]); field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4 field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1 diff --git a/api/system-current.txt b/api/system-current.txt index 18199b7..f3ceadb 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -17646,6 +17646,14 @@ package android.media { method public abstract void onAudioDeviceConnection(); } + public abstract interface OnAudioRecordRoutingListener { + method public abstract void onAudioRecordRouting(android.media.AudioRecord); + } + + public abstract interface OnAudioTrackRoutingListener { + method public abstract void onAudioTrackRouting(android.media.AudioTrack); + } + public final class PlaybackSettings { ctor public PlaybackSettings(); method public android.media.PlaybackSettings allowDefaults(); @@ -17664,14 +17672,6 @@ package android.media { field public static final int AUDIO_STRETCH_MODE_VOICE = 1; // 0x1 } - public abstract interface OnAudioRecordRoutingListener { - method public abstract void onAudioRecordRouting(android.media.AudioRecord); - } - - public abstract interface OnAudioTrackRoutingListener { - method public abstract void onAudioTrackRouting(android.media.AudioTrack); - } - public final class Rating implements android.os.Parcelable { method public int describeContents(); method public float getPercentRating(); @@ -30957,6 +30957,7 @@ package android.service.notification { method public void registerAsSystemService(android.content.Context, android.content.ComponentName, int) throws android.os.RemoteException; method public final void requestInterruptionFilter(int); method public final void requestListenerHints(int); + method public final void setNotificationsShown(java.lang.String[]); method public final void setOnNotificationPostedTrim(int); method public void unregisterAsSystemService() throws android.os.RemoteException; field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index e275df0..ac8d5d8 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -67,6 +67,8 @@ interface INotificationManager void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id); void cancelNotificationsFromListener(in INotificationListener token, in String[] keys); + void setNotificationsShownFromListener(in INotificationListener token, in String[] keys); + ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim); void requestHintsFromListener(in INotificationListener token, int hints); int getHintsFromListener(in INotificationListener token); diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index cc7f880..35b8819 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -358,6 +358,20 @@ public abstract class NotificationListenerService extends Service { } /** + * Inform the notification manager that these notifications have been viewed by the + * user. + * @param keys Notifications to mark as seen. + */ + public final void setNotificationsShown(String[] keys) { + if (!isBound()) return; + try { + getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + + /** * Sets the notification trim that will be received via {@link #onNotificationPosted}. * *

diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index de4874f..e542264 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -700,6 +700,26 @@ public abstract class BaseStatusBar extends SystemUI implements return isCurrentProfile(notificationUserId); } + protected void setNotificationShown(StatusBarNotification n) { + mNotificationListener.setNotificationsShown(new String[] { n.getKey() }); + } + + protected void setNotificationsShown(String[] keys) { + mNotificationListener.setNotificationsShown(keys); + } + + protected void setNotificationsShownAll() { + ArrayList activeNotifications = mNotificationData.getActiveNotifications(); + final int N = activeNotifications.size(); + + String[] keys = new String[N]; + for (int i = 0; i < N; i++) { + NotificationData.Entry entry = activeNotifications.get(i); + keys[i] = entry.key; + } + setNotificationsShown(keys); + } + protected boolean isCurrentProfile(int userId) { synchronized (mCurrentProfiles) { return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null; @@ -1681,6 +1701,7 @@ public abstract class BaseStatusBar extends SystemUI implements boolean clearNotificationEffects = (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); mBarService.onPanelRevealed(clearNotificationEffects); + setNotificationsShownAll(); } else { mBarService.onPanelHidden(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index c854d63..d058892 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -1119,6 +1119,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, boolean isHeadsUped = mUseHeadsUp && shouldInterrupt(notification); if (isHeadsUped) { mHeadsUpManager.showNotification(shadeEntry); + // Mark as seen immediately + setNotificationShown(notification); } if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1008653..25998da 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -36,6 +36,9 @@ import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.app.PendingIntent; import android.app.StatusBarManager; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -97,6 +100,7 @@ import android.widget.Toast; import com.android.internal.R; import com.android.internal.util.FastXmlSerializer; import com.android.server.EventLogTags; +import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.lights.Light; import com.android.server.lights.LightsManager; @@ -236,6 +240,7 @@ public class NotificationManagerService extends SystemService { ArrayList mLights = new ArrayList<>(); private AppOpsManager mAppOps; + private UsageStatsManagerInternal mAppUsageStats; private Archive mArchive; @@ -871,6 +876,7 @@ public class NotificationManagerService extends SystemService { mAm = ActivityManagerNative.getDefault(); mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); mHandler = new WorkerHandler(); mRankingThread.start(); @@ -1405,6 +1411,41 @@ public class NotificationManagerService extends SystemService { } } + @Override + public void setNotificationsShownFromListener(INotificationListener token, String[] keys) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + long identity = Binder.clearCallingIdentity(); + try { + synchronized (mNotificationList) { + final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + if (keys != null) { + final int N = keys.length; + for (int i = 0; i < N; i++) { + NotificationRecord r = mNotificationsByKey.get(keys[i]); + if (r == null) continue; + final int userId = r.sbn.getUserId(); + if (userId != info.userid && userId != UserHandle.USER_ALL && + !mUserProfiles.isCurrentProfile(userId)) { + throw new SecurityException("Disallowed call from listener: " + + info.service); + } + if (!r.isSeen()) { + if (DBG) Slog.d(TAG, "Marking notification as seen " + keys[i]); + mAppUsageStats.reportEvent(r.sbn.getPackageName(), + userId == UserHandle.USER_ALL ? UserHandle.USER_OWNER + : userId, + UsageEvents.Event.INTERACTION); + r.setSeen(); + } + } + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + private void cancelNotificationFromListenerLocked(ManagedServiceInfo info, int callingUid, int callingPid, String pkg, String tag, int id, int userId) { cancelNotification(callingUid, callingPid, pkg, tag, id, 0, diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 5569a09..e106a4a 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -50,6 +50,8 @@ public final class NotificationRecord { NotificationUsageStats.SingleNotificationStats stats; boolean isCanceled; int score; + /** Whether the notification was seen by the user via one of the notification listeners. */ + boolean mIsSeen; // These members are used by NotificationSignalExtractors // to communicate with the ranking module. @@ -301,6 +303,16 @@ public final class NotificationRecord { return mGlobalSortKey; } + /** Check if any of the listeners have marked this notification as seen by the user. */ + public boolean isSeen() { + return mIsSeen; + } + + /** Mark the notification as seen by the user. */ + public void setSeen() { + mIsSeen = true; + } + public void setAuthoritativeRank(int authoritativeRank) { mAuthoritativeRank = authoritativeRank; } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index edeeaba..04984d3 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -28,6 +28,7 @@ import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; +import android.appwidget.AppWidgetManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -435,9 +436,7 @@ public class UsageStatsService extends SystemService implements DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class); if (dpm == null) return false; List components = dpm.getActiveAdminsAsUser(userId); - if (components == null) { - return false; - } + if (components == null) return false; final int size = components.size(); for (int i = 0; i < size; i++) { if (components.get(i).getPackageName().equals(packageName)) { -- cgit v1.1