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