Loading packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +9 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationGutsManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLogger; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.phone.DozeParameters; Loading @@ -37,6 +39,7 @@ import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBar; Loading Loading @@ -114,10 +117,16 @@ public class SystemUIFactory { Context context) { providers.put(NotificationLockscreenUserManager.class, () -> new NotificationLockscreenUserManager(context)); providers.put(NotificationGroupManager.class, NotificationGroupManager::new); providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager( Dependency.get(NotificationLockscreenUserManager.class), context)); providers.put(NotificationRemoteInputManager.class, () -> new NotificationRemoteInputManager( Dependency.get(NotificationLockscreenUserManager.class), context)); providers.put(NotificationListener.class, () -> new NotificationListener( Dependency.get(NotificationRemoteInputManager.class), context)); providers.put(NotificationLogger.class, () -> new NotificationLogger( Dependency.get(NotificationListener.class), Dependency.get(UiOffloadThread.class))); } } packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +7 −5 Original line number Diff line number Diff line Loading @@ -36,13 +36,13 @@ import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; public class NotificationListener extends NotificationListenerWithPlugins { private static final String TAG = "NotificationListener"; private final NotificationPresenter mPresenter; private final NotificationRemoteInputManager mRemoteInputManager; private final Context mContext; public NotificationListener(NotificationPresenter presenter, NotificationRemoteInputManager remoteInputManager, Context context) { mPresenter = presenter; private NotificationPresenter mPresenter; public NotificationListener(NotificationRemoteInputManager remoteInputManager, Context context) { mRemoteInputManager = remoteInputManager; mContext = context; } Loading Loading @@ -120,7 +120,9 @@ public class NotificationListener extends NotificationListenerWithPlugins { } } public void register() { public void setUpWithPresenter(NotificationPresenter presenter) { mPresenter = presenter; try { registerAsSystemService(mContext, new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), Loading packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java 0 → 100644 +223 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.systemui.statusbar; import android.content.Context; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.service.notification.NotificationListenerService; import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.UiOffloadThread; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; /** * Handles notification logging, in particular, logging which notifications are visible and which * are not. */ public class NotificationLogger { private static final String TAG = "NotificationLogger"; /** The minimum delay in ms between reports of notification visibility. */ private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500; /** Keys of notifications currently visible to the user. */ private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications = new ArraySet<>(); private final NotificationListenerService mNotificationListener; private final UiOffloadThread mUiOffloadThread; protected NotificationPresenter mPresenter; protected Handler mHandler = new Handler(); protected IStatusBarService mBarService; private long mLastVisibilityReportUptimeMs; private NotificationStackScrollLayout mStackScroller; protected final NotificationStackScrollLayout.OnChildLocationsChangedListener mNotificationLocationsChangedListener = new NotificationStackScrollLayout.OnChildLocationsChangedListener() { @Override public void onChildLocationsChanged( NotificationStackScrollLayout stackScrollLayout) { if (mHandler.hasCallbacks(mVisibilityReporter)) { // Visibilities will be reported when the existing // callback is executed. return; } // Calculate when we're allowed to run the visibility // reporter. Note that this timestamp might already have // passed. That's OK, the callback will just be executed // ASAP. long nextReportUptimeMs = mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS; mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs); } }; // Tracks notifications currently visible in mNotificationStackScroller and // emits visibility events via NoMan on changes. protected final Runnable mVisibilityReporter = new Runnable() { private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications = new ArraySet<>(); private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications = new ArraySet<>(); private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications = new ArraySet<>(); @Override public void run() { mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); // 1. Loop over mNotificationData entries: // A. Keep list of visible notifications. // B. Keep list of previously hidden, now visible notifications. // 2. Compute no-longer visible notifications by removing currently // visible notifications from the set of previously visible // notifications. // 3. Report newly visible and no-longer visible notifications. // 4. Keep currently visible notifications for next report. ArrayList<NotificationData.Entry> activeNotifications = mPresenter. getNotificationData().getActiveNotifications(); int N = activeNotifications.size(); for (int i = 0; i < N; i++) { NotificationData.Entry entry = activeNotifications.get(i); String key = entry.notification.getKey(); boolean isVisible = mStackScroller.isInVisibleLocation(entry.row); NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible); boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj); if (isVisible) { // Build new set of visible notifications. mTmpCurrentlyVisibleNotifications.add(visObj); if (!previouslyVisible) { mTmpNewlyVisibleNotifications.add(visObj); } } else { // release object visObj.recycle(); } } mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications); mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); logNotificationVisibilityChanges( mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications); recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications); recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications); mTmpCurrentlyVisibleNotifications.clear(); mTmpNewlyVisibleNotifications.clear(); mTmpNoLongerVisibleNotifications.clear(); } }; public NotificationLogger(NotificationListenerService notificationListener, UiOffloadThread uiOffloadThread) { mNotificationListener = notificationListener; mUiOffloadThread = uiOffloadThread; mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } // TODO: Remove dependency on NotificationStackScrollLayout. public void setUpWithPresenter(NotificationPresenter presenter, NotificationStackScrollLayout stackScroller) { mPresenter = presenter; mStackScroller = stackScroller; } public void stopNotificationLogging() { // Report all notifications as invisible and turn down the // reporter. if (!mCurrentlyVisibleNotifications.isEmpty()) { logNotificationVisibilityChanges( Collections.emptyList(), mCurrentlyVisibleNotifications); recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); } mHandler.removeCallbacks(mVisibilityReporter); mStackScroller.setChildLocationsChangedListener(null); } public void startNotificationLogging() { mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener); // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't // cause the scroller to emit child location events. Hence generate // one ourselves to guarantee that we're reporting visible // notifications. // (Note that in cases where the scroller does emit events, this // additional event doesn't break anything.) mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller); } private void logNotificationVisibilityChanges( Collection<NotificationVisibility> newlyVisible, Collection<NotificationVisibility> noLongerVisible) { if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) { return; } NotificationVisibility[] newlyVisibleAr = newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]); NotificationVisibility[] noLongerVisibleAr = noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]); mUiOffloadThread.submit(() -> { try { mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr); } catch (RemoteException e) { // Ignore. } final int N = newlyVisible.size(); if (N > 0) { String[] newlyVisibleKeyAr = new String[N]; for (int i = 0; i < N; i++) { newlyVisibleKeyAr[i] = newlyVisibleAr[i].key; } // TODO: Call NotificationEntryManager to do this, once it exists. // TODO: Consider not catching all runtime exceptions here. try { mNotificationListener.setNotificationsShown(newlyVisibleKeyAr); } catch (RuntimeException e) { Log.d(TAG, "failed setNotificationsShown: ", e); } } }); } private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { final int N = array.size(); for (int i = 0 ; i < N; i++) { array.valueAt(i).recycle(); } array.clear(); } @VisibleForTesting public Runnable getVisibilityReporter() { return mVisibilityReporter; } } packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +12 −160 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +3 −1 Original line number Diff line number Diff line Loading @@ -71,9 +71,11 @@ public class NotificationListenerTest extends SysuiTestCase { when(mPresenter.getNotificationData()).thenReturn(mNotificationData); when(mRemoteInputManager.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput); mListener = new NotificationListener(mPresenter, mRemoteInputManager, mContext); mListener = new NotificationListener(mRemoteInputManager, mContext); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); mListener.setUpWithPresenter(mPresenter); } @Test Loading Loading
packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +9 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationGutsManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLogger; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.phone.DozeParameters; Loading @@ -37,6 +39,7 @@ import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBar; Loading Loading @@ -114,10 +117,16 @@ public class SystemUIFactory { Context context) { providers.put(NotificationLockscreenUserManager.class, () -> new NotificationLockscreenUserManager(context)); providers.put(NotificationGroupManager.class, NotificationGroupManager::new); providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager( Dependency.get(NotificationLockscreenUserManager.class), context)); providers.put(NotificationRemoteInputManager.class, () -> new NotificationRemoteInputManager( Dependency.get(NotificationLockscreenUserManager.class), context)); providers.put(NotificationListener.class, () -> new NotificationListener( Dependency.get(NotificationRemoteInputManager.class), context)); providers.put(NotificationLogger.class, () -> new NotificationLogger( Dependency.get(NotificationListener.class), Dependency.get(UiOffloadThread.class))); } }
packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +7 −5 Original line number Diff line number Diff line Loading @@ -36,13 +36,13 @@ import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; public class NotificationListener extends NotificationListenerWithPlugins { private static final String TAG = "NotificationListener"; private final NotificationPresenter mPresenter; private final NotificationRemoteInputManager mRemoteInputManager; private final Context mContext; public NotificationListener(NotificationPresenter presenter, NotificationRemoteInputManager remoteInputManager, Context context) { mPresenter = presenter; private NotificationPresenter mPresenter; public NotificationListener(NotificationRemoteInputManager remoteInputManager, Context context) { mRemoteInputManager = remoteInputManager; mContext = context; } Loading Loading @@ -120,7 +120,9 @@ public class NotificationListener extends NotificationListenerWithPlugins { } } public void register() { public void setUpWithPresenter(NotificationPresenter presenter) { mPresenter = presenter; try { registerAsSystemService(mContext, new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), Loading
packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java 0 → 100644 +223 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.systemui.statusbar; import android.content.Context; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.service.notification.NotificationListenerService; import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.UiOffloadThread; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; /** * Handles notification logging, in particular, logging which notifications are visible and which * are not. */ public class NotificationLogger { private static final String TAG = "NotificationLogger"; /** The minimum delay in ms between reports of notification visibility. */ private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500; /** Keys of notifications currently visible to the user. */ private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications = new ArraySet<>(); private final NotificationListenerService mNotificationListener; private final UiOffloadThread mUiOffloadThread; protected NotificationPresenter mPresenter; protected Handler mHandler = new Handler(); protected IStatusBarService mBarService; private long mLastVisibilityReportUptimeMs; private NotificationStackScrollLayout mStackScroller; protected final NotificationStackScrollLayout.OnChildLocationsChangedListener mNotificationLocationsChangedListener = new NotificationStackScrollLayout.OnChildLocationsChangedListener() { @Override public void onChildLocationsChanged( NotificationStackScrollLayout stackScrollLayout) { if (mHandler.hasCallbacks(mVisibilityReporter)) { // Visibilities will be reported when the existing // callback is executed. return; } // Calculate when we're allowed to run the visibility // reporter. Note that this timestamp might already have // passed. That's OK, the callback will just be executed // ASAP. long nextReportUptimeMs = mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS; mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs); } }; // Tracks notifications currently visible in mNotificationStackScroller and // emits visibility events via NoMan on changes. protected final Runnable mVisibilityReporter = new Runnable() { private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications = new ArraySet<>(); private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications = new ArraySet<>(); private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications = new ArraySet<>(); @Override public void run() { mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); // 1. Loop over mNotificationData entries: // A. Keep list of visible notifications. // B. Keep list of previously hidden, now visible notifications. // 2. Compute no-longer visible notifications by removing currently // visible notifications from the set of previously visible // notifications. // 3. Report newly visible and no-longer visible notifications. // 4. Keep currently visible notifications for next report. ArrayList<NotificationData.Entry> activeNotifications = mPresenter. getNotificationData().getActiveNotifications(); int N = activeNotifications.size(); for (int i = 0; i < N; i++) { NotificationData.Entry entry = activeNotifications.get(i); String key = entry.notification.getKey(); boolean isVisible = mStackScroller.isInVisibleLocation(entry.row); NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible); boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj); if (isVisible) { // Build new set of visible notifications. mTmpCurrentlyVisibleNotifications.add(visObj); if (!previouslyVisible) { mTmpNewlyVisibleNotifications.add(visObj); } } else { // release object visObj.recycle(); } } mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications); mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); logNotificationVisibilityChanges( mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications); recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications); recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications); mTmpCurrentlyVisibleNotifications.clear(); mTmpNewlyVisibleNotifications.clear(); mTmpNoLongerVisibleNotifications.clear(); } }; public NotificationLogger(NotificationListenerService notificationListener, UiOffloadThread uiOffloadThread) { mNotificationListener = notificationListener; mUiOffloadThread = uiOffloadThread; mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } // TODO: Remove dependency on NotificationStackScrollLayout. public void setUpWithPresenter(NotificationPresenter presenter, NotificationStackScrollLayout stackScroller) { mPresenter = presenter; mStackScroller = stackScroller; } public void stopNotificationLogging() { // Report all notifications as invisible and turn down the // reporter. if (!mCurrentlyVisibleNotifications.isEmpty()) { logNotificationVisibilityChanges( Collections.emptyList(), mCurrentlyVisibleNotifications); recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); } mHandler.removeCallbacks(mVisibilityReporter); mStackScroller.setChildLocationsChangedListener(null); } public void startNotificationLogging() { mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener); // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't // cause the scroller to emit child location events. Hence generate // one ourselves to guarantee that we're reporting visible // notifications. // (Note that in cases where the scroller does emit events, this // additional event doesn't break anything.) mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller); } private void logNotificationVisibilityChanges( Collection<NotificationVisibility> newlyVisible, Collection<NotificationVisibility> noLongerVisible) { if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) { return; } NotificationVisibility[] newlyVisibleAr = newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]); NotificationVisibility[] noLongerVisibleAr = noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]); mUiOffloadThread.submit(() -> { try { mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr); } catch (RemoteException e) { // Ignore. } final int N = newlyVisible.size(); if (N > 0) { String[] newlyVisibleKeyAr = new String[N]; for (int i = 0; i < N; i++) { newlyVisibleKeyAr[i] = newlyVisibleAr[i].key; } // TODO: Call NotificationEntryManager to do this, once it exists. // TODO: Consider not catching all runtime exceptions here. try { mNotificationListener.setNotificationsShown(newlyVisibleKeyAr); } catch (RuntimeException e) { Log.d(TAG, "failed setNotificationsShown: ", e); } } }); } private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { final int N = array.size(); for (int i = 0 ; i < N; i++) { array.valueAt(i).recycle(); } array.clear(); } @VisibleForTesting public Runnable getVisibilityReporter() { return mVisibilityReporter; } }
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +12 −160 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +3 −1 Original line number Diff line number Diff line Loading @@ -71,9 +71,11 @@ public class NotificationListenerTest extends SysuiTestCase { when(mPresenter.getNotificationData()).thenReturn(mNotificationData); when(mRemoteInputManager.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput); mListener = new NotificationListener(mPresenter, mRemoteInputManager, mContext); mListener = new NotificationListener(mRemoteInputManager, mContext); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); mListener.setUpWithPresenter(mPresenter); } @Test Loading