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.aware;
18
19import android.hardware.wifi.V1_0.NanCapabilities;
20import android.hardware.wifi.V1_0.NanClusterEventInd;
21import android.hardware.wifi.V1_0.NanClusterEventType;
22import android.hardware.wifi.V1_0.NanDataPathConfirmInd;
23import android.hardware.wifi.V1_0.NanDataPathRequestInd;
24import android.hardware.wifi.V1_0.NanFollowupReceivedInd;
25import android.hardware.wifi.V1_0.NanMatchInd;
26import android.hardware.wifi.V1_0.NanStatusType;
27import android.hardware.wifi.V1_0.WifiNanStatus;
28import android.hardware.wifi.V1_2.IWifiNanIfaceEventCallback;
29import android.hardware.wifi.V1_2.NanDataPathScheduleUpdateInd;
30import android.os.ShellCommand;
31import android.util.Log;
32import android.util.SparseIntArray;
33
34import libcore.util.HexEncoding;
35
36import org.json.JSONException;
37import org.json.JSONObject;
38
39import java.io.FileDescriptor;
40import java.io.PrintWriter;
41import java.util.ArrayList;
42import java.util.Arrays;
43
44/**
45 * Manages the callbacks from Wi-Fi Aware HIDL (HAL).
46 */
47public class WifiAwareNativeCallback extends IWifiNanIfaceEventCallback.Stub implements
48        WifiAwareShellCommand.DelegatedShellCommand {
49    private static final String TAG = "WifiAwareNativeCallback";
50    private static final boolean VDBG = false;
51    /* package */ boolean mDbg = false;
52
53    /* package */ boolean mIsHal12OrLater = false;
54
55    private final WifiAwareStateManager mWifiAwareStateManager;
56
57    public WifiAwareNativeCallback(WifiAwareStateManager wifiAwareStateManager) {
58        mWifiAwareStateManager = wifiAwareStateManager;
59    }
60
61    /*
62     * Counts of callbacks from HAL. Retrievable through shell command.
63     */
64    private static final int CB_EV_CLUSTER = 0;
65    private static final int CB_EV_DISABLED = 1;
66    private static final int CB_EV_PUBLISH_TERMINATED = 2;
67    private static final int CB_EV_SUBSCRIBE_TERMINATED = 3;
68    private static final int CB_EV_MATCH = 4;
69    private static final int CB_EV_MATCH_EXPIRED = 5;
70    private static final int CB_EV_FOLLOWUP_RECEIVED = 6;
71    private static final int CB_EV_TRANSMIT_FOLLOWUP = 7;
72    private static final int CB_EV_DATA_PATH_REQUEST = 8;
73    private static final int CB_EV_DATA_PATH_CONFIRM = 9;
74    private static final int CB_EV_DATA_PATH_TERMINATED = 10;
75    private static final int CB_EV_DATA_PATH_SCHED_UPDATE = 11;
76
77    private SparseIntArray mCallbackCounter = new SparseIntArray();
78
79    private void incrementCbCount(int callbackId) {
80        mCallbackCounter.put(callbackId, mCallbackCounter.get(callbackId) + 1);
81    }
82
83    /**
84     * Interpreter of adb shell command 'adb shell wifiaware native_cb ...'.
85     *
86     * @return -1 if parameter not recognized or invalid value, 0 otherwise.
87     */
88    @Override
89    public int onCommand(ShellCommand parentShell) {
90        final PrintWriter pwe = parentShell.getErrPrintWriter();
91        final PrintWriter pwo = parentShell.getOutPrintWriter();
92
93        String subCmd = parentShell.getNextArgRequired();
94        if (VDBG) Log.v(TAG, "onCommand: subCmd='" + subCmd + "'");
95        switch (subCmd) {
96            case "get_cb_count": {
97                String option = parentShell.getNextOption();
98                if (VDBG) Log.v(TAG, "option='" + option + "'");
99                boolean reset = false;
100                if (option != null) {
101                    if ("--reset".equals(option)) {
102                        reset = true;
103                    } else {
104                        pwe.println("Unknown option to 'get_cb_count'");
105                        return -1;
106                    }
107                }
108
109                JSONObject j = new JSONObject();
110                try {
111                    for (int i = 0; i < mCallbackCounter.size(); ++i) {
112                        j.put(Integer.toString(mCallbackCounter.keyAt(i)),
113                                mCallbackCounter.valueAt(i));
114                    }
115                } catch (JSONException e) {
116                    Log.e(TAG, "onCommand: get_cb_count e=" + e);
117                }
118                pwo.println(j.toString());
119                if (reset) {
120                    mCallbackCounter.clear();
121                }
122                return 0;
123            }
124            default:
125                pwe.println("Unknown 'wifiaware native_cb <cmd>'");
126        }
127
128        return -1;
129    }
130
131    @Override
132    public void onReset() {
133        // NOP (onReset is intended for configuration reset - not data reset)
134    }
135
136    @Override
137    public void onHelp(String command, ShellCommand parentShell) {
138        final PrintWriter pw = parentShell.getOutPrintWriter();
139
140        pw.println("  " + command);
141        pw.println("    get_cb_count [--reset]: gets the number of callbacks (and optionally reset "
142                + "count)");
143    }
144
145    @Override
146    public void notifyCapabilitiesResponse(short id, WifiNanStatus status,
147            NanCapabilities capabilities) {
148        if (mDbg) {
149            Log.v(TAG, "notifyCapabilitiesResponse: id=" + id + ", status=" + statusString(status)
150                    + ", capabilities=" + capabilities);
151        }
152
153        if (status.status == NanStatusType.SUCCESS) {
154            Capabilities frameworkCapabilities = new Capabilities();
155            frameworkCapabilities.maxConcurrentAwareClusters = capabilities.maxConcurrentClusters;
156            frameworkCapabilities.maxPublishes = capabilities.maxPublishes;
157            frameworkCapabilities.maxSubscribes = capabilities.maxSubscribes;
158            frameworkCapabilities.maxServiceNameLen = capabilities.maxServiceNameLen;
159            frameworkCapabilities.maxMatchFilterLen = capabilities.maxMatchFilterLen;
160            frameworkCapabilities.maxTotalMatchFilterLen = capabilities.maxTotalMatchFilterLen;
161            frameworkCapabilities.maxServiceSpecificInfoLen =
162                    capabilities.maxServiceSpecificInfoLen;
163            frameworkCapabilities.maxExtendedServiceSpecificInfoLen =
164                    capabilities.maxExtendedServiceSpecificInfoLen;
165            frameworkCapabilities.maxNdiInterfaces = capabilities.maxNdiInterfaces;
166            frameworkCapabilities.maxNdpSessions = capabilities.maxNdpSessions;
167            frameworkCapabilities.maxAppInfoLen = capabilities.maxAppInfoLen;
168            frameworkCapabilities.maxQueuedTransmitMessages =
169                    capabilities.maxQueuedTransmitFollowupMsgs;
170            frameworkCapabilities.maxSubscribeInterfaceAddresses =
171                    capabilities.maxSubscribeInterfaceAddresses;
172            frameworkCapabilities.supportedCipherSuites = capabilities.supportedCipherSuites;
173
174            mWifiAwareStateManager.onCapabilitiesUpdateResponse(id, frameworkCapabilities);
175        } else {
176            Log.e(TAG, "notifyCapabilitiesResponse: error code=" + status.status + " ("
177                    + status.description + ")");
178        }
179    }
180
181    @Override
182    public void notifyEnableResponse(short id, WifiNanStatus status) {
183        if (mDbg) Log.v(TAG, "notifyEnableResponse: id=" + id + ", status=" + statusString(status));
184
185        if (status.status == NanStatusType.ALREADY_ENABLED) {
186            Log.wtf(TAG, "notifyEnableResponse: id=" + id + ", already enabled!?");
187        }
188
189        if (status.status == NanStatusType.SUCCESS
190                || status.status == NanStatusType.ALREADY_ENABLED) {
191            mWifiAwareStateManager.onConfigSuccessResponse(id);
192        } else {
193            mWifiAwareStateManager.onConfigFailedResponse(id, status.status);
194        }
195    }
196
197    @Override
198    public void notifyConfigResponse(short id, WifiNanStatus status) {
199        if (mDbg) Log.v(TAG, "notifyConfigResponse: id=" + id + ", status=" + statusString(status));
200
201        if (status.status == NanStatusType.SUCCESS) {
202            mWifiAwareStateManager.onConfigSuccessResponse(id);
203        } else {
204            mWifiAwareStateManager.onConfigFailedResponse(id, status.status);
205        }
206    }
207
208    @Override
209    public void notifyDisableResponse(short id, WifiNanStatus status) {
210        if (mDbg) {
211            Log.v(TAG, "notifyDisableResponse: id=" + id + ", status=" + statusString(status));
212        }
213
214        if (status.status != NanStatusType.SUCCESS) {
215            Log.e(TAG, "notifyDisableResponse: failure - code=" + status.status + " ("
216                    + status.description + ")");
217        }
218        mWifiAwareStateManager.onDisableResponse(id, status.status);
219    }
220
221    @Override
222    public void notifyStartPublishResponse(short id, WifiNanStatus status, byte publishId) {
223        if (mDbg) {
224            Log.v(TAG, "notifyStartPublishResponse: id=" + id + ", status=" + statusString(status)
225                    + ", publishId=" + publishId);
226        }
227
228        if (status.status == NanStatusType.SUCCESS) {
229            mWifiAwareStateManager.onSessionConfigSuccessResponse(id, true, publishId);
230        } else {
231            mWifiAwareStateManager.onSessionConfigFailResponse(id, true, status.status);
232        }
233    }
234
235    @Override
236    public void notifyStopPublishResponse(short id, WifiNanStatus status) {
237        if (mDbg) {
238            Log.v(TAG, "notifyStopPublishResponse: id=" + id + ", status=" + statusString(status));
239        }
240
241        if (status.status == NanStatusType.SUCCESS) {
242            // NOP
243        } else {
244            Log.e(TAG, "notifyStopPublishResponse: failure - code=" + status.status + " ("
245                    + status.description + ")");
246        }
247    }
248
249    @Override
250    public void notifyStartSubscribeResponse(short id, WifiNanStatus status, byte subscribeId) {
251        if (mDbg) {
252            Log.v(TAG, "notifyStartSubscribeResponse: id=" + id + ", status=" + statusString(status)
253                    + ", subscribeId=" + subscribeId);
254        }
255
256        if (status.status == NanStatusType.SUCCESS) {
257            mWifiAwareStateManager.onSessionConfigSuccessResponse(id, false, subscribeId);
258        } else {
259            mWifiAwareStateManager.onSessionConfigFailResponse(id, false, status.status);
260        }
261    }
262
263    @Override
264    public void notifyStopSubscribeResponse(short id, WifiNanStatus status) {
265        if (mDbg) {
266            Log.v(TAG, "notifyStopSubscribeResponse: id=" + id + ", status="
267                    + statusString(status));
268        }
269
270        if (status.status == NanStatusType.SUCCESS) {
271            // NOP
272        } else {
273            Log.e(TAG, "notifyStopSubscribeResponse: failure - code=" + status.status + " ("
274                    + status.description + ")");
275        }
276    }
277
278    @Override
279    public void notifyTransmitFollowupResponse(short id, WifiNanStatus status) {
280        if (mDbg) {
281            Log.v(TAG, "notifyTransmitFollowupResponse: id=" + id + ", status="
282                    + statusString(status));
283        }
284
285        if (status.status == NanStatusType.SUCCESS) {
286            mWifiAwareStateManager.onMessageSendQueuedSuccessResponse(id);
287        } else {
288            mWifiAwareStateManager.onMessageSendQueuedFailResponse(id, status.status);
289        }
290    }
291
292    @Override
293    public void notifyCreateDataInterfaceResponse(short id, WifiNanStatus status) {
294        if (mDbg) {
295            Log.v(TAG, "notifyCreateDataInterfaceResponse: id=" + id + ", status="
296                    + statusString(status));
297        }
298
299        mWifiAwareStateManager.onCreateDataPathInterfaceResponse(id,
300                status.status == NanStatusType.SUCCESS, status.status);
301    }
302
303    @Override
304    public void notifyDeleteDataInterfaceResponse(short id, WifiNanStatus status) {
305        if (mDbg) {
306            Log.v(TAG, "notifyDeleteDataInterfaceResponse: id=" + id + ", status="
307                    + statusString(status));
308        }
309
310        mWifiAwareStateManager.onDeleteDataPathInterfaceResponse(id,
311                status.status == NanStatusType.SUCCESS, status.status);
312    }
313
314    @Override
315    public void notifyInitiateDataPathResponse(short id, WifiNanStatus status,
316            int ndpInstanceId) {
317        if (mDbg) {
318            Log.v(TAG, "notifyInitiateDataPathResponse: id=" + id + ", status="
319                    + statusString(status) + ", ndpInstanceId=" + ndpInstanceId);
320        }
321
322        if (status.status == NanStatusType.SUCCESS) {
323            mWifiAwareStateManager.onInitiateDataPathResponseSuccess(id, ndpInstanceId);
324        } else {
325            mWifiAwareStateManager.onInitiateDataPathResponseFail(id, status.status);
326        }
327    }
328
329    @Override
330    public void notifyRespondToDataPathIndicationResponse(short id, WifiNanStatus status) {
331        if (mDbg) {
332            Log.v(TAG, "notifyRespondToDataPathIndicationResponse: id=" + id
333                    + ", status=" + statusString(status));
334        }
335
336        mWifiAwareStateManager.onRespondToDataPathSetupRequestResponse(id,
337                status.status == NanStatusType.SUCCESS, status.status);
338    }
339
340    @Override
341    public void notifyTerminateDataPathResponse(short id, WifiNanStatus status) {
342        if (mDbg) {
343            Log.v(TAG, "notifyTerminateDataPathResponse: id=" + id + ", status="
344                    + statusString(status));
345        }
346
347        mWifiAwareStateManager.onEndDataPathResponse(id, status.status == NanStatusType.SUCCESS,
348                status.status);
349    }
350
351    @Override
352    public void eventClusterEvent(NanClusterEventInd event) {
353        if (mDbg) {
354            Log.v(TAG, "eventClusterEvent: eventType=" + event.eventType + ", addr="
355                    + String.valueOf(HexEncoding.encode(event.addr)));
356        }
357        incrementCbCount(CB_EV_CLUSTER);
358
359        if (event.eventType == NanClusterEventType.DISCOVERY_MAC_ADDRESS_CHANGED) {
360            mWifiAwareStateManager.onInterfaceAddressChangeNotification(event.addr);
361        } else if (event.eventType == NanClusterEventType.STARTED_CLUSTER) {
362            mWifiAwareStateManager.onClusterChangeNotification(
363                    WifiAwareClientState.CLUSTER_CHANGE_EVENT_STARTED, event.addr);
364        } else if (event.eventType == NanClusterEventType.JOINED_CLUSTER) {
365            mWifiAwareStateManager.onClusterChangeNotification(
366                    WifiAwareClientState.CLUSTER_CHANGE_EVENT_JOINED, event.addr);
367        } else {
368            Log.e(TAG, "eventClusterEvent: invalid eventType=" + event.eventType);
369        }
370    }
371
372    @Override
373    public void eventDisabled(WifiNanStatus status) {
374        if (mDbg) Log.v(TAG, "eventDisabled: status=" + statusString(status));
375        incrementCbCount(CB_EV_DISABLED);
376
377        mWifiAwareStateManager.onAwareDownNotification(status.status);
378    }
379
380    @Override
381    public void eventPublishTerminated(byte sessionId, WifiNanStatus status) {
382        if (mDbg) {
383            Log.v(TAG, "eventPublishTerminated: sessionId=" + sessionId + ", status="
384                    + statusString(status));
385        }
386        incrementCbCount(CB_EV_PUBLISH_TERMINATED);
387
388        mWifiAwareStateManager.onSessionTerminatedNotification(sessionId, status.status, true);
389    }
390
391    @Override
392    public void eventSubscribeTerminated(byte sessionId, WifiNanStatus status) {
393        if (mDbg) {
394            Log.v(TAG, "eventSubscribeTerminated: sessionId=" + sessionId + ", status="
395                    + statusString(status));
396        }
397        incrementCbCount(CB_EV_SUBSCRIBE_TERMINATED);
398
399        mWifiAwareStateManager.onSessionTerminatedNotification(sessionId, status.status, false);
400    }
401
402    @Override
403    public void eventMatch(NanMatchInd event) {
404        if (mDbg) {
405            Log.v(TAG, "eventMatch: discoverySessionId=" + event.discoverySessionId + ", peerId="
406                    + event.peerId + ", addr=" + String.valueOf(HexEncoding.encode(event.addr))
407                    + ", serviceSpecificInfo=" + Arrays.toString(
408                    convertArrayListToNativeByteArray(event.serviceSpecificInfo)) + ", ssi.size()="
409                    + (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size())
410                    + ", matchFilter=" + Arrays.toString(
411                    convertArrayListToNativeByteArray(event.matchFilter)) + ", mf.size()=" + (
412                    event.matchFilter == null ? 0 : event.matchFilter.size())
413                    + ", rangingIndicationType=" + event.rangingIndicationType
414                    + ", rangingMeasurementInCm=" + event.rangingMeasurementInCm);
415        }
416        incrementCbCount(CB_EV_MATCH);
417
418        // TODO: b/69428593 get rid of conversion once HAL moves from CM to MM
419        mWifiAwareStateManager.onMatchNotification(event.discoverySessionId, event.peerId,
420                event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo),
421                convertArrayListToNativeByteArray(event.matchFilter), event.rangingIndicationType,
422                event.rangingMeasurementInCm * 10);
423    }
424
425    @Override
426    public void eventMatchExpired(byte discoverySessionId, int peerId) {
427        if (mDbg) {
428            Log.v(TAG, "eventMatchExpired: discoverySessionId=" + discoverySessionId
429                    + ", peerId=" + peerId);
430        }
431        incrementCbCount(CB_EV_MATCH_EXPIRED);
432
433        // NOP
434    }
435
436    @Override
437    public void eventFollowupReceived(NanFollowupReceivedInd event) {
438        if (mDbg) {
439            Log.v(TAG, "eventFollowupReceived: discoverySessionId=" + event.discoverySessionId
440                    + ", peerId=" + event.peerId + ", addr=" + String.valueOf(
441                    HexEncoding.encode(event.addr)) + ", serviceSpecificInfo=" + Arrays.toString(
442                    convertArrayListToNativeByteArray(event.serviceSpecificInfo)) + ", ssi.size()="
443                    + (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size()));
444        }
445        incrementCbCount(CB_EV_FOLLOWUP_RECEIVED);
446
447        mWifiAwareStateManager.onMessageReceivedNotification(event.discoverySessionId, event.peerId,
448                event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo));
449    }
450
451    @Override
452    public void eventTransmitFollowup(short id, WifiNanStatus status) {
453        if (mDbg) {
454            Log.v(TAG, "eventTransmitFollowup: id=" + id + ", status=" + statusString(status));
455        }
456        incrementCbCount(CB_EV_TRANSMIT_FOLLOWUP);
457
458        if (status.status == NanStatusType.SUCCESS) {
459            mWifiAwareStateManager.onMessageSendSuccessNotification(id);
460        } else {
461            mWifiAwareStateManager.onMessageSendFailNotification(id, status.status);
462        }
463    }
464
465    @Override
466    public void eventDataPathRequest(NanDataPathRequestInd event) {
467        if (mDbg) {
468            Log.v(TAG, "eventDataPathRequest: discoverySessionId=" + event.discoverySessionId
469                    + ", peerDiscMacAddr=" + String.valueOf(
470                    HexEncoding.encode(event.peerDiscMacAddr)) + ", ndpInstanceId="
471                    + event.ndpInstanceId);
472        }
473        incrementCbCount(CB_EV_DATA_PATH_REQUEST);
474
475        mWifiAwareStateManager.onDataPathRequestNotification(event.discoverySessionId,
476                event.peerDiscMacAddr, event.ndpInstanceId);
477    }
478
479    @Override
480    public void eventDataPathConfirm(NanDataPathConfirmInd event) {
481        if (mDbg) {
482            Log.v(TAG, "onDataPathConfirm: ndpInstanceId=" + event.ndpInstanceId
483                    + ", peerNdiMacAddr=" + String.valueOf(HexEncoding.encode(event.peerNdiMacAddr))
484                    + ", dataPathSetupSuccess=" + event.dataPathSetupSuccess + ", reason="
485                    + event.status.status);
486        }
487        if (mIsHal12OrLater) {
488            Log.wtf(TAG, "eventDataPathConfirm should not be called by a >=1.2 HAL!");
489        }
490        incrementCbCount(CB_EV_DATA_PATH_CONFIRM);
491
492        mWifiAwareStateManager.onDataPathConfirmNotification(event.ndpInstanceId,
493                event.peerNdiMacAddr, event.dataPathSetupSuccess, event.status.status,
494                convertArrayListToNativeByteArray(event.appInfo), null);
495    }
496
497    @Override
498    public void eventDataPathConfirm_1_2(android.hardware.wifi.V1_2.NanDataPathConfirmInd event) {
499        if (mDbg) {
500            Log.v(TAG, "eventDataPathConfirm_1_2: ndpInstanceId=" + event.V1_0.ndpInstanceId
501                    + ", peerNdiMacAddr=" + String.valueOf(
502                    HexEncoding.encode(event.V1_0.peerNdiMacAddr)) + ", dataPathSetupSuccess="
503                    + event.V1_0.dataPathSetupSuccess + ", reason=" + event.V1_0.status.status);
504        }
505        if (!mIsHal12OrLater) {
506            Log.wtf(TAG, "eventDataPathConfirm_1_2 should not be called by a <1.2 HAL!");
507            return;
508        }
509        incrementCbCount(CB_EV_DATA_PATH_CONFIRM);
510
511        mWifiAwareStateManager.onDataPathConfirmNotification(event.V1_0.ndpInstanceId,
512                event.V1_0.peerNdiMacAddr, event.V1_0.dataPathSetupSuccess,
513                event.V1_0.status.status, convertArrayListToNativeByteArray(event.V1_0.appInfo),
514                event.channelInfo);
515    }
516
517    @Override
518    public void eventDataPathScheduleUpdate(NanDataPathScheduleUpdateInd event) {
519        if (mDbg) {
520            Log.v(TAG, "eventDataPathScheduleUpdate");
521        }
522        if (!mIsHal12OrLater) {
523            Log.wtf(TAG, "eventDataPathScheduleUpdate should not be called by a <1.2 HAL!");
524            return;
525        }
526        incrementCbCount(CB_EV_DATA_PATH_SCHED_UPDATE);
527
528        mWifiAwareStateManager.onDataPathScheduleUpdateNotification(event.peerDiscoveryAddress,
529                event.ndpInstanceIds, event.channelInfo);
530    }
531
532    @Override
533    public void eventDataPathTerminated(int ndpInstanceId) {
534        if (mDbg) Log.v(TAG, "eventDataPathTerminated: ndpInstanceId=" + ndpInstanceId);
535        incrementCbCount(CB_EV_DATA_PATH_TERMINATED);
536
537        mWifiAwareStateManager.onDataPathEndNotification(ndpInstanceId);
538    }
539
540    /**
541     * Dump the internal state of the class.
542     */
543    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
544        pw.println("WifiAwareNativeCallback:");
545        pw.println("  mCallbackCounter: " + mCallbackCounter);
546    }
547
548
549    // utilities
550
551    /**
552     * Converts an ArrayList<Byte> to a byte[].
553     *
554     * @param from The input ArrayList<Byte></Byte> to convert from.
555     *
556     * @return A newly allocated byte[].
557     */
558    private byte[] convertArrayListToNativeByteArray(ArrayList<Byte> from) {
559        if (from == null) {
560            return null;
561        }
562
563        byte[] to = new byte[from.size()];
564        for (int i = 0; i < from.size(); ++i) {
565            to[i] = from.get(i);
566        }
567        return to;
568    }
569
570    private static String statusString(WifiNanStatus status) {
571        if (status == null) {
572            return "status=null";
573        }
574        StringBuilder sb = new StringBuilder();
575        sb.append(status.status).append(" (").append(status.description).append(")");
576        return sb.toString();
577    }
578}
579