13985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney/*
23985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * Copyright (C) 2017 The Android Open Source Project
33985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney *
43985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * Licensed under the Apache License, Version 2.0 (the "License");
53985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * you may not use this file except in compliance with the License.
63985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * You may obtain a copy of the License at
73985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney *
83985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney *      http://www.apache.org/licenses/LICENSE-2.0
93985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney *
103985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * Unless required by applicable law or agreed to in writing, software
113985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * distributed under the License is distributed on an "AS IS" BASIS,
123985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * See the License for the specific language governing permissions and
143985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * limitations under the License
153985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney */
163985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneypackage com.android.systemui.statusbar;
173985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
183985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport android.content.Context;
193985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport android.os.Handler;
203985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport android.os.RemoteException;
213985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport android.os.ServiceManager;
223985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport android.os.SystemClock;
233985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport android.service.notification.NotificationListenerService;
243985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport android.util.ArraySet;
253985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport android.util.Log;
263985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
273985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport com.android.internal.annotations.VisibleForTesting;
283985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport com.android.internal.statusbar.IStatusBarService;
293985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport com.android.internal.statusbar.NotificationVisibility;
306c313d3224c878d832db3ed833f4a3dd3786fb1fEliot Courtneyimport com.android.systemui.Dependency;
313985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport com.android.systemui.UiOffloadThread;
323985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
333985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport java.util.ArrayList;
343985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport java.util.Collection;
353985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneyimport java.util.Collections;
363985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
373985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney/**
383985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * Handles notification logging, in particular, logging which notifications are visible and which
393985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney * are not.
403985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney */
413985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtneypublic class NotificationLogger {
423985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    private static final String TAG = "NotificationLogger";
433985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
443985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    /** The minimum delay in ms between reports of notification visibility. */
453985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
463985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
473985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    /** Keys of notifications currently visible to the user. */
483985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
493985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            new ArraySet<>();
506c313d3224c878d832db3ed833f4a3dd3786fb1fEliot Courtney
516c313d3224c878d832db3ed833f4a3dd3786fb1fEliot Courtney    // Dependencies:
526c313d3224c878d832db3ed833f4a3dd3786fb1fEliot Courtney    private final NotificationListenerService mNotificationListener =
536c313d3224c878d832db3ed833f4a3dd3786fb1fEliot Courtney            Dependency.get(NotificationListener.class);
546c313d3224c878d832db3ed833f4a3dd3786fb1fEliot Courtney    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
553985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
564a96b36fd9857cd3d3534ed0396ec3d7155a324cEliot Courtney    protected NotificationEntryManager mEntryManager;
573985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    protected Handler mHandler = new Handler();
583985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    protected IStatusBarService mBarService;
593985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    private long mLastVisibilityReportUptimeMs;
602b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney    private NotificationListContainer mListContainer;
613985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
622b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney    protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
632b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney            new OnChildLocationsChangedListener() {
643985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                @Override
652b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney                public void onChildLocationsChanged() {
663985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    if (mHandler.hasCallbacks(mVisibilityReporter)) {
673985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                        // Visibilities will be reported when the existing
683985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                        // callback is executed.
693985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                        return;
703985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    }
713985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    // Calculate when we're allowed to run the visibility
723985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    // reporter. Note that this timestamp might already have
733985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    // passed. That's OK, the callback will just be executed
743985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    // ASAP.
753985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    long nextReportUptimeMs =
763985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                            mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
773985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
783985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                }
793985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            };
803985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
813985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    // Tracks notifications currently visible in mNotificationStackScroller and
823985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    // emits visibility events via NoMan on changes.
833985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    protected final Runnable mVisibilityReporter = new Runnable() {
843985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
853985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                new ArraySet<>();
863985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
873985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                new ArraySet<>();
883985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications =
893985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                new ArraySet<>();
903985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
913985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        @Override
923985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        public void run() {
933985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
943985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
953985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            // 1. Loop over mNotificationData entries:
963985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            //   A. Keep list of visible notifications.
973985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            //   B. Keep list of previously hidden, now visible notifications.
983985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            // 2. Compute no-longer visible notifications by removing currently
993985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            //    visible notifications from the set of previously visible
1003985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            //    notifications.
1013985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            // 3. Report newly visible and no-longer visible notifications.
1023985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            // 4. Keep currently visible notifications for next report.
1034a96b36fd9857cd3d3534ed0396ec3d7155a324cEliot Courtney            ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
1044a96b36fd9857cd3d3534ed0396ec3d7155a324cEliot Courtney                    .getNotificationData().getActiveNotifications();
1053985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            int N = activeNotifications.size();
1063985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            for (int i = 0; i < N; i++) {
1073985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                NotificationData.Entry entry = activeNotifications.get(i);
1083985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                String key = entry.notification.getKey();
1092b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney                boolean isVisible = mListContainer.isInVisibleLocation(entry.row);
110d39f0d52dcdca78fb8d57fa0a805ec0bdc8589daDieter Hsu                NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible);
1113985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
1123985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                if (isVisible) {
1133985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    // Build new set of visible notifications.
1143985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    mTmpCurrentlyVisibleNotifications.add(visObj);
1153985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    if (!previouslyVisible) {
1163985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                        mTmpNewlyVisibleNotifications.add(visObj);
1173985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    }
1183985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                } else {
1193985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    // release object
1203985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    visObj.recycle();
1213985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                }
1223985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            }
1233985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications);
1243985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
1253985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
1263985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            logNotificationVisibilityChanges(
1273985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications);
1283985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
1293985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
1303985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
1313985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
1323985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
1333985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            mTmpCurrentlyVisibleNotifications.clear();
1343985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            mTmpNewlyVisibleNotifications.clear();
1353985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            mTmpNoLongerVisibleNotifications.clear();
1363985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        }
1373985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    };
1383985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
1396c313d3224c878d832db3ed833f4a3dd3786fb1fEliot Courtney    public NotificationLogger() {
1403985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        mBarService = IStatusBarService.Stub.asInterface(
1413985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
1423985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    }
1433985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
1444a96b36fd9857cd3d3534ed0396ec3d7155a324cEliot Courtney    public void setUpWithEntryManager(NotificationEntryManager entryManager,
1452b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney            NotificationListContainer listContainer) {
1464a96b36fd9857cd3d3534ed0396ec3d7155a324cEliot Courtney        mEntryManager = entryManager;
1472b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney        mListContainer = listContainer;
1483985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    }
1493985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
1503985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    public void stopNotificationLogging() {
1513985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        // Report all notifications as invisible and turn down the
1523985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        // reporter.
1533985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        if (!mCurrentlyVisibleNotifications.isEmpty()) {
1543985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            logNotificationVisibilityChanges(
1553985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    Collections.emptyList(), mCurrentlyVisibleNotifications);
1563985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
1573985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        }
1583985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        mHandler.removeCallbacks(mVisibilityReporter);
1592b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney        mListContainer.setChildLocationsChangedListener(null);
1603985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    }
1613985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
1623985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    public void startNotificationLogging() {
1632b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney        mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
1643985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
1653985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        // cause the scroller to emit child location events. Hence generate
1663985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        // one ourselves to guarantee that we're reporting visible
1673985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        // notifications.
1683985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        // (Note that in cases where the scroller does emit events, this
1693985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        // additional event doesn't break anything.)
1702b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney        mNotificationLocationsChangedListener.onChildLocationsChanged();
1713985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    }
1723985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
1733985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    private void logNotificationVisibilityChanges(
1743985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            Collection<NotificationVisibility> newlyVisible,
1753985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            Collection<NotificationVisibility> noLongerVisible) {
1763985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
1773985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            return;
1783985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        }
1792e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren        final NotificationVisibility[] newlyVisibleAr = cloneVisibilitiesAsArr(newlyVisible);
1802e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren        final NotificationVisibility[] noLongerVisibleAr = cloneVisibilitiesAsArr(noLongerVisible);
1812e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren
1823985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        mUiOffloadThread.submit(() -> {
1833985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            try {
1843985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
1853985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            } catch (RemoteException e) {
1863985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                // Ignore.
1873985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            }
1883985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
1893985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            final int N = newlyVisible.size();
1903985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            if (N > 0) {
1913985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                String[] newlyVisibleKeyAr = new String[N];
1923985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                for (int i = 0; i < N; i++) {
1933985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
1943985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                }
1953985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
1963985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                // TODO: Call NotificationEntryManager to do this, once it exists.
1973985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                // TODO: Consider not catching all runtime exceptions here.
1983985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                try {
1993985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    mNotificationListener.setNotificationsShown(newlyVisibleKeyAr);
2003985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                } catch (RuntimeException e) {
2013985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                    Log.d(TAG, "failed setNotificationsShown: ", e);
2023985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney                }
2033985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            }
2042e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren            recycleAllVisibilityObjects(newlyVisibleAr);
2052e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren            recycleAllVisibilityObjects(noLongerVisibleAr);
2063985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        });
2073985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    }
2083985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
2093985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
2103985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        final int N = array.size();
2113985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        for (int i = 0 ; i < N; i++) {
2123985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney            array.valueAt(i).recycle();
2133985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        }
2143985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        array.clear();
2153985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    }
2163985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney
2172e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren    private void recycleAllVisibilityObjects(NotificationVisibility[] array) {
2182e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren        final int N = array.length;
2192e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren        for (int i = 0 ; i < N; i++) {
2202e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren            if (array[i] != null) {
2212e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren                array[i].recycle();
2222e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren            }
2232e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren        }
2242e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren    }
2252e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren
2262e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren    private NotificationVisibility[] cloneVisibilitiesAsArr(Collection<NotificationVisibility> c) {
2272e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren
2282e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren        final NotificationVisibility[] array = new NotificationVisibility[c.size()];
2292e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren        int i = 0;
2302e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren        for(NotificationVisibility nv: c) {
2312e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren            if (nv != null) {
2322e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren                array[i] = nv.clone();
2332e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren            }
2342e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren            i++;
2352e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren        }
2362e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren        return array;
2372e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren    }
2382e89e8d893acfe571ad6f5555baccb1b5e55abb7Chris Wren
2393985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    @VisibleForTesting
2403985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    public Runnable getVisibilityReporter() {
2413985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney        return mVisibilityReporter;
2423985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney    }
2432b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney
2442b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney    /**
2452b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney     * A listener that is notified when some child locations might have changed.
2462b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney     */
2472b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney    public interface OnChildLocationsChangedListener {
2482b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney        void onChildLocationsChanged();
2492b4c3a08fcfcb60c527a1a372318e5ef4ce2d49cEliot Courtney    }
2503985ad5773cd4573525cbfb00e132b960a83ef48Eliot Courtney}
251