/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi.aware; import android.hardware.wifi.V1_0.NanCapabilities; import android.hardware.wifi.V1_0.NanClusterEventInd; import android.hardware.wifi.V1_0.NanClusterEventType; import android.hardware.wifi.V1_0.NanDataPathConfirmInd; import android.hardware.wifi.V1_0.NanDataPathRequestInd; import android.hardware.wifi.V1_0.NanFollowupReceivedInd; import android.hardware.wifi.V1_0.NanMatchInd; import android.hardware.wifi.V1_0.NanStatusType; import android.hardware.wifi.V1_0.WifiNanStatus; import android.hardware.wifi.V1_2.IWifiNanIfaceEventCallback; import android.hardware.wifi.V1_2.NanDataPathScheduleUpdateInd; import android.os.ShellCommand; import android.util.Log; import android.util.SparseIntArray; import libcore.util.HexEncoding; import org.json.JSONException; import org.json.JSONObject; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; /** * Manages the callbacks from Wi-Fi Aware HIDL (HAL). */ public class WifiAwareNativeCallback extends IWifiNanIfaceEventCallback.Stub implements WifiAwareShellCommand.DelegatedShellCommand { private static final String TAG = "WifiAwareNativeCallback"; private static final boolean VDBG = false; /* package */ boolean mDbg = false; /* package */ boolean mIsHal12OrLater = false; private final WifiAwareStateManager mWifiAwareStateManager; public WifiAwareNativeCallback(WifiAwareStateManager wifiAwareStateManager) { mWifiAwareStateManager = wifiAwareStateManager; } /* * Counts of callbacks from HAL. Retrievable through shell command. */ private static final int CB_EV_CLUSTER = 0; private static final int CB_EV_DISABLED = 1; private static final int CB_EV_PUBLISH_TERMINATED = 2; private static final int CB_EV_SUBSCRIBE_TERMINATED = 3; private static final int CB_EV_MATCH = 4; private static final int CB_EV_MATCH_EXPIRED = 5; private static final int CB_EV_FOLLOWUP_RECEIVED = 6; private static final int CB_EV_TRANSMIT_FOLLOWUP = 7; private static final int CB_EV_DATA_PATH_REQUEST = 8; private static final int CB_EV_DATA_PATH_CONFIRM = 9; private static final int CB_EV_DATA_PATH_TERMINATED = 10; private static final int CB_EV_DATA_PATH_SCHED_UPDATE = 11; private SparseIntArray mCallbackCounter = new SparseIntArray(); private void incrementCbCount(int callbackId) { mCallbackCounter.put(callbackId, mCallbackCounter.get(callbackId) + 1); } /** * Interpreter of adb shell command 'adb shell wifiaware native_cb ...'. * * @return -1 if parameter not recognized or invalid value, 0 otherwise. */ @Override public int onCommand(ShellCommand parentShell) { final PrintWriter pwe = parentShell.getErrPrintWriter(); final PrintWriter pwo = parentShell.getOutPrintWriter(); String subCmd = parentShell.getNextArgRequired(); if (VDBG) Log.v(TAG, "onCommand: subCmd='" + subCmd + "'"); switch (subCmd) { case "get_cb_count": { String option = parentShell.getNextOption(); if (VDBG) Log.v(TAG, "option='" + option + "'"); boolean reset = false; if (option != null) { if ("--reset".equals(option)) { reset = true; } else { pwe.println("Unknown option to 'get_cb_count'"); return -1; } } JSONObject j = new JSONObject(); try { for (int i = 0; i < mCallbackCounter.size(); ++i) { j.put(Integer.toString(mCallbackCounter.keyAt(i)), mCallbackCounter.valueAt(i)); } } catch (JSONException e) { Log.e(TAG, "onCommand: get_cb_count e=" + e); } pwo.println(j.toString()); if (reset) { mCallbackCounter.clear(); } return 0; } default: pwe.println("Unknown 'wifiaware native_cb '"); } return -1; } @Override public void onReset() { // NOP (onReset is intended for configuration reset - not data reset) } @Override public void onHelp(String command, ShellCommand parentShell) { final PrintWriter pw = parentShell.getOutPrintWriter(); pw.println(" " + command); pw.println(" get_cb_count [--reset]: gets the number of callbacks (and optionally reset " + "count)"); } @Override public void notifyCapabilitiesResponse(short id, WifiNanStatus status, NanCapabilities capabilities) { if (mDbg) { Log.v(TAG, "notifyCapabilitiesResponse: id=" + id + ", status=" + statusString(status) + ", capabilities=" + capabilities); } if (status.status == NanStatusType.SUCCESS) { Capabilities frameworkCapabilities = new Capabilities(); frameworkCapabilities.maxConcurrentAwareClusters = capabilities.maxConcurrentClusters; frameworkCapabilities.maxPublishes = capabilities.maxPublishes; frameworkCapabilities.maxSubscribes = capabilities.maxSubscribes; frameworkCapabilities.maxServiceNameLen = capabilities.maxServiceNameLen; frameworkCapabilities.maxMatchFilterLen = capabilities.maxMatchFilterLen; frameworkCapabilities.maxTotalMatchFilterLen = capabilities.maxTotalMatchFilterLen; frameworkCapabilities.maxServiceSpecificInfoLen = capabilities.maxServiceSpecificInfoLen; frameworkCapabilities.maxExtendedServiceSpecificInfoLen = capabilities.maxExtendedServiceSpecificInfoLen; frameworkCapabilities.maxNdiInterfaces = capabilities.maxNdiInterfaces; frameworkCapabilities.maxNdpSessions = capabilities.maxNdpSessions; frameworkCapabilities.maxAppInfoLen = capabilities.maxAppInfoLen; frameworkCapabilities.maxQueuedTransmitMessages = capabilities.maxQueuedTransmitFollowupMsgs; frameworkCapabilities.maxSubscribeInterfaceAddresses = capabilities.maxSubscribeInterfaceAddresses; frameworkCapabilities.supportedCipherSuites = capabilities.supportedCipherSuites; mWifiAwareStateManager.onCapabilitiesUpdateResponse(id, frameworkCapabilities); } else { Log.e(TAG, "notifyCapabilitiesResponse: error code=" + status.status + " (" + status.description + ")"); } } @Override public void notifyEnableResponse(short id, WifiNanStatus status) { if (mDbg) Log.v(TAG, "notifyEnableResponse: id=" + id + ", status=" + statusString(status)); if (status.status == NanStatusType.ALREADY_ENABLED) { Log.wtf(TAG, "notifyEnableResponse: id=" + id + ", already enabled!?"); } if (status.status == NanStatusType.SUCCESS || status.status == NanStatusType.ALREADY_ENABLED) { mWifiAwareStateManager.onConfigSuccessResponse(id); } else { mWifiAwareStateManager.onConfigFailedResponse(id, status.status); } } @Override public void notifyConfigResponse(short id, WifiNanStatus status) { if (mDbg) Log.v(TAG, "notifyConfigResponse: id=" + id + ", status=" + statusString(status)); if (status.status == NanStatusType.SUCCESS) { mWifiAwareStateManager.onConfigSuccessResponse(id); } else { mWifiAwareStateManager.onConfigFailedResponse(id, status.status); } } @Override public void notifyDisableResponse(short id, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "notifyDisableResponse: id=" + id + ", status=" + statusString(status)); } if (status.status != NanStatusType.SUCCESS) { Log.e(TAG, "notifyDisableResponse: failure - code=" + status.status + " (" + status.description + ")"); } mWifiAwareStateManager.onDisableResponse(id, status.status); } @Override public void notifyStartPublishResponse(short id, WifiNanStatus status, byte publishId) { if (mDbg) { Log.v(TAG, "notifyStartPublishResponse: id=" + id + ", status=" + statusString(status) + ", publishId=" + publishId); } if (status.status == NanStatusType.SUCCESS) { mWifiAwareStateManager.onSessionConfigSuccessResponse(id, true, publishId); } else { mWifiAwareStateManager.onSessionConfigFailResponse(id, true, status.status); } } @Override public void notifyStopPublishResponse(short id, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "notifyStopPublishResponse: id=" + id + ", status=" + statusString(status)); } if (status.status == NanStatusType.SUCCESS) { // NOP } else { Log.e(TAG, "notifyStopPublishResponse: failure - code=" + status.status + " (" + status.description + ")"); } } @Override public void notifyStartSubscribeResponse(short id, WifiNanStatus status, byte subscribeId) { if (mDbg) { Log.v(TAG, "notifyStartSubscribeResponse: id=" + id + ", status=" + statusString(status) + ", subscribeId=" + subscribeId); } if (status.status == NanStatusType.SUCCESS) { mWifiAwareStateManager.onSessionConfigSuccessResponse(id, false, subscribeId); } else { mWifiAwareStateManager.onSessionConfigFailResponse(id, false, status.status); } } @Override public void notifyStopSubscribeResponse(short id, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "notifyStopSubscribeResponse: id=" + id + ", status=" + statusString(status)); } if (status.status == NanStatusType.SUCCESS) { // NOP } else { Log.e(TAG, "notifyStopSubscribeResponse: failure - code=" + status.status + " (" + status.description + ")"); } } @Override public void notifyTransmitFollowupResponse(short id, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "notifyTransmitFollowupResponse: id=" + id + ", status=" + statusString(status)); } if (status.status == NanStatusType.SUCCESS) { mWifiAwareStateManager.onMessageSendQueuedSuccessResponse(id); } else { mWifiAwareStateManager.onMessageSendQueuedFailResponse(id, status.status); } } @Override public void notifyCreateDataInterfaceResponse(short id, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "notifyCreateDataInterfaceResponse: id=" + id + ", status=" + statusString(status)); } mWifiAwareStateManager.onCreateDataPathInterfaceResponse(id, status.status == NanStatusType.SUCCESS, status.status); } @Override public void notifyDeleteDataInterfaceResponse(short id, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "notifyDeleteDataInterfaceResponse: id=" + id + ", status=" + statusString(status)); } mWifiAwareStateManager.onDeleteDataPathInterfaceResponse(id, status.status == NanStatusType.SUCCESS, status.status); } @Override public void notifyInitiateDataPathResponse(short id, WifiNanStatus status, int ndpInstanceId) { if (mDbg) { Log.v(TAG, "notifyInitiateDataPathResponse: id=" + id + ", status=" + statusString(status) + ", ndpInstanceId=" + ndpInstanceId); } if (status.status == NanStatusType.SUCCESS) { mWifiAwareStateManager.onInitiateDataPathResponseSuccess(id, ndpInstanceId); } else { mWifiAwareStateManager.onInitiateDataPathResponseFail(id, status.status); } } @Override public void notifyRespondToDataPathIndicationResponse(short id, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "notifyRespondToDataPathIndicationResponse: id=" + id + ", status=" + statusString(status)); } mWifiAwareStateManager.onRespondToDataPathSetupRequestResponse(id, status.status == NanStatusType.SUCCESS, status.status); } @Override public void notifyTerminateDataPathResponse(short id, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "notifyTerminateDataPathResponse: id=" + id + ", status=" + statusString(status)); } mWifiAwareStateManager.onEndDataPathResponse(id, status.status == NanStatusType.SUCCESS, status.status); } @Override public void eventClusterEvent(NanClusterEventInd event) { if (mDbg) { Log.v(TAG, "eventClusterEvent: eventType=" + event.eventType + ", addr=" + String.valueOf(HexEncoding.encode(event.addr))); } incrementCbCount(CB_EV_CLUSTER); if (event.eventType == NanClusterEventType.DISCOVERY_MAC_ADDRESS_CHANGED) { mWifiAwareStateManager.onInterfaceAddressChangeNotification(event.addr); } else if (event.eventType == NanClusterEventType.STARTED_CLUSTER) { mWifiAwareStateManager.onClusterChangeNotification( WifiAwareClientState.CLUSTER_CHANGE_EVENT_STARTED, event.addr); } else if (event.eventType == NanClusterEventType.JOINED_CLUSTER) { mWifiAwareStateManager.onClusterChangeNotification( WifiAwareClientState.CLUSTER_CHANGE_EVENT_JOINED, event.addr); } else { Log.e(TAG, "eventClusterEvent: invalid eventType=" + event.eventType); } } @Override public void eventDisabled(WifiNanStatus status) { if (mDbg) Log.v(TAG, "eventDisabled: status=" + statusString(status)); incrementCbCount(CB_EV_DISABLED); mWifiAwareStateManager.onAwareDownNotification(status.status); } @Override public void eventPublishTerminated(byte sessionId, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "eventPublishTerminated: sessionId=" + sessionId + ", status=" + statusString(status)); } incrementCbCount(CB_EV_PUBLISH_TERMINATED); mWifiAwareStateManager.onSessionTerminatedNotification(sessionId, status.status, true); } @Override public void eventSubscribeTerminated(byte sessionId, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "eventSubscribeTerminated: sessionId=" + sessionId + ", status=" + statusString(status)); } incrementCbCount(CB_EV_SUBSCRIBE_TERMINATED); mWifiAwareStateManager.onSessionTerminatedNotification(sessionId, status.status, false); } @Override public void eventMatch(NanMatchInd event) { if (mDbg) { Log.v(TAG, "eventMatch: discoverySessionId=" + event.discoverySessionId + ", peerId=" + event.peerId + ", addr=" + String.valueOf(HexEncoding.encode(event.addr)) + ", serviceSpecificInfo=" + Arrays.toString( convertArrayListToNativeByteArray(event.serviceSpecificInfo)) + ", ssi.size()=" + (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size()) + ", matchFilter=" + Arrays.toString( convertArrayListToNativeByteArray(event.matchFilter)) + ", mf.size()=" + ( event.matchFilter == null ? 0 : event.matchFilter.size()) + ", rangingIndicationType=" + event.rangingIndicationType + ", rangingMeasurementInCm=" + event.rangingMeasurementInCm); } incrementCbCount(CB_EV_MATCH); // TODO: b/69428593 get rid of conversion once HAL moves from CM to MM mWifiAwareStateManager.onMatchNotification(event.discoverySessionId, event.peerId, event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo), convertArrayListToNativeByteArray(event.matchFilter), event.rangingIndicationType, event.rangingMeasurementInCm * 10); } @Override public void eventMatchExpired(byte discoverySessionId, int peerId) { if (mDbg) { Log.v(TAG, "eventMatchExpired: discoverySessionId=" + discoverySessionId + ", peerId=" + peerId); } incrementCbCount(CB_EV_MATCH_EXPIRED); // NOP } @Override public void eventFollowupReceived(NanFollowupReceivedInd event) { if (mDbg) { Log.v(TAG, "eventFollowupReceived: discoverySessionId=" + event.discoverySessionId + ", peerId=" + event.peerId + ", addr=" + String.valueOf( HexEncoding.encode(event.addr)) + ", serviceSpecificInfo=" + Arrays.toString( convertArrayListToNativeByteArray(event.serviceSpecificInfo)) + ", ssi.size()=" + (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size())); } incrementCbCount(CB_EV_FOLLOWUP_RECEIVED); mWifiAwareStateManager.onMessageReceivedNotification(event.discoverySessionId, event.peerId, event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo)); } @Override public void eventTransmitFollowup(short id, WifiNanStatus status) { if (mDbg) { Log.v(TAG, "eventTransmitFollowup: id=" + id + ", status=" + statusString(status)); } incrementCbCount(CB_EV_TRANSMIT_FOLLOWUP); if (status.status == NanStatusType.SUCCESS) { mWifiAwareStateManager.onMessageSendSuccessNotification(id); } else { mWifiAwareStateManager.onMessageSendFailNotification(id, status.status); } } @Override public void eventDataPathRequest(NanDataPathRequestInd event) { if (mDbg) { Log.v(TAG, "eventDataPathRequest: discoverySessionId=" + event.discoverySessionId + ", peerDiscMacAddr=" + String.valueOf( HexEncoding.encode(event.peerDiscMacAddr)) + ", ndpInstanceId=" + event.ndpInstanceId + ", appInfo.size()=" + event.appInfo.size()); } incrementCbCount(CB_EV_DATA_PATH_REQUEST); mWifiAwareStateManager.onDataPathRequestNotification(event.discoverySessionId, event.peerDiscMacAddr, event.ndpInstanceId, convertArrayListToNativeByteArray(event.appInfo)); } @Override public void eventDataPathConfirm(NanDataPathConfirmInd event) { if (mDbg) { Log.v(TAG, "onDataPathConfirm: ndpInstanceId=" + event.ndpInstanceId + ", peerNdiMacAddr=" + String.valueOf(HexEncoding.encode(event.peerNdiMacAddr)) + ", dataPathSetupSuccess=" + event.dataPathSetupSuccess + ", reason=" + event.status.status + ", appInfo.size()=" + event.appInfo.size()); } if (mIsHal12OrLater) { Log.wtf(TAG, "eventDataPathConfirm should not be called by a >=1.2 HAL!"); } incrementCbCount(CB_EV_DATA_PATH_CONFIRM); mWifiAwareStateManager.onDataPathConfirmNotification(event.ndpInstanceId, event.peerNdiMacAddr, event.dataPathSetupSuccess, event.status.status, convertArrayListToNativeByteArray(event.appInfo), null); } @Override public void eventDataPathConfirm_1_2(android.hardware.wifi.V1_2.NanDataPathConfirmInd event) { if (mDbg) { Log.v(TAG, "eventDataPathConfirm_1_2: ndpInstanceId=" + event.V1_0.ndpInstanceId + ", peerNdiMacAddr=" + String.valueOf( HexEncoding.encode(event.V1_0.peerNdiMacAddr)) + ", dataPathSetupSuccess=" + event.V1_0.dataPathSetupSuccess + ", reason=" + event.V1_0.status.status + ", appInfo.size()=" + event.V1_0.appInfo.size()); } if (!mIsHal12OrLater) { Log.wtf(TAG, "eventDataPathConfirm_1_2 should not be called by a <1.2 HAL!"); return; } incrementCbCount(CB_EV_DATA_PATH_CONFIRM); mWifiAwareStateManager.onDataPathConfirmNotification(event.V1_0.ndpInstanceId, event.V1_0.peerNdiMacAddr, event.V1_0.dataPathSetupSuccess, event.V1_0.status.status, convertArrayListToNativeByteArray(event.V1_0.appInfo), event.channelInfo); } @Override public void eventDataPathScheduleUpdate(NanDataPathScheduleUpdateInd event) { if (mDbg) { Log.v(TAG, "eventDataPathScheduleUpdate"); } if (!mIsHal12OrLater) { Log.wtf(TAG, "eventDataPathScheduleUpdate should not be called by a <1.2 HAL!"); return; } incrementCbCount(CB_EV_DATA_PATH_SCHED_UPDATE); mWifiAwareStateManager.onDataPathScheduleUpdateNotification(event.peerDiscoveryAddress, event.ndpInstanceIds, event.channelInfo); } @Override public void eventDataPathTerminated(int ndpInstanceId) { if (mDbg) Log.v(TAG, "eventDataPathTerminated: ndpInstanceId=" + ndpInstanceId); incrementCbCount(CB_EV_DATA_PATH_TERMINATED); mWifiAwareStateManager.onDataPathEndNotification(ndpInstanceId); } /** * Dump the internal state of the class. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("WifiAwareNativeCallback:"); pw.println(" mCallbackCounter: " + mCallbackCounter); } // utilities /** * Converts an ArrayList to a byte[]. * * @param from The input ArrayList to convert from. * * @return A newly allocated byte[]. */ private byte[] convertArrayListToNativeByteArray(ArrayList from) { if (from == null) { return null; } byte[] to = new byte[from.size()]; for (int i = 0; i < from.size(); ++i) { to[i] = from.get(i); } return to; } private static String statusString(WifiNanStatus status) { if (status == null) { return "status=null"; } StringBuilder sb = new StringBuilder(); sb.append(status.status).append(" (").append(status.description).append(")"); return sb.toString(); } }