OSDN Git Service

More usage tracking
authorAmith Yamasani <yamasani@google.com>
Fri, 17 Apr 2015 17:02:15 +0000 (10:02 -0700)
committerAmith Yamasani <yamasani@google.com>
Fri, 24 Apr 2015 23:12:01 +0000 (16:12 -0700)
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
api/system-current.txt
core/java/android/app/INotificationManager.aidl
core/java/android/service/notification/NotificationListenerService.java
packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
services/core/java/com/android/server/notification/NotificationManagerService.java
services/core/java/com/android/server/notification/NotificationRecord.java
services/usage/java/com/android/server/usage/UsageStatsService.java

index 0d96042..3901721 100644 (file)
@@ -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
index 18199b7..f3ceadb 100644 (file)
@@ -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
index e275df0..ac8d5d8 100644 (file)
@@ -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);
index cc7f880..35b8819 100644 (file)
@@ -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}.
      *
      * <p>
index de4874f..e542264 100644 (file)
@@ -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<Entry> 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();
             }
index c854d63..d058892 100644 (file)
@@ -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) {
index 1008653..25998da 100644 (file)
@@ -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<String> 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,
index 5569a09..e106a4a 100644 (file)
@@ -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;
     }
index edeeaba..04984d3 100644 (file)
@@ -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<ComponentName> 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)) {