blob: 8e8e7180a8ab4322093114d7628ad071b5492f42 [file] [log] [blame]
Eliot Courtney3985ad52017-11-17 16:51:52 +09001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16package com.android.systemui.statusbar;
17
18import android.content.Context;
19import android.os.Handler;
20import android.os.RemoteException;
21import android.os.ServiceManager;
22import android.os.SystemClock;
23import android.service.notification.NotificationListenerService;
24import android.util.ArraySet;
25import android.util.Log;
26
27import com.android.internal.annotations.VisibleForTesting;
28import com.android.internal.statusbar.IStatusBarService;
29import com.android.internal.statusbar.NotificationVisibility;
Eliot Courtney6c313d32017-12-14 19:57:51 +090030import com.android.systemui.Dependency;
Eliot Courtney3985ad52017-11-17 16:51:52 +090031import com.android.systemui.UiOffloadThread;
Eliot Courtney3985ad52017-11-17 16:51:52 +090032
33import java.util.ArrayList;
34import java.util.Collection;
35import java.util.Collections;
36
37/**
38 * Handles notification logging, in particular, logging which notifications are visible and which
39 * are not.
40 */
41public class NotificationLogger {
42 private static final String TAG = "NotificationLogger";
43
44 /** The minimum delay in ms between reports of notification visibility. */
45 private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
46
47 /** Keys of notifications currently visible to the user. */
48 private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
49 new ArraySet<>();
Eliot Courtney6c313d32017-12-14 19:57:51 +090050
51 // Dependencies:
52 private final NotificationListenerService mNotificationListener =
53 Dependency.get(NotificationListener.class);
54 private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
Eliot Courtney3985ad52017-11-17 16:51:52 +090055
Eliot Courtney4a96b362017-12-14 19:38:52 +090056 protected NotificationEntryManager mEntryManager;
Eliot Courtney3985ad52017-11-17 16:51:52 +090057 protected Handler mHandler = new Handler();
58 protected IStatusBarService mBarService;
59 private long mLastVisibilityReportUptimeMs;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090060 private NotificationListContainer mListContainer;
Eliot Courtney3985ad52017-11-17 16:51:52 +090061
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090062 protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
63 new OnChildLocationsChangedListener() {
Eliot Courtney3985ad52017-11-17 16:51:52 +090064 @Override
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090065 public void onChildLocationsChanged() {
Eliot Courtney3985ad52017-11-17 16:51:52 +090066 if (mHandler.hasCallbacks(mVisibilityReporter)) {
67 // Visibilities will be reported when the existing
68 // callback is executed.
69 return;
70 }
71 // Calculate when we're allowed to run the visibility
72 // reporter. Note that this timestamp might already have
73 // passed. That's OK, the callback will just be executed
74 // ASAP.
75 long nextReportUptimeMs =
76 mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
77 mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
78 }
79 };
80
81 // Tracks notifications currently visible in mNotificationStackScroller and
82 // emits visibility events via NoMan on changes.
83 protected final Runnable mVisibilityReporter = new Runnable() {
84 private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
85 new ArraySet<>();
86 private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
87 new ArraySet<>();
88 private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications =
89 new ArraySet<>();
90
91 @Override
92 public void run() {
93 mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
94
95 // 1. Loop over mNotificationData entries:
96 // A. Keep list of visible notifications.
97 // B. Keep list of previously hidden, now visible notifications.
98 // 2. Compute no-longer visible notifications by removing currently
99 // visible notifications from the set of previously visible
100 // notifications.
101 // 3. Report newly visible and no-longer visible notifications.
102 // 4. Keep currently visible notifications for next report.
Eliot Courtney4a96b362017-12-14 19:38:52 +0900103 ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
104 .getNotificationData().getActiveNotifications();
Eliot Courtney3985ad52017-11-17 16:51:52 +0900105 int N = activeNotifications.size();
106 for (int i = 0; i < N; i++) {
107 NotificationData.Entry entry = activeNotifications.get(i);
108 String key = entry.notification.getKey();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900109 boolean isVisible = mListContainer.isInVisibleLocation(entry.row);
Dieter Hsud39f0d52018-04-14 02:08:30 +0800110 NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible);
Eliot Courtney3985ad52017-11-17 16:51:52 +0900111 boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
112 if (isVisible) {
113 // Build new set of visible notifications.
114 mTmpCurrentlyVisibleNotifications.add(visObj);
115 if (!previouslyVisible) {
116 mTmpNewlyVisibleNotifications.add(visObj);
117 }
118 } else {
119 // release object
120 visObj.recycle();
121 }
122 }
123 mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications);
124 mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
125
126 logNotificationVisibilityChanges(
127 mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications);
128
129 recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
130 mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
131
132 recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
133 mTmpCurrentlyVisibleNotifications.clear();
134 mTmpNewlyVisibleNotifications.clear();
135 mTmpNoLongerVisibleNotifications.clear();
136 }
137 };
138
Eliot Courtney6c313d32017-12-14 19:57:51 +0900139 public NotificationLogger() {
Eliot Courtney3985ad52017-11-17 16:51:52 +0900140 mBarService = IStatusBarService.Stub.asInterface(
141 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
142 }
143
Eliot Courtney4a96b362017-12-14 19:38:52 +0900144 public void setUpWithEntryManager(NotificationEntryManager entryManager,
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900145 NotificationListContainer listContainer) {
Eliot Courtney4a96b362017-12-14 19:38:52 +0900146 mEntryManager = entryManager;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900147 mListContainer = listContainer;
Eliot Courtney3985ad52017-11-17 16:51:52 +0900148 }
149
150 public void stopNotificationLogging() {
151 // Report all notifications as invisible and turn down the
152 // reporter.
153 if (!mCurrentlyVisibleNotifications.isEmpty()) {
154 logNotificationVisibilityChanges(
155 Collections.emptyList(), mCurrentlyVisibleNotifications);
156 recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
157 }
158 mHandler.removeCallbacks(mVisibilityReporter);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900159 mListContainer.setChildLocationsChangedListener(null);
Eliot Courtney3985ad52017-11-17 16:51:52 +0900160 }
161
162 public void startNotificationLogging() {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900163 mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
Eliot Courtney3985ad52017-11-17 16:51:52 +0900164 // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
165 // cause the scroller to emit child location events. Hence generate
166 // one ourselves to guarantee that we're reporting visible
167 // notifications.
168 // (Note that in cases where the scroller does emit events, this
169 // additional event doesn't break anything.)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900170 mNotificationLocationsChangedListener.onChildLocationsChanged();
Eliot Courtney3985ad52017-11-17 16:51:52 +0900171 }
172
173 private void logNotificationVisibilityChanges(
174 Collection<NotificationVisibility> newlyVisible,
175 Collection<NotificationVisibility> noLongerVisible) {
176 if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
177 return;
178 }
Chris Wren2e89e8d2018-05-17 18:55:42 -0400179 final NotificationVisibility[] newlyVisibleAr = cloneVisibilitiesAsArr(newlyVisible);
180 final NotificationVisibility[] noLongerVisibleAr = cloneVisibilitiesAsArr(noLongerVisible);
181
Eliot Courtney3985ad52017-11-17 16:51:52 +0900182 mUiOffloadThread.submit(() -> {
183 try {
184 mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
185 } catch (RemoteException e) {
186 // Ignore.
187 }
188
189 final int N = newlyVisible.size();
190 if (N > 0) {
191 String[] newlyVisibleKeyAr = new String[N];
192 for (int i = 0; i < N; i++) {
193 newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
194 }
195
196 // TODO: Call NotificationEntryManager to do this, once it exists.
197 // TODO: Consider not catching all runtime exceptions here.
198 try {
199 mNotificationListener.setNotificationsShown(newlyVisibleKeyAr);
200 } catch (RuntimeException e) {
201 Log.d(TAG, "failed setNotificationsShown: ", e);
202 }
203 }
Chris Wren2e89e8d2018-05-17 18:55:42 -0400204 recycleAllVisibilityObjects(newlyVisibleAr);
205 recycleAllVisibilityObjects(noLongerVisibleAr);
Eliot Courtney3985ad52017-11-17 16:51:52 +0900206 });
207 }
208
209 private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
210 final int N = array.size();
211 for (int i = 0 ; i < N; i++) {
212 array.valueAt(i).recycle();
213 }
214 array.clear();
215 }
216
Chris Wren2e89e8d2018-05-17 18:55:42 -0400217 private void recycleAllVisibilityObjects(NotificationVisibility[] array) {
218 final int N = array.length;
219 for (int i = 0 ; i < N; i++) {
220 if (array[i] != null) {
221 array[i].recycle();
222 }
223 }
224 }
225
226 private NotificationVisibility[] cloneVisibilitiesAsArr(Collection<NotificationVisibility> c) {
227
228 final NotificationVisibility[] array = new NotificationVisibility[c.size()];
229 int i = 0;
230 for(NotificationVisibility nv: c) {
231 if (nv != null) {
232 array[i] = nv.clone();
233 }
234 i++;
235 }
236 return array;
237 }
238
Eliot Courtney3985ad52017-11-17 16:51:52 +0900239 @VisibleForTesting
240 public Runnable getVisibilityReporter() {
241 return mVisibilityReporter;
242 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900243
244 /**
245 * A listener that is notified when some child locations might have changed.
246 */
247 public interface OnChildLocationsChangedListener {
248 void onChildLocationsChanged();
249 }
Eliot Courtney3985ad52017-11-17 16:51:52 +0900250}