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
17package com.android.server.wifi.rtt;
18
19import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
20
21import android.app.ActivityManager;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.pm.PackageManager;
27import android.database.ContentObserver;
28import android.hardware.wifi.V1_0.RttResult;
29import android.hardware.wifi.V1_0.RttStatus;
30import android.location.LocationManager;
31import android.net.MacAddress;
32import android.net.wifi.aware.IWifiAwareMacAddressProvider;
33import android.net.wifi.aware.IWifiAwareManager;
34import android.net.wifi.rtt.IRttCallback;
35import android.net.wifi.rtt.IWifiRttManager;
36import android.net.wifi.rtt.RangingRequest;
37import android.net.wifi.rtt.RangingResult;
38import android.net.wifi.rtt.RangingResultCallback;
39import android.net.wifi.rtt.ResponderConfig;
40import android.net.wifi.rtt.WifiRttManager;
41import android.os.Binder;
42import android.os.Handler;
43import android.os.IBinder;
44import android.os.Looper;
45import android.os.PowerManager;
46import android.os.RemoteException;
47import android.os.ResultReceiver;
48import android.os.ShellCallback;
49import android.os.ShellCommand;
50import android.os.UserHandle;
51import android.os.WorkSource;
52import android.os.WorkSource.WorkChain;
53import android.provider.Settings;
54import android.util.Log;
55import android.util.SparseIntArray;
56
57import com.android.internal.util.WakeupMessage;
58import com.android.server.wifi.Clock;
59import com.android.server.wifi.FrameworkFacade;
60import com.android.server.wifi.nano.WifiMetricsProto;
61import com.android.server.wifi.util.NativeUtil;
62import com.android.server.wifi.util.WifiPermissionsUtil;
63
64import java.io.FileDescriptor;
65import java.io.PrintWriter;
66import java.util.ArrayList;
67import java.util.Arrays;
68import java.util.HashMap;
69import java.util.LinkedList;
70import java.util.List;
71import java.util.ListIterator;
72import java.util.Map;
73
74/**
75 * Implementation of the IWifiRttManager AIDL interface and of the RttService state manager.
76 */
77public class RttServiceImpl extends IWifiRttManager.Stub {
78    private static final String TAG = "RttServiceImpl";
79    private static final boolean VDBG = false; // STOPSHIP if true
80    private boolean mDbg = false;
81
82    private final Context mContext;
83    private final RttShellCommand mShellCommand;
84    private Clock mClock;
85    private IWifiAwareManager mAwareBinder;
86    private RttNative mRttNative;
87    private RttMetrics mRttMetrics;
88    private WifiPermissionsUtil mWifiPermissionsUtil;
89    private ActivityManager mActivityManager;
90    private PowerManager mPowerManager;
91    private LocationManager mLocationManager;
92    private FrameworkFacade mFrameworkFacade;
93    private long mBackgroundProcessExecGapMs;
94
95    private RttServiceSynchronized mRttServiceSynchronized;
96
97    private static final int CONVERSION_US_TO_MS = 1_000;
98
99    /* package */ static final String HAL_RANGING_TIMEOUT_TAG = TAG + " HAL Ranging Timeout";
100
101    private static final long HAL_RANGING_TIMEOUT_MS = 5_000; // 5 sec
102
103    // Default value for RTT background throttling interval.
104    private static final long DEFAULT_BACKGROUND_PROCESS_EXEC_GAP_MS = 1_800_000; // 30 min
105
106    // arbitrary, larger than anything reasonable
107    /* package */ static final int MAX_QUEUED_PER_UID = 20;
108
109    public RttServiceImpl(Context context) {
110        mContext = context;
111        mShellCommand = new RttShellCommand();
112        mShellCommand.reset();
113    }
114
115    /*
116     * Shell command: adb shell cmd wifirtt ...
117     */
118
119    // If set to 0: normal behavior, if set to 1: do not allow any caller (including system
120    // callers) privileged API access
121    private static final String CONTROL_PARAM_OVERRIDE_ASSUME_NO_PRIVILEGE_NAME =
122            "override_assume_no_privilege";
123    private static final int CONTROL_PARAM_OVERRIDE_ASSUME_NO_PRIVILEGE_DEFAULT = 0;
124
125    private class RttShellCommand extends ShellCommand {
126        private Map<String, Integer> mControlParams = new HashMap<>();
127
128        @Override
129        public int onCommand(String cmd) {
130            final int uid = Binder.getCallingUid();
131            if (uid != 0) {
132                throw new SecurityException(
133                        "Uid " + uid + " does not have access to wifirtt commands");
134            }
135
136            final PrintWriter pw = getErrPrintWriter();
137            try {
138                if ("reset".equals(cmd)) {
139                    reset();
140                    return 0;
141                } else if ("get".equals(cmd)) {
142                    String name = getNextArgRequired();
143                    if (!mControlParams.containsKey(name)) {
144                        pw.println("Unknown parameter name -- '" + name + "'");
145                        return -1;
146                    }
147                    getOutPrintWriter().println(mControlParams.get(name));
148                    return 0;
149                } else if ("set".equals(cmd)) {
150                    String name = getNextArgRequired();
151                    String valueStr = getNextArgRequired();
152
153                    if (!mControlParams.containsKey(name)) {
154                        pw.println("Unknown parameter name -- '" + name + "'");
155                        return -1;
156                    }
157
158                    try {
159                        mControlParams.put(name, Integer.valueOf(valueStr));
160                        return 0;
161                    } catch (NumberFormatException e) {
162                        pw.println("Can't convert value to integer -- '" + valueStr + "'");
163                        return -1;
164                    }
165                } else {
166                    handleDefaultCommands(cmd);
167                }
168            } catch (Exception e) {
169                pw.println("Exception: " + e);
170            }
171            return -1;
172        }
173
174        @Override
175        public void onHelp() {
176            final PrintWriter pw = getOutPrintWriter();
177
178            pw.println("Wi-Fi RTT (wifirt) commands:");
179            pw.println("  help");
180            pw.println("    Print this help text.");
181            pw.println("  reset");
182            pw.println("    Reset parameters to default values.");
183            pw.println("  get <name>");
184            pw.println("    Get the value of the control parameter.");
185            pw.println("  set <name> <value>");
186            pw.println("    Set the value of the control parameter.");
187            pw.println("  Control parameters:");
188            for (String name : mControlParams.keySet()) {
189                pw.println("    " + name);
190            }
191            pw.println();
192        }
193
194        public int getControlParam(String name) {
195            if (mControlParams.containsKey(name)) {
196                return mControlParams.get(name);
197            }
198
199            Log.wtf(TAG, "getControlParam for unknown variable: " + name);
200            return 0;
201        }
202
203        public void reset() {
204            mControlParams.put(CONTROL_PARAM_OVERRIDE_ASSUME_NO_PRIVILEGE_NAME,
205                    CONTROL_PARAM_OVERRIDE_ASSUME_NO_PRIVILEGE_DEFAULT);
206        }
207    }
208
209    /*
210     * INITIALIZATION
211     */
212
213    /**
214     * Initializes the RTT service (usually with objects from an injector).
215     *
216     * @param looper The looper on which to synchronize operations.
217     * @param clock A mockable clock.
218     * @param awareBinder The Wi-Fi Aware service (binder) if supported on the system.
219     * @param rttNative The Native interface to the HAL.
220     * @param rttMetrics The Wi-Fi RTT metrics object.
221     * @param wifiPermissionsUtil Utility for permission checks.
222     * @param frameworkFacade Facade for framework classes, allows mocking.
223     */
224    public void start(Looper looper, Clock clock, IWifiAwareManager awareBinder,
225            RttNative rttNative, RttMetrics rttMetrics, WifiPermissionsUtil wifiPermissionsUtil,
226            FrameworkFacade frameworkFacade) {
227        mClock = clock;
228        mAwareBinder = awareBinder;
229        mRttNative = rttNative;
230        mRttMetrics = rttMetrics;
231        mWifiPermissionsUtil = wifiPermissionsUtil;
232        mFrameworkFacade = frameworkFacade;
233        mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
234
235        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
236        mPowerManager = mContext.getSystemService(PowerManager.class);
237        mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
238        IntentFilter intentFilter = new IntentFilter();
239        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
240        mContext.registerReceiver(new BroadcastReceiver() {
241            @Override
242            public void onReceive(Context context, Intent intent) {
243                String action = intent.getAction();
244                if (mDbg) Log.v(TAG, "BroadcastReceiver: action=" + action);
245
246                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
247                    if (mPowerManager.isDeviceIdleMode()) {
248                        disable();
249                    } else {
250                        enableIfPossible();
251                    }
252                }
253            }
254        }, intentFilter);
255
256        frameworkFacade.registerContentObserver(mContext,
257                Settings.Global.getUriFor(Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED), true,
258                new ContentObserver(mRttServiceSynchronized.mHandler) {
259                    @Override
260                    public void onChange(boolean selfChange) {
261                        enableVerboseLogging(frameworkFacade.getIntegerSetting(mContext,
262                                Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0));
263                    }
264                });
265
266        enableVerboseLogging(frameworkFacade.getIntegerSetting(mContext,
267                Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0));
268
269        frameworkFacade.registerContentObserver(mContext,
270                Settings.Global.getUriFor(Settings.Global.WIFI_RTT_BACKGROUND_EXEC_GAP_MS),
271                true,
272                new ContentObserver(mRttServiceSynchronized.mHandler) {
273                    @Override
274                    public void onChange(boolean selfChange) {
275                        updateBackgroundThrottlingInterval(frameworkFacade);
276                    }
277                });
278
279        updateBackgroundThrottlingInterval(frameworkFacade);
280
281        intentFilter = new IntentFilter();
282        intentFilter.addAction(LocationManager.MODE_CHANGED_ACTION);
283        mContext.registerReceiver(new BroadcastReceiver() {
284            @Override
285            public void onReceive(Context context, Intent intent) {
286                if (mDbg) Log.v(TAG, "onReceive: MODE_CHANGED_ACTION: intent=" + intent);
287                if (mLocationManager.isLocationEnabled()) {
288                    enableIfPossible();
289                } else {
290                    disable();
291                }
292            }
293        }, intentFilter);
294
295        mRttServiceSynchronized.mHandler.post(() -> {
296            rttNative.start(mRttServiceSynchronized.mHandler);
297        });
298    }
299
300    private void enableVerboseLogging(int verbose) {
301        if (verbose > 0) {
302            mDbg = true;
303        } else {
304            mDbg = false;
305        }
306        if (VDBG) {
307            mDbg = true; // just override
308        }
309        mRttNative.mDbg = mDbg;
310        mRttMetrics.mDbg = mDbg;
311    }
312
313    private void updateBackgroundThrottlingInterval(FrameworkFacade frameworkFacade) {
314        mBackgroundProcessExecGapMs = frameworkFacade.getLongSetting(mContext,
315            Settings.Global.WIFI_RTT_BACKGROUND_EXEC_GAP_MS,
316            DEFAULT_BACKGROUND_PROCESS_EXEC_GAP_MS);
317    }
318
319    /*
320     * ASYNCHRONOUS DOMAIN - can be called from different threads!
321     */
322
323    /**
324     * Proxy for the final native call of the parent class. Enables mocking of
325     * the function.
326     */
327    public int getMockableCallingUid() {
328        return getCallingUid();
329    }
330
331    /**
332     * Enable the API if possible: broadcast notification & start launching any queued requests
333     *
334     * If possible:
335     * - RTT HAL is available
336     * - Not in Idle mode
337     * - Location Mode allows Wi-Fi based locationing
338     */
339    public void enableIfPossible() {
340        boolean isAvailable = isAvailable();
341        if (VDBG) Log.v(TAG, "enableIfPossible: isAvailable=" + isAvailable);
342        if (!isAvailable) {
343            return;
344        }
345        sendRttStateChangedBroadcast(true);
346        mRttServiceSynchronized.mHandler.post(() -> {
347            // queue should be empty at this point (but this call allows validation)
348            mRttServiceSynchronized.executeNextRangingRequestIfPossible(false);
349        });
350    }
351
352    /**
353     * Disable the API:
354     * - Clean-up (fail) pending requests
355     * - Broadcast notification
356     */
357    public void disable() {
358        if (VDBG) Log.v(TAG, "disable");
359        sendRttStateChangedBroadcast(false);
360        mRttServiceSynchronized.mHandler.post(() -> {
361            mRttServiceSynchronized.cleanUpOnDisable();
362        });
363    }
364
365    @Override
366    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
367            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
368        mShellCommand.exec(this, in, out, err, args, callback, resultReceiver);
369    }
370
371    /**
372     * Binder interface API to indicate whether the API is currently available. This requires an
373     * immediate asynchronous response.
374     */
375    @Override
376    public boolean isAvailable() {
377        return mRttNative.isReady() && !mPowerManager.isDeviceIdleMode()
378                && mLocationManager.isLocationEnabled();
379    }
380
381    /**
382     * Binder interface API to start a ranging operation. Called on binder thread, operations needs
383     * to be posted to handler thread.
384     */
385    @Override
386    public void startRanging(IBinder binder, String callingPackage, WorkSource workSource,
387            RangingRequest request, IRttCallback callback) throws RemoteException {
388        if (VDBG) {
389            Log.v(TAG, "startRanging: binder=" + binder + ", callingPackage=" + callingPackage
390                    + ", workSource=" + workSource + ", request=" + request + ", callback="
391                    + callback);
392        }
393        // verify arguments
394        if (binder == null) {
395            throw new IllegalArgumentException("Binder must not be null");
396        }
397        if (request == null || request.mRttPeers == null || request.mRttPeers.size() == 0) {
398            throw new IllegalArgumentException("Request must not be null or empty");
399        }
400        for (ResponderConfig responder : request.mRttPeers) {
401            if (responder == null) {
402                throw new IllegalArgumentException("Request must not contain null Responders");
403            }
404        }
405        if (callback == null) {
406            throw new IllegalArgumentException("Callback must not be null");
407        }
408        request.enforceValidity(mAwareBinder != null);
409
410        if (!isAvailable()) {
411            try {
412                mRttMetrics.recordOverallStatus(
413                        WifiMetricsProto.WifiRttLog.OVERALL_RTT_NOT_AVAILABLE);
414                callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
415            } catch (RemoteException e) {
416                Log.e(TAG, "startRanging: disabled, callback failed -- " + e);
417            }
418            return;
419        }
420
421        final int uid = getMockableCallingUid();
422
423        // permission checks
424        enforceAccessPermission();
425        enforceChangePermission();
426        mWifiPermissionsUtil.enforceFineLocationPermission(callingPackage, uid);
427        if (workSource != null) {
428            enforceLocationHardware();
429            // We only care about UIDs in the incoming worksources and not their associated
430            // tags. Clear names so that other operations involving wakesources become simpler.
431            workSource.clearNames();
432        }
433        boolean isCalledFromPrivilegedContext =
434                checkLocationHardware() && mShellCommand.getControlParam(
435                        CONTROL_PARAM_OVERRIDE_ASSUME_NO_PRIVILEGE_NAME) == 0;
436
437        // register for binder death
438        IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
439            @Override
440            public void binderDied() {
441                if (mDbg) Log.v(TAG, "binderDied: uid=" + uid);
442                binder.unlinkToDeath(this, 0);
443
444                mRttServiceSynchronized.mHandler.post(() -> {
445                    mRttServiceSynchronized.cleanUpClientRequests(uid, null);
446                });
447            }
448        };
449
450        try {
451            binder.linkToDeath(dr, 0);
452        } catch (RemoteException e) {
453            Log.e(TAG, "Error on linkToDeath - " + e);
454            return;
455        }
456
457        mRttServiceSynchronized.mHandler.post(() -> {
458            WorkSource sourceToUse = workSource;
459            if (workSource == null || workSource.isEmpty()) {
460                sourceToUse = new WorkSource(uid);
461            }
462            mRttServiceSynchronized.queueRangingRequest(uid, sourceToUse, binder, dr,
463                    callingPackage, request, callback, isCalledFromPrivilegedContext);
464        });
465    }
466
467    @Override
468    public void cancelRanging(WorkSource workSource) throws RemoteException {
469        if (VDBG) Log.v(TAG, "cancelRanging: workSource=" + workSource);
470        enforceLocationHardware();
471        if (workSource != null) {
472            // We only care about UIDs in the incoming worksources and not their associated
473            // tags. Clear names so that other operations involving wakesources become simpler.
474            workSource.clearNames();
475        }
476
477        if (workSource == null || workSource.isEmpty()) {
478            Log.e(TAG, "cancelRanging: invalid work-source -- " + workSource);
479            return;
480        }
481
482        mRttServiceSynchronized.mHandler.post(() -> {
483            mRttServiceSynchronized.cleanUpClientRequests(0, workSource);
484        });
485    }
486
487    /**
488     * Called by HAL to report ranging results. Called on HAL thread - needs to post to local
489     * thread.
490     */
491    public void onRangingResults(int cmdId, List<RttResult> results) {
492        if (VDBG) Log.v(TAG, "onRangingResults: cmdId=" + cmdId);
493        mRttServiceSynchronized.mHandler.post(() -> {
494            mRttServiceSynchronized.onRangingResults(cmdId, results);
495        });
496    }
497
498    private void enforceAccessPermission() {
499        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
500    }
501
502    private void enforceChangePermission() {
503        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
504    }
505
506    private void enforceLocationHardware() {
507        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.LOCATION_HARDWARE,
508                TAG);
509    }
510
511    private boolean checkLocationHardware() {
512        return mContext.checkCallingOrSelfPermission(android.Manifest.permission.LOCATION_HARDWARE)
513                == PackageManager.PERMISSION_GRANTED;
514    }
515
516    private void sendRttStateChangedBroadcast(boolean enabled) {
517        if (VDBG) Log.v(TAG, "sendRttStateChangedBroadcast: enabled=" + enabled);
518        final Intent intent = new Intent(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
519        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
520        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
521    }
522
523    @Override
524    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
525        if (mContext.checkCallingOrSelfPermission(
526                android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
527            pw.println("Permission Denial: can't dump RttService from pid="
528                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
529            return;
530        }
531        pw.println("Wi-Fi RTT Service");
532        mRttServiceSynchronized.dump(fd, pw, args);
533    }
534
535    /*
536     * SYNCHRONIZED DOMAIN
537     */
538
539    /**
540     * RTT service implementation - synchronized on a single thread. All commands should be posted
541     * to the exposed handler.
542     */
543    private class RttServiceSynchronized {
544        public Handler mHandler;
545
546        private RttNative mRttNative;
547        private int mNextCommandId = 1000;
548        private Map<Integer, RttRequesterInfo> mRttRequesterInfo = new HashMap<>();
549        private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>();
550        private WakeupMessage mRangingTimeoutMessage = null;
551
552        RttServiceSynchronized(Looper looper, RttNative rttNative) {
553            mRttNative = rttNative;
554
555            mHandler = new Handler(looper);
556            mRangingTimeoutMessage = new WakeupMessage(mContext, mHandler,
557                    HAL_RANGING_TIMEOUT_TAG, () -> {
558                timeoutRangingRequest();
559            });
560        }
561
562        private void cancelRanging(RttRequestInfo rri) {
563            ArrayList<byte[]> macAddresses = new ArrayList<>();
564            for (ResponderConfig peer : rri.request.mRttPeers) {
565                macAddresses.add(peer.macAddress.toByteArray());
566            }
567
568            mRttNative.rangeCancel(rri.cmdId, macAddresses);
569        }
570
571        private void cleanUpOnDisable() {
572            if (VDBG) Log.v(TAG, "RttServiceSynchronized.cleanUpOnDisable");
573            for (RttRequestInfo rri : mRttRequestQueue) {
574                try {
575                    if (rri.dispatchedToNative) {
576                        // may not be necessary in some cases (e.g. Wi-Fi disable may already clear
577                        // up active RTT), but in other cases will be needed (doze disabling RTT
578                        // but Wi-Fi still up). Doesn't hurt - worst case will fail.
579                        cancelRanging(rri);
580                    }
581                    mRttMetrics.recordOverallStatus(
582                            WifiMetricsProto.WifiRttLog.OVERALL_RTT_NOT_AVAILABLE);
583                    rri.callback.onRangingFailure(
584                            RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
585                } catch (RemoteException e) {
586                    Log.e(TAG, "RttServiceSynchronized.startRanging: disabled, callback failed -- "
587                            + e);
588                }
589                rri.binder.unlinkToDeath(rri.dr, 0);
590            }
591            mRttRequestQueue.clear();
592            mRangingTimeoutMessage.cancel();
593        }
594
595        /**
596         * Remove entries related to the specified client and cancel any dispatched to HAL
597         * requests. Expected to provide either the UID or the WorkSource (the other will be 0 or
598         * null respectively).
599         *
600         * A workSource specification will be cleared from the requested workSource and the request
601         * cancelled only if there are no remaining uids in the work-source.
602         */
603        private void cleanUpClientRequests(int uid, WorkSource workSource) {
604            if (VDBG) {
605                Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
606                        + ", workSource=" + workSource + ", mRttRequestQueue=" + mRttRequestQueue);
607            }
608            boolean dispatchedRequestAborted = false;
609            ListIterator<RttRequestInfo> it = mRttRequestQueue.listIterator();
610            while (it.hasNext()) {
611                RttRequestInfo rri = it.next();
612
613                boolean match = rri.uid == uid; // original UID will never be 0
614                if (rri.workSource != null && workSource != null) {
615                    rri.workSource.remove(workSource);
616                    if (rri.workSource.isEmpty()) {
617                        match = true;
618                    }
619                }
620
621                if (match) {
622                    if (!rri.dispatchedToNative) {
623                        it.remove();
624                        rri.binder.unlinkToDeath(rri.dr, 0);
625                    } else {
626                        dispatchedRequestAborted = true;
627                        Log.d(TAG, "Client death - cancelling RTT operation in progress: cmdId="
628                                + rri.cmdId);
629                        mRangingTimeoutMessage.cancel();
630                        cancelRanging(rri);
631                    }
632                }
633            }
634
635            if (VDBG) {
636                Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
637                        + ", dispatchedRequestAborted=" + dispatchedRequestAborted
638                        + ", after cleanup - mRttRequestQueue=" + mRttRequestQueue);
639            }
640
641            if (dispatchedRequestAborted) {
642                executeNextRangingRequestIfPossible(true);
643            }
644        }
645
646        private void timeoutRangingRequest() {
647            if (VDBG) {
648                Log.v(TAG, "RttServiceSynchronized.timeoutRangingRequest mRttRequestQueue="
649                        + mRttRequestQueue);
650            }
651            if (mRttRequestQueue.size() == 0) {
652                Log.w(TAG, "RttServiceSynchronized.timeoutRangingRequest: but nothing in queue!?");
653                return;
654            }
655            RttRequestInfo rri = mRttRequestQueue.get(0);
656            if (!rri.dispatchedToNative) {
657                Log.w(TAG, "RttServiceSynchronized.timeoutRangingRequest: command not dispatched "
658                        + "to native!?");
659                return;
660            }
661            cancelRanging(rri);
662            try {
663                mRttMetrics.recordOverallStatus(WifiMetricsProto.WifiRttLog.OVERALL_TIMEOUT);
664                rri.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
665            } catch (RemoteException e) {
666                Log.e(TAG, "RttServiceSynchronized.timeoutRangingRequest: callback failed: " + e);
667            }
668            executeNextRangingRequestIfPossible(true);
669        }
670
671        private void queueRangingRequest(int uid, WorkSource workSource, IBinder binder,
672                IBinder.DeathRecipient dr, String callingPackage, RangingRequest request,
673                IRttCallback callback, boolean isCalledFromPrivilegedContext) {
674            mRttMetrics.recordRequest(workSource, request);
675
676            if (isRequestorSpamming(workSource)) {
677                Log.w(TAG,
678                        "Work source " + workSource + " is spamming, dropping request: " + request);
679                binder.unlinkToDeath(dr, 0);
680                try {
681                    mRttMetrics.recordOverallStatus(WifiMetricsProto.WifiRttLog.OVERALL_THROTTLE);
682                    callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
683                } catch (RemoteException e) {
684                    Log.e(TAG, "RttServiceSynchronized.queueRangingRequest: spamming, callback "
685                            + "failed -- " + e);
686                }
687                return;
688            }
689
690            RttRequestInfo newRequest = new RttRequestInfo();
691            newRequest.uid = uid;
692            newRequest.workSource = workSource;
693            newRequest.binder = binder;
694            newRequest.dr = dr;
695            newRequest.callingPackage = callingPackage;
696            newRequest.request = request;
697            newRequest.callback = callback;
698            newRequest.isCalledFromPrivilegedContext = isCalledFromPrivilegedContext;
699            mRttRequestQueue.add(newRequest);
700
701            if (VDBG) {
702                Log.v(TAG, "RttServiceSynchronized.queueRangingRequest: newRequest=" + newRequest);
703            }
704
705            executeNextRangingRequestIfPossible(false);
706        }
707
708        private boolean isRequestorSpamming(WorkSource ws) {
709            if (VDBG) Log.v(TAG, "isRequestorSpamming: ws" + ws);
710
711            SparseIntArray counts = new SparseIntArray();
712
713            for (RttRequestInfo rri : mRttRequestQueue) {
714                for (int i = 0; i < rri.workSource.size(); ++i) {
715                    int uid = rri.workSource.get(i);
716                    counts.put(uid, counts.get(uid) + 1);
717                }
718
719                final ArrayList<WorkChain> workChains = rri.workSource.getWorkChains();
720                if (workChains != null) {
721                    for (int i = 0; i < workChains.size(); ++i) {
722                        final int uid = workChains.get(i).getAttributionUid();
723                        counts.put(uid, counts.get(uid) + 1);
724                    }
725                }
726            }
727
728            for (int i = 0; i < ws.size(); ++i) {
729                if (counts.get(ws.get(i)) < MAX_QUEUED_PER_UID) {
730                    return false;
731                }
732            }
733
734            final ArrayList<WorkChain> workChains = ws.getWorkChains();
735            if (workChains != null) {
736                for (int i = 0; i < workChains.size(); ++i) {
737                    final int uid = workChains.get(i).getAttributionUid();
738                    if (counts.get(uid) < MAX_QUEUED_PER_UID) {
739                        return false;
740                    }
741                }
742            }
743
744            if (mDbg) {
745                Log.v(TAG, "isRequestorSpamming: ws=" + ws + ", someone is spamming: " + counts);
746            }
747            return true;
748        }
749
750        private void executeNextRangingRequestIfPossible(boolean popFirst) {
751            if (VDBG) Log.v(TAG, "executeNextRangingRequestIfPossible: popFirst=" + popFirst);
752
753            if (popFirst) {
754                if (mRttRequestQueue.size() == 0) {
755                    Log.w(TAG, "executeNextRangingRequestIfPossible: pop requested - but empty "
756                            + "queue!? Ignoring pop.");
757                } else {
758                    RttRequestInfo topOfQueueRequest = mRttRequestQueue.remove(0);
759                    topOfQueueRequest.binder.unlinkToDeath(topOfQueueRequest.dr, 0);
760                }
761            }
762
763            if (mRttRequestQueue.size() == 0) {
764                if (VDBG) Log.v(TAG, "executeNextRangingRequestIfPossible: no requests pending");
765                return;
766            }
767
768            // if top of list is in progress then do nothing
769            RttRequestInfo nextRequest = mRttRequestQueue.get(0);
770            if (nextRequest.peerHandlesTranslated || nextRequest.dispatchedToNative) {
771                if (VDBG) {
772                    Log.v(TAG, "executeNextRangingRequestIfPossible: called but a command is "
773                            + "executing. topOfQueue=" + nextRequest);
774                }
775                return;
776            }
777
778            startRanging(nextRequest);
779        }
780
781        private void startRanging(RttRequestInfo nextRequest) {
782            if (VDBG) {
783                Log.v(TAG, "RttServiceSynchronized.startRanging: nextRequest=" + nextRequest);
784            }
785
786            if (!isAvailable()) {
787                Log.d(TAG, "RttServiceSynchronized.startRanging: disabled");
788                try {
789                    mRttMetrics.recordOverallStatus(
790                            WifiMetricsProto.WifiRttLog.OVERALL_RTT_NOT_AVAILABLE);
791                    nextRequest.callback.onRangingFailure(
792                            RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
793                } catch (RemoteException e) {
794                    Log.e(TAG, "RttServiceSynchronized.startRanging: disabled, callback failed -- "
795                            + e);
796                    executeNextRangingRequestIfPossible(true);
797                    return;
798                }
799            }
800
801            if (processAwarePeerHandles(nextRequest)) {
802                if (VDBG) {
803                    Log.v(TAG, "RttServiceSynchronized.startRanging: deferring due to PeerHandle "
804                            + "Aware requests");
805                }
806                return;
807            }
808
809            if (!preExecThrottleCheck(nextRequest.workSource)) {
810                Log.w(TAG, "RttServiceSynchronized.startRanging: execution throttled - nextRequest="
811                        + nextRequest + ", mRttRequesterInfo=" + mRttRequesterInfo);
812                try {
813                    mRttMetrics.recordOverallStatus(WifiMetricsProto.WifiRttLog.OVERALL_THROTTLE);
814                    nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
815                } catch (RemoteException e) {
816                    Log.e(TAG, "RttServiceSynchronized.startRanging: throttled, callback failed -- "
817                            + e);
818                }
819                executeNextRangingRequestIfPossible(true);
820                return;
821            }
822
823            nextRequest.cmdId = mNextCommandId++;
824            if (mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request,
825                    nextRequest.isCalledFromPrivilegedContext)) {
826                mRangingTimeoutMessage.schedule(
827                        mClock.getElapsedSinceBootMillis() + HAL_RANGING_TIMEOUT_MS);
828            } else {
829                Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed");
830                try {
831                    mRttMetrics.recordOverallStatus(
832                            WifiMetricsProto.WifiRttLog.OVERALL_HAL_FAILURE);
833                    nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
834                } catch (RemoteException e) {
835                    Log.e(TAG, "RttServiceSynchronized.startRanging: HAL request failed, callback "
836                            + "failed -- " + e);
837                }
838                executeNextRangingRequestIfPossible(true);
839            }
840            nextRequest.dispatchedToNative = true;
841        }
842
843        /**
844         * Perform pre-execution throttling checks:
845         * - If all uids in ws are in background then check last execution and block if request is
846         * more frequent than permitted
847         * - If executing (i.e. permitted) then update execution time
848         *
849         * Returns true to permit execution, false to abort it.
850         */
851        private boolean preExecThrottleCheck(WorkSource ws) {
852            if (VDBG) Log.v(TAG, "preExecThrottleCheck: ws=" + ws);
853
854            // are all UIDs running in the background or is at least 1 in the foreground?
855            boolean allUidsInBackground = true;
856            for (int i = 0; i < ws.size(); ++i) {
857                int uidImportance = mActivityManager.getUidImportance(ws.get(i));
858                if (VDBG) {
859                    Log.v(TAG, "preExecThrottleCheck: uid=" + ws.get(i) + " -> importance="
860                            + uidImportance);
861                }
862                if (uidImportance <= IMPORTANCE_FOREGROUND_SERVICE) {
863                    allUidsInBackground = false;
864                    break;
865                }
866            }
867
868            final ArrayList<WorkChain> workChains = ws.getWorkChains();
869            if (allUidsInBackground && workChains != null) {
870                for (int i = 0; i < workChains.size(); ++i) {
871                    final WorkChain wc = workChains.get(i);
872                    int uidImportance = mActivityManager.getUidImportance(wc.getAttributionUid());
873                    if (VDBG) {
874                        Log.v(TAG, "preExecThrottleCheck: workChain=" + wc + " -> importance="
875                                + uidImportance);
876                    }
877
878                    if (uidImportance <= IMPORTANCE_FOREGROUND_SERVICE) {
879                        allUidsInBackground = false;
880                        break;
881                    }
882                }
883            }
884
885            // if all UIDs are in background then check timestamp since last execution and see if
886            // any is permitted (infrequent enough)
887            boolean allowExecution = false;
888            long mostRecentExecutionPermitted =
889                    mClock.getElapsedSinceBootMillis() - mBackgroundProcessExecGapMs;
890            if (allUidsInBackground) {
891                for (int i = 0; i < ws.size(); ++i) {
892                    RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i));
893                    if (info == null || info.lastRangingExecuted < mostRecentExecutionPermitted) {
894                        allowExecution = true;
895                        break;
896                    }
897                }
898
899                if (workChains != null & !allowExecution) {
900                    for (int i = 0; i < workChains.size(); ++i) {
901                        final WorkChain wc = workChains.get(i);
902                        RttRequesterInfo info = mRttRequesterInfo.get(wc.getAttributionUid());
903                        if (info == null
904                                || info.lastRangingExecuted < mostRecentExecutionPermitted) {
905                            allowExecution = true;
906                            break;
907                        }
908                    }
909                }
910            } else {
911                allowExecution = true;
912            }
913
914            // update exec time
915            if (allowExecution) {
916                for (int i = 0; i < ws.size(); ++i) {
917                    RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i));
918                    if (info == null) {
919                        info = new RttRequesterInfo();
920                        mRttRequesterInfo.put(ws.get(i), info);
921                    }
922                    info.lastRangingExecuted = mClock.getElapsedSinceBootMillis();
923                }
924
925                if (workChains != null) {
926                    for (int i = 0; i < workChains.size(); ++i) {
927                        final WorkChain wc = workChains.get(i);
928                        RttRequesterInfo info = mRttRequesterInfo.get(wc.getAttributionUid());
929                        if (info == null) {
930                            info = new RttRequesterInfo();
931                            mRttRequesterInfo.put(wc.getAttributionUid(), info);
932                        }
933                        info.lastRangingExecuted = mClock.getElapsedSinceBootMillis();
934                    }
935                }
936            }
937
938            return allowExecution;
939        }
940
941        /**
942         * Check request for any PeerHandle Aware requests. If there are any: issue requests to
943         * translate the peer ID to a MAC address and abort current execution of the range request.
944         * The request will be re-attempted when response is received.
945         *
946         * In cases of failure: pop the current request and execute the next one. Failures:
947         * - Not able to connect to remote service (unlikely)
948         * - Request already processed: but we're missing information
949         *
950         * @return true if need to abort execution, false otherwise.
951         */
952        private boolean processAwarePeerHandles(RttRequestInfo request) {
953            List<Integer> peerIdsNeedingTranslation = new ArrayList<>();
954            for (ResponderConfig rttPeer : request.request.mRttPeers) {
955                if (rttPeer.peerHandle != null && rttPeer.macAddress == null) {
956                    peerIdsNeedingTranslation.add(rttPeer.peerHandle.peerId);
957                }
958            }
959
960            if (peerIdsNeedingTranslation.size() == 0) {
961                return false;
962            }
963
964            if (request.peerHandlesTranslated) {
965                Log.w(TAG, "processAwarePeerHandles: request=" + request
966                        + ": PeerHandles translated - but information still missing!?");
967                try {
968                    mRttMetrics.recordOverallStatus(
969                            WifiMetricsProto.WifiRttLog.OVERALL_AWARE_TRANSLATION_FAILURE);
970                    request.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
971                } catch (RemoteException e) {
972                    Log.e(TAG, "processAwarePeerHandles: onRangingResults failure -- " + e);
973                }
974                executeNextRangingRequestIfPossible(true);
975                return true; // an abort because we removed request and are executing next one
976            }
977
978            request.peerHandlesTranslated = true;
979            try {
980                mAwareBinder.requestMacAddresses(request.uid, peerIdsNeedingTranslation,
981                        new IWifiAwareMacAddressProvider.Stub() {
982                            @Override
983                            public void macAddress(Map peerIdToMacMap) {
984                                // ASYNC DOMAIN
985                                mHandler.post(() -> {
986                                    // BACK TO SYNC DOMAIN
987                                    processReceivedAwarePeerMacAddresses(request, peerIdToMacMap);
988                                });
989                            }
990                        });
991            } catch (RemoteException e1) {
992                Log.e(TAG,
993                        "processAwarePeerHandles: exception while calling requestMacAddresses -- "
994                                + e1 + ", aborting request=" + request);
995                try {
996                    mRttMetrics.recordOverallStatus(
997                            WifiMetricsProto.WifiRttLog.OVERALL_AWARE_TRANSLATION_FAILURE);
998                    request.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
999                } catch (RemoteException e2) {
1000                    Log.e(TAG, "processAwarePeerHandles: onRangingResults failure -- " + e2);
1001                }
1002                executeNextRangingRequestIfPossible(true);
1003                return true; // an abort because we removed request and are executing next one
1004            }
1005
1006            return true; // a deferral
1007        }
1008
1009        private void processReceivedAwarePeerMacAddresses(RttRequestInfo request,
1010                Map<Integer, byte[]> peerIdToMacMap) {
1011            if (VDBG) {
1012                Log.v(TAG, "processReceivedAwarePeerMacAddresses: request=" + request
1013                        + ", peerIdToMacMap=" + peerIdToMacMap);
1014            }
1015
1016            RangingRequest.Builder newRequestBuilder = new RangingRequest.Builder();
1017            for (ResponderConfig rttPeer : request.request.mRttPeers) {
1018                if (rttPeer.peerHandle != null && rttPeer.macAddress == null) {
1019                    newRequestBuilder.addResponder(new ResponderConfig(
1020                            MacAddress.fromBytes(peerIdToMacMap.get(rttPeer.peerHandle.peerId)),
1021                            rttPeer.peerHandle, rttPeer.responderType, rttPeer.supports80211mc,
1022                            rttPeer.channelWidth, rttPeer.frequency, rttPeer.centerFreq0,
1023                            rttPeer.centerFreq1, rttPeer.preamble));
1024                } else {
1025                    newRequestBuilder.addResponder(rttPeer);
1026                }
1027            }
1028            request.request = newRequestBuilder.build();
1029
1030            // run request again
1031            startRanging(request);
1032        }
1033
1034        private void onRangingResults(int cmdId, List<RttResult> results) {
1035            if (mRttRequestQueue.size() == 0) {
1036                Log.e(TAG, "RttServiceSynchronized.onRangingResults: no current RTT request "
1037                        + "pending!?");
1038                return;
1039            }
1040            mRangingTimeoutMessage.cancel();
1041            RttRequestInfo topOfQueueRequest = mRttRequestQueue.get(0);
1042
1043            if (VDBG) {
1044                Log.v(TAG, "RttServiceSynchronized.onRangingResults: cmdId=" + cmdId
1045                        + ", topOfQueueRequest=" + topOfQueueRequest + ", results="
1046                        + Arrays.toString(results.toArray()));
1047            }
1048
1049            if (topOfQueueRequest.cmdId != cmdId) {
1050                Log.e(TAG, "RttServiceSynchronized.onRangingResults: cmdId=" + cmdId
1051                        + ", does not match pending RTT request cmdId=" + topOfQueueRequest.cmdId);
1052                return;
1053            }
1054
1055            boolean permissionGranted = mWifiPermissionsUtil.checkCallersLocationPermission(
1056                    topOfQueueRequest.callingPackage, topOfQueueRequest.uid)
1057                    && mLocationManager.isLocationEnabled();
1058            try {
1059                if (permissionGranted) {
1060                    List<RangingResult> finalResults = postProcessResults(topOfQueueRequest.request,
1061                            results, topOfQueueRequest.isCalledFromPrivilegedContext);
1062                    mRttMetrics.recordOverallStatus(WifiMetricsProto.WifiRttLog.OVERALL_SUCCESS);
1063                    mRttMetrics.recordResult(topOfQueueRequest.request, results);
1064                    if (VDBG) {
1065                        Log.v(TAG, "RttServiceSynchronized.onRangingResults: finalResults="
1066                                + finalResults);
1067                    }
1068                    topOfQueueRequest.callback.onRangingResults(finalResults);
1069                } else {
1070                    Log.w(TAG, "RttServiceSynchronized.onRangingResults: location permission "
1071                            + "revoked - not forwarding results");
1072                    mRttMetrics.recordOverallStatus(
1073                            WifiMetricsProto.WifiRttLog.OVERALL_LOCATION_PERMISSION_MISSING);
1074                    topOfQueueRequest.callback.onRangingFailure(
1075                            RangingResultCallback.STATUS_CODE_FAIL);
1076                }
1077            } catch (RemoteException e) {
1078                Log.e(TAG,
1079                        "RttServiceSynchronized.onRangingResults: callback exception -- " + e);
1080            }
1081
1082            executeNextRangingRequestIfPossible(true);
1083        }
1084
1085        /*
1086         * Post process the results:
1087         * - For requests without results: add FAILED results
1088         * - For Aware requests using PeerHandle: replace MAC address with PeerHandle
1089         * - Effectively: throws away results which don't match requests
1090         */
1091        private List<RangingResult> postProcessResults(RangingRequest request,
1092                List<RttResult> results, boolean isCalledFromPrivilegedContext) {
1093            Map<MacAddress, RttResult> resultEntries = new HashMap<>();
1094            for (RttResult result : results) {
1095                resultEntries.put(MacAddress.fromBytes(result.addr), result);
1096            }
1097
1098            List<RangingResult> finalResults = new ArrayList<>(request.mRttPeers.size());
1099
1100            for (ResponderConfig peer : request.mRttPeers) {
1101                RttResult resultForRequest = resultEntries.get(peer.macAddress);
1102                if (resultForRequest == null) {
1103                    if (mDbg) {
1104                        Log.v(TAG, "postProcessResults: missing=" + peer.macAddress);
1105                    }
1106
1107                    int errorCode = RangingResult.STATUS_FAIL;
1108                    if (!isCalledFromPrivilegedContext) {
1109                        if (!peer.supports80211mc) {
1110                            errorCode = RangingResult.STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC;
1111                        }
1112                    }
1113
1114                    if (peer.peerHandle == null) {
1115                        finalResults.add(
1116                                new RangingResult(errorCode, peer.macAddress, 0, 0, 0, 0, 0, null,
1117                                        null, 0));
1118                    } else {
1119                        finalResults.add(
1120                                new RangingResult(errorCode, peer.peerHandle, 0, 0, 0, 0, 0, null,
1121                                        null, 0));
1122                    }
1123                } else {
1124                    int status = resultForRequest.status == RttStatus.SUCCESS
1125                            ? RangingResult.STATUS_SUCCESS : RangingResult.STATUS_FAIL;
1126                    byte[] lci = null;
1127                    byte[] lcr = null;
1128                    if (isCalledFromPrivilegedContext) {
1129                        // should not get results if not privileged - but extra check
1130                        lci = NativeUtil.byteArrayFromArrayList(resultForRequest.lci.data);
1131                        lcr = NativeUtil.byteArrayFromArrayList(resultForRequest.lcr.data);
1132                    }
1133                    if (resultForRequest.successNumber <= 1
1134                            && resultForRequest.distanceSdInMm != 0) {
1135                        if (mDbg) {
1136                            Log.w(TAG, "postProcessResults: non-zero distance stdev with 0||1 num "
1137                                    + "samples!? result=" + resultForRequest);
1138                        }
1139                        resultForRequest.distanceSdInMm = 0;
1140                    }
1141                    if (peer.peerHandle == null) {
1142                        finalResults.add(new RangingResult(status, peer.macAddress,
1143                                resultForRequest.distanceInMm, resultForRequest.distanceSdInMm,
1144                                resultForRequest.rssi / -2, resultForRequest.numberPerBurstPeer,
1145                                resultForRequest.successNumber, lci, lcr,
1146                                resultForRequest.timeStampInUs / CONVERSION_US_TO_MS));
1147                    } else {
1148                        finalResults.add(new RangingResult(status, peer.peerHandle,
1149                                resultForRequest.distanceInMm, resultForRequest.distanceSdInMm,
1150                                resultForRequest.rssi / -2, resultForRequest.numberPerBurstPeer,
1151                                resultForRequest.successNumber, lci, lcr,
1152                                resultForRequest.timeStampInUs / CONVERSION_US_TO_MS));
1153                    }
1154                }
1155            }
1156
1157            return finalResults;
1158        }
1159
1160        // dump call (asynchronous most likely)
1161        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1162            pw.println("  mNextCommandId: " + mNextCommandId);
1163            pw.println("  mRttRequesterInfo: " + mRttRequesterInfo);
1164            pw.println("  mRttRequestQueue: " + mRttRequestQueue);
1165            pw.println("  mRangingTimeoutMessage: " + mRangingTimeoutMessage);
1166            mRttMetrics.dump(fd, pw, args);
1167            mRttNative.dump(fd, pw, args);
1168        }
1169    }
1170
1171    private static class RttRequestInfo {
1172        public int uid;
1173        public WorkSource workSource;
1174        public IBinder binder;
1175        public IBinder.DeathRecipient dr;
1176        public String callingPackage;
1177        public RangingRequest request;
1178        public IRttCallback callback;
1179        public boolean isCalledFromPrivilegedContext;
1180
1181        public int cmdId = 0; // uninitialized cmdId value
1182        public boolean dispatchedToNative = false;
1183        public boolean peerHandlesTranslated = false;
1184
1185        @Override
1186        public String toString() {
1187            return new StringBuilder("RttRequestInfo: uid=").append(uid).append(
1188                    ", workSource=").append(workSource).append(", binder=").append(binder).append(
1189                    ", dr=").append(dr).append(", callingPackage=").append(callingPackage).append(
1190                    ", request=").append(request.toString()).append(", callback=").append(
1191                    callback).append(", cmdId=").append(cmdId).append(
1192                    ", peerHandlesTranslated=").append(peerHandlesTranslated).append(
1193                    ", isCalledFromPrivilegedContext=").append(
1194                    isCalledFromPrivilegedContext).toString();
1195        }
1196    }
1197
1198    private static class RttRequesterInfo {
1199        public long lastRangingExecuted;
1200
1201        @Override
1202        public String toString() {
1203            return new StringBuilder("RttRequesterInfo: lastRangingExecuted=").append(
1204                    lastRangingExecuted).toString();
1205        }
1206    }
1207}
1208