M7350/base/core/java/android/net/QoSTracker.java

499 lines
18 KiB
Java
Raw Normal View History

2024-09-09 08:52:07 +00:00
/*
* Copyright (C) 2011, The Linux Foundation. All rights reserved.
*
* 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 android.net;
import com.android.internal.net.IPVersion;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.QosSpec;
import android.net.ILinkSocketMessageHandler;
import android.net.LinkCapabilities;
import android.net.ExtraLinkCapabilities;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
/** @hide */
public class QoSTracker {
private static final String LOG_TAG = "QoSTracker";
private static final String LOCAL_TAG = "QoSTracker_DEBUG";
private final boolean DBG = true;
private int mId;
private int mQosId;
private ExtraLinkCapabilities myCap = null;
private QosSpec mQosSpec;
private ILinkSocketMessageHandler mNotifier;
private boolean mSetupRequested;
private boolean mTeardownRequested;
private int mDetailedState; //Detail QoS State obtained from lower layers
private String mState; //QoS State notified to the APP.
private String lastState; //last state notified to the app.
private boolean notifyQosToSocket; //flag to track if the socket needs to be notified
private boolean isWaitingForSpecUpdate; //flag to track qosspec request/response
private final String QOS_STATE_FAILED = "failed";
private final String QOS_STATE_ACTIVE = "active";
private final String QOS_STATE_INACTIVE = "inactive";
private final String QOS_STATE_SUSPENDED = "suspended";
private final int[] capRoKeys = new int[] {
LinkCapabilities.Key.RO_MIN_AVAILABLE_FWD_BW,
LinkCapabilities.Key.RO_MAX_AVAILABLE_FWD_BW,
LinkCapabilities.Key.RO_MIN_AVAILABLE_REV_BW,
LinkCapabilities.Key.RO_MAX_AVAILABLE_REV_BW,
LinkCapabilities.Key.RO_CURRENT_FWD_LATENCY,
LinkCapabilities.Key.RO_CURRENT_REV_LATENCY,
LinkCapabilities.Key.RO_BOUND_INTERFACE,
LinkCapabilities.Key.RO_NETWORK_TYPE,
LinkCapabilities.Key.RO_PHYSICAL_INTERFACE,
LinkCapabilities.Key.RO_CARRIER_ROLE,
LinkCapabilities.Key.RO_QOS_STATE
};
//Constructor
public QoSTracker (int id, ILinkSocketMessageHandler lsmh, ExtraLinkCapabilities eCap) {
dlogd("socket id: " + id + " QoSTracker EX");
mId = id;
mNotifier = lsmh;
myCap = eCap;
mQosId = -1;
mDetailedState = -1;
mState = QOS_STATE_INACTIVE;
lastState = QOS_STATE_INACTIVE;
mSetupRequested = false;
mTeardownRequested = false;
myCap.put(LinkCapabilities.Key.RO_QOS_STATE, mState);
isWaitingForSpecUpdate = false;
}
public int getSocketId() {
return mId;
}
public int getQosId() {
return mQosId;
}
public LinkCapabilities getQosCapabilities () {
return myCap;
}
public void startQosTransaction(QosSpec spec) {
if (spec == null) return;
mQosSpec = spec;
mQosSpec.setUserData(mId);
dlogi("startQosTransaction got called for socket: " + mId);
if (!mSetupRequested) {
//TODO Have CNE/App decide on the APN type for QoS.
//FIXME Hardcoding APN type for now.
if (enableQoS(mQosSpec, Phone.APN_TYPE_DEFAULT)) {
mSetupRequested = true;
} else {
//TODO implement error handlers for calls to telephony
mSetupRequested = false;
}
} else {
//TODO do modify qos
}
}
public void stopQosTransaction() {
dlogd("stopQosTransaction got called for sid: " + mId);
if (!mTeardownRequested) {
disableQos(mQosId);
mTeardownRequested = true;
}
}
public void handleQosEvent(int qosId, int qosIndState, int qosState, QosSpec spec) {
mQosId = qosId;
if (myCap == null) {
dlogw("handleQosEvent failed due to null capabilities... aborting");
return;
}
if (qosState == -1) { // event is an unsolicited indication
handleQosIndEvent(qosIndState);
//do not go further if a spec update is needed.
if (isWaitingForSpecUpdate) return;
} else { //event is a response to getQosStatus
if (spec == null) {
//remove current flow spec entries from the capabilities if spec is null
myCap.remove(LinkCapabilities.Key.RO_MIN_AVAILABLE_REV_BW);
myCap.remove(LinkCapabilities.Key.RO_MAX_AVAILABLE_REV_BW);
myCap.remove(LinkCapabilities.Key.RO_MIN_AVAILABLE_FWD_BW);
myCap.remove(LinkCapabilities.Key.RO_MAX_AVAILABLE_FWD_BW);
myCap.remove(LinkCapabilities.Key.RO_CURRENT_REV_LATENCY);
myCap.remove(LinkCapabilities.Key.RO_CURRENT_FWD_LATENCY);
} else {
updateCapabilitiesFromSpec(spec);
}
isWaitingForSpecUpdate = false; //received spec update so reset the flag
}
if (notifyQosToSocket) {
ExtraLinkCapabilities sendCap = new ExtraLinkCapabilities();
for (int roKey : capRoKeys) {
if (myCap.containsKey(roKey))
sendCap.put(roKey, myCap.get(roKey));
}
try {
dlogi("notifying socket of updated capabilities: " + sendCap);
mNotifier.onCapabilitiesChanged(sendCap);
notifyQosToSocket = false;
} catch (RemoteException re) {
dlogd(" oncapabilitieschanged failed for sid: "
+ mId + " with exception: " + re);
} catch (NullPointerException npe) {
dlogd(" onCapabilitiesChgd got null notifier " + npe);
}
}
}
private void handleQosIndEvent(int qosIndState) {
mDetailedState = qosIndState;
/*
* Convert detailed state to coarse state that will be conveyed to
* the socket per the following table
* ==================================================
* Detailed State | State
* --------------------------------------------------
* REQUEST_FAILED | QOS_STATE_FAILED
* --------------------------------------------------
* INITIATED, RELEASED, |
* RELEASED_NETWORK, | QOS_STATE_INACTIVE
* NONE |
* -------------------------|------------------------
* ACTIVATED, |
* MODIFYING, MODIFIED, |
* MODIFIED_NETWORK, | QOS_STATE_ACTIVE
* RELEASING, |
* RESUMED_NETWORK, |
* SUSPENDING |
* -------------------------|------------------------
* SUSPENDED, | QOS_STATE_SUSPENDED
* --------------------------------------------------
*/
switch (mDetailedState) {
case QosSpec.QosIndStates.REQUEST_FAILED:
mSetupRequested = false;
notifyQosToSocket = true;
mState = QOS_STATE_FAILED;
break;
case QosSpec.QosIndStates.RELEASED_NETWORK:
case QosSpec.QosIndStates.RELEASED:
case QosSpec.QosIndStates.NONE:
mSetupRequested = false;
mState = QOS_STATE_INACTIVE;
//sometimes we need to update even if the coarse QOS_STATE does not change
notifyQosToSocket = true;
break;
case QosSpec.QosIndStates.INITIATED:
mSetupRequested = true;
mState = QOS_STATE_INACTIVE;
break;
case QosSpec.QosIndStates.ACTIVATED:
case QosSpec.QosIndStates.MODIFIED:
case QosSpec.QosIndStates.MODIFIED_NETWORK:
case QosSpec.QosIndStates.RESUMED_NETWORK:
//sometimes we need to update even if the coarse QOS_STATE does not change
notifyQosToSocket = true;
case QosSpec.QosIndStates.MODIFYING:
case QosSpec.QosIndStates.SUSPENDING:
case QosSpec.QosIndStates.RELEASING:
mState = QOS_STATE_ACTIVE;
break;
case QosSpec.QosIndStates.SUSPENDED:
mState = QOS_STATE_SUSPENDED;
break;
default:
dlogd("CnE got invalid qos indication: " + mDetailedState);
}
myCap.put(LinkCapabilities.Key.RO_QOS_STATE, mState);
//FIXME Querying for a spec for every indication for now.
//TODO find out for which indications the spec is expected to change
//and query the spec for those indications only.
isWaitingForSpecUpdate = getQos(mQosId);
if (!mState.equals(lastState)) {
notifyQosToSocket = true;
lastState = mState;
}
}
private void updateCapabilitiesFromSpec (QosSpec spec) {
//Only extract flow information for bandiwdth and latency
//FIXME hardcoding to two flows per spec, viz one fwd and reverse.
//TODO extract flows as per qos role definition in the config file
if (spec == null) return;
dlogi("updateCapabilities got spec: " + spec);
String temp = null;
QosSpec.QosPipe txPipe = null;
QosSpec.QosPipe rxPipe = null;
for (QosSpec.QosPipe p : spec.getQosPipes()) {
if (p.get(QosSpec.QosSpecKey.FLOW_DIRECTION).equals(
Integer.toString(QosSpec.QosDirection.QOS_TX))) txPipe = p;
if (p.get(QosSpec.QosSpecKey.FLOW_DIRECTION).equals(
Integer.toString(QosSpec.QosDirection.QOS_TX))) rxPipe = p;
}
if (txPipe == null && rxPipe == null) {
dlogw("updateCapabilities expected tx and rx pipes but did not find them");
return;
}
if ((temp = txPipe.get(QosSpec.QosSpecKey.FLOW_DATA_RATE_MIN)) != null) {
myCap.put(LinkCapabilities.Key.RO_MIN_AVAILABLE_REV_BW, temp);
}
if ((temp = txPipe.get(QosSpec.QosSpecKey.FLOW_DATA_RATE_MAX)) != null) {
myCap.put(LinkCapabilities.Key.RO_MAX_AVAILABLE_REV_BW, temp);
}
if ((temp = rxPipe.get(QosSpec.QosSpecKey.FLOW_DATA_RATE_MIN)) != null) {
myCap.put(LinkCapabilities.Key.RO_MIN_AVAILABLE_FWD_BW, temp);
}
if ((temp = rxPipe.get(QosSpec.QosSpecKey.FLOW_DATA_RATE_MAX)) != null) {
myCap.put(LinkCapabilities.Key.RO_MAX_AVAILABLE_FWD_BW, temp);
}
if ((temp = txPipe.get(QosSpec.QosSpecKey.FLOW_LATENCY)) != null) {
myCap.put(LinkCapabilities.Key.RO_CURRENT_REV_LATENCY, temp);
}
if ((temp = rxPipe.get(QosSpec.QosSpecKey.FLOW_LATENCY)) != null) {
myCap.put(LinkCapabilities.Key.RO_CURRENT_FWD_LATENCY, temp);
}
dlogi("updated capabilities to: " + myCap);
}
//update Active Capabilities
//TODO Move this out of here and do this when requestLink is received
private boolean enableQoS (QosSpec spec, String apnType) {
boolean res = false;
if (spec.getUserData() < 1 || apnType == null) return res;
if (spec == null) {
dlogw( "qos spec is null");
return res;
}
dlogi("requesting qos with spec: " + spec.toString()
+ " for txId: " + spec.getUserData()
+ " on apn: " + apnType);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
// Currently only IPv4 is supported
res = (mPhone.enableQos(spec, apnType, IPVersion.INET.toString()) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
return res;
}
private boolean disableQos (int qosId) {
boolean res = false;
dlogi( "disabling qos for id: " + qosId);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
res = (mPhone.disableQos(qosId) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
return res;
}
private boolean getQos (int qosId) {
boolean res = false;
dlogi( "requesting qos spec for id: " + qosId);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
res = (mPhone.getQosStatus(qosId) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
dlogi("getQoS returned: " + res);
return res;
}
private boolean modifyQos (int qosId, QosSpec spec) {
boolean res = false;
if (spec == null) {
dlogw( "qos spec is null");
return res;
}
dlogi( "modifying qos spec for id: " + qosId);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
res = (mPhone.modifyQos(qosId, spec) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
return res;
}
private boolean suspendQos (int qosId) {
boolean res = false;
dlogi( "suspending qos for id: " + qosId);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
res = (mPhone.suspendQos(qosId) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
return res;
}
private boolean resumeQos (int qosId) {
boolean res = false;
dlogi( "resuming qos for id: " + qosId);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
res = (mPhone.resumeQos(qosId) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
return res;
}
private ITelephony getPhone() {
return ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
}
/**
* Convert a data rate string such as "2Mbps" to an integer
* representing the data rate in bps. If no data rate is given
* then bps will be assumed.
* @param rate a data rate string, such as "100kbps" or "2Mbps"
* @return the data rate in bps as an integer
*/
private static int parseBwString(String rate) {
if (rate == null) return 0;
int rateMultiple = 1; // defaults to bps
if (rate.toLowerCase().endsWith("kbps") || rate.endsWith("kbit/s") || rate.endsWith("kb/s")) {
rateMultiple = 1000; // 1,000 bps per 1 Mbps
} else if (rate.toLowerCase().endsWith("mbps") || rate.endsWith("Mbit/s") || rate.endsWith("Mb/s")) {
rateMultiple = 1000000; // 1,000,000 bps per 1 Mbps
} else if (rate.toLowerCase().endsWith("gbps") || rate.endsWith("Gbit/s") || rate.endsWith("Gb/s")) {
rateMultiple = 1000000000; // 1,000,000,000 bps per 1 Gbps
}
// find first non-numeric character, and trim
int trimPosition = rate.length();
for (int i = 0; i < rate.length(); i++) {
if (rate.charAt(i) <= '0' || rate.charAt(i) >= '9') {
trimPosition = i;
break;
}
}
rate = rate.substring(0, trimPosition);
if (rate.length() == 0) rate = "0";
return (Integer.parseInt(rate) * rateMultiple);
}
/* logging macros */
private void logd (String s) {
Log.d(LOG_TAG,s);
}
private void loge (String s) {
Log.e(LOG_TAG,s);
}
private void logw (String s) {
Log.w(LOG_TAG,s);
}
private void logv (String s) {
Log.v(LOG_TAG,s);
}
private void logi (String s) {
Log.i(LOG_TAG,s);
}
private void dlogd (String s) {
if (DBG) Log.d(LOCAL_TAG,s);
}
private void dloge (String s) {
if (DBG) Log.e(LOCAL_TAG,s);
}
private void dlogw (String s) {
if (DBG) Log.w(LOCAL_TAG,s);
}
private void dlogv (String s) {
if (DBG) Log.v(LOCAL_TAG,s);
}
private void dlogi (String s) {
if (DBG) Log.i(LOCAL_TAG,s);
}
}