/*
 * 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.NanDataPathChannelInfo;
import android.hardware.wifi.V1_2.NanDataPathScheduleUpdateInd;
import android.net.MacAddress;
import android.net.wifi.util.HexEncoding;
import android.os.BasicShellCommandHandler;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;

import org.json.JSONArray;
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 boolean mDbg = false;

    /* package */ boolean mIsHal12OrLater = false;

    private final WifiAwareStateManager mWifiAwareStateManager;

    public WifiAwareNativeCallback(WifiAwareStateManager wifiAwareStateManager) {
        mWifiAwareStateManager = wifiAwareStateManager;
    }

    /**
     * Enable verbose logging.
     */
    public void enableVerboseLogging(boolean verbose) {
        mDbg = verbose;
    }


    /*
     * 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 SparseArray<ArrayList<NanDataPathChannelInfo>> mChannelInfoPerNdp = new SparseArray<>();

    private void incrementCbCount(int callbackId) {
        mCallbackCounter.put(callbackId, mCallbackCounter.get(callbackId) + 1);
    }

    /**
     * Interpreter of adb shell command 'adb shell cmd wifiaware native_cb ...'.
     *
     * @return -1 if parameter not recognized or invalid value, 0 otherwise.
     */
    @Override
    public int onCommand(BasicShellCommandHandler parentShell) {
        final PrintWriter pwe = parentShell.getErrPrintWriter();
        final PrintWriter pwo = parentShell.getOutPrintWriter();

        String subCmd = parentShell.getNextArgRequired();
        switch (subCmd) {
            case "get_cb_count": {
                String option = parentShell.getNextOption();
                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;
            }
            case  "get_channel_info": {
                String option = parentShell.getNextOption();
                if (option != null) {
                    pwe.println("Unknown option to 'get_channel_info'");
                    return -1;
                }
                String channelInfoString = convertChannelInfoToJsonString();
                pwo.println(channelInfoString);
                return 0;
            }
            default:
                pwe.println("Unknown 'wifiaware native_cb <cmd>'");
        }

        return -1;
    }

    @Override
    public void onReset() {
        // NOP (onReset is intended for configuration reset - not data reset)
    }

    @Override
    public void onHelp(String command, BasicShellCommandHandler parentShell) {
        final PrintWriter pw = parentShell.getOutPrintWriter();

        pw.println("  " + command);
        pw.println("    get_cb_count [--reset]: gets the number of callbacks (and optionally reset "
                + "count)");
        pw.println("    get_channel_info: prints out existing NDP channel info as a JSON String");
    }

    @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()
                    + ", channelInfo" + event.channelInfo);
        }
        if (!mIsHal12OrLater) {
            Log.wtf(TAG, "eventDataPathConfirm_1_2 should not be called by a <1.2 HAL!");
            return;
        }
        incrementCbCount(CB_EV_DATA_PATH_CONFIRM);
        mChannelInfoPerNdp.put(event.V1_0.ndpInstanceId, event.channelInfo);

        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: peerMac="
                    + MacAddress.fromBytes(event.peerDiscoveryAddress).toString()
                    + ", ndpIds=" + event.ndpInstanceIds + ", channelInfo=" + event.channelInfo);
        }
        if (!mIsHal12OrLater) {
            Log.wtf(TAG, "eventDataPathScheduleUpdate should not be called by a <1.2 HAL!");
            return;
        }
        incrementCbCount(CB_EV_DATA_PATH_SCHED_UPDATE);
        for (int ndpInstanceId : event.ndpInstanceIds) {
            mChannelInfoPerNdp.put(ndpInstanceId, event.channelInfo);
        }

        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);
        mChannelInfoPerNdp.remove(ndpInstanceId);

        mWifiAwareStateManager.onDataPathEndNotification(ndpInstanceId);
    }

    /**
     * Reset the channel info when Aware is down.
     */
    /* package */ void resetChannelInfo() {
        mChannelInfoPerNdp.clear();
    }

    /**
     * Dump the internal state of the class.
     */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("WifiAwareNativeCallback:");
        pw.println("  mCallbackCounter: " + mCallbackCounter);
        pw.println("  mChannelInfoPerNdp: " + mChannelInfoPerNdp);
    }


    // utilities

    /**
     * Converts an ArrayList<Byte> to a byte[].
     *
     * @param from The input ArrayList<Byte></Byte> to convert from.
     *
     * @return A newly allocated byte[].
     */
    private byte[] convertArrayListToNativeByteArray(ArrayList<Byte> 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();
    }

    /**
     * Transfer the channel Info dict into a Json String which can be decoded by Json reader.
     * The Format is: "{ndpInstanceId: [{"channelFreq": channelFreq,
     * "channelBandwidth": channelBandwidth, "numSpatialStreams": numSpatialStreams}]}"
     * @return Json String.
     */
    private String convertChannelInfoToJsonString() {
        JSONObject channelInfoJson = new JSONObject();
        try {
            for (int i = 0; i < mChannelInfoPerNdp.size(); i++) {
                JSONArray infoJsonArray = new JSONArray();
                for (NanDataPathChannelInfo info : mChannelInfoPerNdp.valueAt(i)) {
                    JSONObject j = new JSONObject();
                    j.put("channelFreq", info.channelFreq);
                    j.put("channelBandwidth", info.channelBandwidth);
                    j.put("numSpatialStreams", info.numSpatialStreams);
                    infoJsonArray.put(j);
                }
                channelInfoJson.put(Integer.toString(mChannelInfoPerNdp.keyAt(i)), infoJsonArray);
            }
        } catch (JSONException e) {
            Log.e(TAG, "onCommand: get_channel_info e=" + e);
        }
        return channelInfoJson.toString();
    }
}
