• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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  */
16 package com.android.systemui.statusbar.notification.logging;
17 
18 import android.content.Context;
19 import android.os.Handler;
20 import android.os.RemoteException;
21 import android.os.ServiceManager;
22 import android.os.SystemClock;
23 import android.service.notification.NotificationListenerService;
24 import android.service.notification.StatusBarNotification;
25 import android.util.ArrayMap;
26 import android.util.ArraySet;
27 import android.util.Log;
28 
29 import androidx.annotation.Nullable;
30 
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.statusbar.IStatusBarService;
34 import com.android.internal.statusbar.NotificationVisibility;
35 import com.android.systemui.dagger.qualifiers.UiBackground;
36 import com.android.systemui.plugins.statusbar.StatusBarStateController;
37 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
38 import com.android.systemui.statusbar.NotificationListener;
39 import com.android.systemui.statusbar.StatusBarState;
40 import com.android.systemui.statusbar.notification.NotificationEntryListener;
41 import com.android.systemui.statusbar.notification.NotificationEntryManager;
42 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
43 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
44 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
45 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
46 
47 import java.util.Collection;
48 import java.util.Collections;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.concurrent.Executor;
52 
53 import javax.inject.Inject;
54 
55 /**
56  * Handles notification logging, in particular, logging which notifications are visible and which
57  * are not.
58  */
59 public class NotificationLogger implements StateListener {
60     private static final String TAG = "NotificationLogger";
61     private static final boolean DEBUG = false;
62 
63     /** The minimum delay in ms between reports of notification visibility. */
64     private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
65 
66     /** Keys of notifications currently visible to the user. */
67     private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
68             new ArraySet<>();
69 
70     // Dependencies:
71     private final NotificationListenerService mNotificationListener;
72     private final Executor mUiBgExecutor;
73     private final NotificationEntryManager mEntryManager;
74     private final NotificationPanelLogger mNotificationPanelLogger;
75     private final ExpansionStateLogger mExpansionStateLogger;
76 
77     protected Handler mHandler = new Handler();
78     protected IStatusBarService mBarService;
79     private long mLastVisibilityReportUptimeMs;
80     private NotificationListContainer mListContainer;
81     private final Object mDozingLock = new Object();
82     @GuardedBy("mDozingLock")
83     private Boolean mDozing = null;  // Use null to indicate state is not yet known
84     @GuardedBy("mDozingLock")
85     private Boolean mLockscreen = null;  // Use null to indicate state is not yet known
86     private Boolean mPanelExpanded = null;  // Use null to indicate state is not yet known
87     private boolean mLogging = false;
88 
89     protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
90             new OnChildLocationsChangedListener() {
91                 @Override
92                 public void onChildLocationsChanged() {
93                     if (mHandler.hasCallbacks(mVisibilityReporter)) {
94                         // Visibilities will be reported when the existing
95                         // callback is executed.
96                         return;
97                     }
98                     // Calculate when we're allowed to run the visibility
99                     // reporter. Note that this timestamp might already have
100                     // passed. That's OK, the callback will just be executed
101                     // ASAP.
102                     long nextReportUptimeMs =
103                             mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
104                     mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
105                 }
106             };
107 
108     // Tracks notifications currently visible in mNotificationStackScroller and
109     // emits visibility events via NoMan on changes.
110     protected Runnable mVisibilityReporter = new Runnable() {
111         private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
112                 new ArraySet<>();
113         private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
114                 new ArraySet<>();
115         private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications =
116                 new ArraySet<>();
117 
118         @Override
119         public void run() {
120             mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
121 
122             // 1. Loop over active entries:
123             //   A. Keep list of visible notifications.
124             //   B. Keep list of previously hidden, now visible notifications.
125             // 2. Compute no-longer visible notifications by removing currently
126             //    visible notifications from the set of previously visible
127             //    notifications.
128             // 3. Report newly visible and no-longer visible notifications.
129             // 4. Keep currently visible notifications for next report.
130             List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
131             int N = activeNotifications.size();
132             for (int i = 0; i < N; i++) {
133                 NotificationEntry entry = activeNotifications.get(i);
134                 String key = entry.getSbn().getKey();
135                 boolean isVisible = mListContainer.isInVisibleLocation(entry);
136                 NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible,
137                         getNotificationLocation(entry));
138                 boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
139                 if (isVisible) {
140                     // Build new set of visible notifications.
141                     mTmpCurrentlyVisibleNotifications.add(visObj);
142                     if (!previouslyVisible) {
143                         mTmpNewlyVisibleNotifications.add(visObj);
144                     }
145                 } else {
146                     // release object
147                     visObj.recycle();
148                 }
149             }
150             mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications);
151             mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
152 
153             logNotificationVisibilityChanges(
154                     mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications);
155 
156             recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
157             mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
158 
159             mExpansionStateLogger.onVisibilityChanged(
160                     mTmpCurrentlyVisibleNotifications, mTmpCurrentlyVisibleNotifications);
161 
162             recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
163             mTmpCurrentlyVisibleNotifications.clear();
164             mTmpNewlyVisibleNotifications.clear();
165             mTmpNoLongerVisibleNotifications.clear();
166         }
167     };
168 
169     /**
170      * Returns the location of the notification referenced by the given {@link NotificationEntry}.
171      */
getNotificationLocation( NotificationEntry entry)172     public static NotificationVisibility.NotificationLocation getNotificationLocation(
173             NotificationEntry entry) {
174         if (entry == null || entry.getRow() == null || entry.getRow().getViewState() == null) {
175             return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
176         }
177         return convertNotificationLocation(entry.getRow().getViewState().location);
178     }
179 
convertNotificationLocation( int location)180     private static NotificationVisibility.NotificationLocation convertNotificationLocation(
181             int location) {
182         switch (location) {
183             case ExpandableViewState.LOCATION_FIRST_HUN:
184                 return NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP;
185             case ExpandableViewState.LOCATION_HIDDEN_TOP:
186                 return NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP;
187             case ExpandableViewState.LOCATION_MAIN_AREA:
188                 return NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA;
189             case ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING:
190                 return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
191             case ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN:
192                 return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
193             case ExpandableViewState.LOCATION_GONE:
194                 return NotificationVisibility.NotificationLocation.LOCATION_GONE;
195             default:
196                 return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
197         }
198     }
199 
200     /**
201      * Injected constructor. See {@link NotificationsModule}.
202      */
NotificationLogger(NotificationListener notificationListener, @UiBackground Executor uiBgExecutor, NotificationEntryManager entryManager, StatusBarStateController statusBarStateController, ExpansionStateLogger expansionStateLogger, NotificationPanelLogger notificationPanelLogger)203     public NotificationLogger(NotificationListener notificationListener,
204             @UiBackground Executor uiBgExecutor,
205             NotificationEntryManager entryManager,
206             StatusBarStateController statusBarStateController,
207             ExpansionStateLogger expansionStateLogger,
208             NotificationPanelLogger notificationPanelLogger) {
209         mNotificationListener = notificationListener;
210         mUiBgExecutor = uiBgExecutor;
211         mEntryManager = entryManager;
212         mBarService = IStatusBarService.Stub.asInterface(
213                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
214         mExpansionStateLogger = expansionStateLogger;
215         mNotificationPanelLogger = notificationPanelLogger;
216         // Not expected to be destroyed, don't need to unsubscribe
217         statusBarStateController.addCallback(this);
218 
219         entryManager.addNotificationEntryListener(new NotificationEntryListener() {
220             @Override
221             public void onEntryRemoved(
222                     NotificationEntry entry,
223                     NotificationVisibility visibility,
224                     boolean removedByUser,
225                     int reason) {
226                 mExpansionStateLogger.onEntryRemoved(entry.getKey());
227             }
228 
229             @Override
230             public void onPreEntryUpdated(NotificationEntry entry) {
231                 mExpansionStateLogger.onEntryUpdated(entry.getKey());
232             }
233 
234             @Override
235             public void onInflationError(
236                     StatusBarNotification notification,
237                     Exception exception) {
238                 logNotificationError(notification, exception);
239             }
240         });
241     }
242 
setUpWithContainer(NotificationListContainer listContainer)243     public void setUpWithContainer(NotificationListContainer listContainer) {
244         mListContainer = listContainer;
245     }
246 
stopNotificationLogging()247     public void stopNotificationLogging() {
248         if (mLogging) {
249             mLogging = false;
250             if (DEBUG) {
251                 Log.i(TAG, "stopNotificationLogging: log notifications invisible");
252             }
253             // Report all notifications as invisible and turn down the
254             // reporter.
255             if (!mCurrentlyVisibleNotifications.isEmpty()) {
256                 logNotificationVisibilityChanges(
257                         Collections.emptyList(), mCurrentlyVisibleNotifications);
258                 recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
259             }
260             mHandler.removeCallbacks(mVisibilityReporter);
261             mListContainer.setChildLocationsChangedListener(null);
262         }
263     }
264 
startNotificationLogging()265     public void startNotificationLogging() {
266         if (!mLogging) {
267             mLogging = true;
268             if (DEBUG) {
269                 Log.i(TAG, "startNotificationLogging");
270             }
271             mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
272             // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
273             // cause the scroller to emit child location events. Hence generate
274             // one ourselves to guarantee that we're reporting visible
275             // notifications.
276             // (Note that in cases where the scroller does emit events, this
277             // additional event doesn't break anything.)
278             mNotificationLocationsChangedListener.onChildLocationsChanged();
279         }
280     }
281 
setDozing(boolean dozing)282     private void setDozing(boolean dozing) {
283         synchronized (mDozingLock) {
284             mDozing = dozing;
285             maybeUpdateLoggingStatus();
286         }
287     }
288 
289     /**
290      * Logs Notification inflation error
291      */
logNotificationError( StatusBarNotification notification, Exception exception)292     private void logNotificationError(
293             StatusBarNotification notification,
294             Exception exception) {
295         try {
296             mBarService.onNotificationError(
297                     notification.getPackageName(),
298                     notification.getTag(),
299                     notification.getId(),
300                     notification.getUid(),
301                     notification.getInitialPid(),
302                     exception.getMessage(),
303                     notification.getUserId());
304         } catch (RemoteException ex) {
305             // The end is nigh.
306         }
307     }
308 
logNotificationVisibilityChanges( Collection<NotificationVisibility> newlyVisible, Collection<NotificationVisibility> noLongerVisible)309     private void logNotificationVisibilityChanges(
310             Collection<NotificationVisibility> newlyVisible,
311             Collection<NotificationVisibility> noLongerVisible) {
312         if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
313             return;
314         }
315         final NotificationVisibility[] newlyVisibleAr = cloneVisibilitiesAsArr(newlyVisible);
316         final NotificationVisibility[] noLongerVisibleAr = cloneVisibilitiesAsArr(noLongerVisible);
317 
318         mUiBgExecutor.execute(() -> {
319             try {
320                 mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
321             } catch (RemoteException e) {
322                 // Ignore.
323             }
324 
325             final int N = newlyVisibleAr.length;
326             if (N > 0) {
327                 String[] newlyVisibleKeyAr = new String[N];
328                 for (int i = 0; i < N; i++) {
329                     newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
330                 }
331                 // TODO: Call NotificationEntryManager to do this, once it exists.
332                 // TODO: Consider not catching all runtime exceptions here.
333                 try {
334                     mNotificationListener.setNotificationsShown(newlyVisibleKeyAr);
335                 } catch (RuntimeException e) {
336                     Log.d(TAG, "failed setNotificationsShown: ", e);
337                 }
338             }
339             recycleAllVisibilityObjects(newlyVisibleAr);
340             recycleAllVisibilityObjects(noLongerVisibleAr);
341         });
342     }
343 
recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array)344     private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
345         final int N = array.size();
346         for (int i = 0 ; i < N; i++) {
347             array.valueAt(i).recycle();
348         }
349         array.clear();
350     }
351 
recycleAllVisibilityObjects(NotificationVisibility[] array)352     private void recycleAllVisibilityObjects(NotificationVisibility[] array) {
353         final int N = array.length;
354         for (int i = 0 ; i < N; i++) {
355             if (array[i] != null) {
356                 array[i].recycle();
357             }
358         }
359     }
360 
cloneVisibilitiesAsArr( Collection<NotificationVisibility> c)361     private static NotificationVisibility[] cloneVisibilitiesAsArr(
362             Collection<NotificationVisibility> c) {
363         final NotificationVisibility[] array = new NotificationVisibility[c.size()];
364         int i = 0;
365         for(NotificationVisibility nv: c) {
366             if (nv != null) {
367                 array[i] = nv.clone();
368             }
369             i++;
370         }
371         return array;
372     }
373 
374     @VisibleForTesting
getVisibilityReporter()375     public Runnable getVisibilityReporter() {
376         return mVisibilityReporter;
377     }
378 
379     @Override
onStateChanged(int newState)380     public void onStateChanged(int newState) {
381         if (DEBUG) {
382             Log.i(TAG, "onStateChanged: new=" + newState);
383         }
384         synchronized (mDozingLock) {
385             mLockscreen = (newState == StatusBarState.KEYGUARD
386                     || newState == StatusBarState.SHADE_LOCKED);
387         }
388     }
389 
390     @Override
onDozingChanged(boolean isDozing)391     public void onDozingChanged(boolean isDozing) {
392         if (DEBUG) {
393             Log.i(TAG, "onDozingChanged: new=" + isDozing);
394         }
395         setDozing(isDozing);
396     }
397 
398     @GuardedBy("mDozingLock")
maybeUpdateLoggingStatus()399     private void maybeUpdateLoggingStatus() {
400         if (mPanelExpanded == null || mDozing == null) {
401             if (DEBUG) {
402                 Log.i(TAG, "Panel status unclear: panelExpandedKnown="
403                         + (mPanelExpanded == null) + " dozingKnown=" + (mDozing == null));
404             }
405             return;
406         }
407         // Once we know panelExpanded and Dozing, turn logging on & off when appropriate
408         boolean lockscreen = mLockscreen == null ? false : mLockscreen;
409         if (mPanelExpanded && !mDozing) {
410             mNotificationPanelLogger.logPanelShown(lockscreen,
411                     mEntryManager.getVisibleNotifications());
412             if (DEBUG) {
413                 Log.i(TAG, "Notification panel shown, lockscreen=" + lockscreen);
414             }
415             startNotificationLogging();
416         } else {
417             if (DEBUG) {
418                 Log.i(TAG, "Notification panel hidden, lockscreen=" + lockscreen);
419             }
420             stopNotificationLogging();
421         }
422     }
423 
424     /**
425      * Called by StatusBar to notify the logger that the panel expansion has changed.
426      * The panel may be showing any of the normal notification panel, the AOD, or the bouncer.
427      * @param isExpanded True if the panel is expanded.
428      */
onPanelExpandedChanged(boolean isExpanded)429     public void onPanelExpandedChanged(boolean isExpanded) {
430         if (DEBUG) {
431             Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded);
432         }
433         mPanelExpanded = isExpanded;
434         synchronized (mDozingLock) {
435             maybeUpdateLoggingStatus();
436         }
437     }
438 
439     /**
440      * Called when the notification is expanded / collapsed.
441      */
onExpansionChanged(String key, boolean isUserAction, boolean isExpanded)442     public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
443         NotificationVisibility.NotificationLocation location =
444                 getNotificationLocation(mEntryManager.getActiveNotificationUnfiltered(key));
445         mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, location);
446     }
447 
448     @VisibleForTesting
setVisibilityReporter(Runnable visibilityReporter)449     public void setVisibilityReporter(Runnable visibilityReporter) {
450         mVisibilityReporter = visibilityReporter;
451     }
452 
453     /**
454      * A listener that is notified when some child locations might have changed.
455      */
456     public interface OnChildLocationsChangedListener {
onChildLocationsChanged()457         void onChildLocationsChanged();
458     }
459 
460     /**
461      * Logs the expansion state change when the notification is visible.
462      */
463     public static class ExpansionStateLogger {
464         /** Notification key -> state, should be accessed in UI offload thread only. */
465         private final Map<String, State> mExpansionStates = new ArrayMap<>();
466 
467         /**
468          * Notification key -> last logged expansion state, should be accessed in UI thread only.
469          */
470         private final Map<String, Boolean> mLoggedExpansionState = new ArrayMap<>();
471         private final Executor mUiBgExecutor;
472         @VisibleForTesting
473         IStatusBarService mBarService;
474 
475         @Inject
ExpansionStateLogger(@iBackground Executor uiBgExecutor)476         public ExpansionStateLogger(@UiBackground Executor uiBgExecutor) {
477             mUiBgExecutor = uiBgExecutor;
478             mBarService =
479                     IStatusBarService.Stub.asInterface(
480                             ServiceManager.getService(Context.STATUS_BAR_SERVICE));
481         }
482 
483         @VisibleForTesting
onExpansionChanged(String key, boolean isUserAction, boolean isExpanded, NotificationVisibility.NotificationLocation location)484         void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded,
485                 NotificationVisibility.NotificationLocation location) {
486             State state = getState(key);
487             state.mIsUserAction = isUserAction;
488             state.mIsExpanded = isExpanded;
489             state.mLocation = location;
490             maybeNotifyOnNotificationExpansionChanged(key, state);
491         }
492 
493         @VisibleForTesting
onVisibilityChanged( Collection<NotificationVisibility> newlyVisible, Collection<NotificationVisibility> noLongerVisible)494         void onVisibilityChanged(
495                 Collection<NotificationVisibility> newlyVisible,
496                 Collection<NotificationVisibility> noLongerVisible) {
497             final NotificationVisibility[] newlyVisibleAr =
498                     cloneVisibilitiesAsArr(newlyVisible);
499             final NotificationVisibility[] noLongerVisibleAr =
500                     cloneVisibilitiesAsArr(noLongerVisible);
501 
502             for (NotificationVisibility nv : newlyVisibleAr) {
503                 State state = getState(nv.key);
504                 state.mIsVisible = true;
505                 state.mLocation = nv.location;
506                 maybeNotifyOnNotificationExpansionChanged(nv.key, state);
507             }
508             for (NotificationVisibility nv : noLongerVisibleAr) {
509                 State state = getState(nv.key);
510                 state.mIsVisible = false;
511             }
512         }
513 
514         @VisibleForTesting
onEntryRemoved(String key)515         void onEntryRemoved(String key) {
516             mExpansionStates.remove(key);
517             mLoggedExpansionState.remove(key);
518         }
519 
520         @VisibleForTesting
onEntryUpdated(String key)521         void onEntryUpdated(String key) {
522             // When the notification is updated, we should consider the notification as not
523             // yet logged.
524             mLoggedExpansionState.remove(key);
525         }
526 
getState(String key)527         private State getState(String key) {
528             State state = mExpansionStates.get(key);
529             if (state == null) {
530                 state = new State();
531                 mExpansionStates.put(key, state);
532             }
533             return state;
534         }
535 
maybeNotifyOnNotificationExpansionChanged(final String key, State state)536         private void maybeNotifyOnNotificationExpansionChanged(final String key, State state) {
537             if (!state.isFullySet()) {
538                 return;
539             }
540             if (!state.mIsVisible) {
541                 return;
542             }
543             Boolean loggedExpansionState = mLoggedExpansionState.get(key);
544             // Consider notification is initially collapsed, so only expanded is logged in the
545             // first time.
546             if (loggedExpansionState == null && !state.mIsExpanded) {
547                 return;
548             }
549             if (loggedExpansionState != null
550                     && state.mIsExpanded == loggedExpansionState) {
551                 return;
552             }
553             mLoggedExpansionState.put(key, state.mIsExpanded);
554             final State stateToBeLogged = new State(state);
555             mUiBgExecutor.execute(() -> {
556                 try {
557                     mBarService.onNotificationExpansionChanged(key, stateToBeLogged.mIsUserAction,
558                             stateToBeLogged.mIsExpanded, stateToBeLogged.mLocation.ordinal());
559                 } catch (RemoteException e) {
560                     Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e);
561                 }
562             });
563         }
564 
565         private static class State {
566             @Nullable
567             Boolean mIsUserAction;
568             @Nullable
569             Boolean mIsExpanded;
570             @Nullable
571             Boolean mIsVisible;
572             @Nullable
573             NotificationVisibility.NotificationLocation mLocation;
574 
State()575             private State() {}
576 
State(State state)577             private State(State state) {
578                 this.mIsUserAction = state.mIsUserAction;
579                 this.mIsExpanded = state.mIsExpanded;
580                 this.mIsVisible = state.mIsVisible;
581                 this.mLocation = state.mLocation;
582             }
583 
isFullySet()584             private boolean isFullySet() {
585                 return mIsUserAction != null && mIsExpanded != null && mIsVisible != null
586                         && mLocation != null;
587             }
588         }
589     }
590 }
591