| /* |
| * Copyright (C) 2019 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.internal.telephony.dataconnection; |
| |
| import android.annotation.NonNull; |
| import android.net.LinkProperties; |
| import android.net.NattKeepalivePacketData; |
| import android.net.NetworkAgent; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkInfo; |
| import android.net.NetworkMisc; |
| import android.net.SocketKeepalive; |
| import android.os.Message; |
| import android.telephony.AccessNetworkConstants; |
| import android.telephony.AccessNetworkConstants.TransportType; |
| import android.telephony.Rlog; |
| import android.util.LocalLog; |
| import android.util.SparseArray; |
| |
| import com.android.internal.telephony.DctConstants; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.RILConstants; |
| import com.android.internal.util.IndentingPrintWriter; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * This class represents a network agent which is communication channel between |
| * {@link DataConnection} and {@link com.android.server.ConnectivityService}. The agent is |
| * created when data connection enters {@link DataConnection.DcActiveState} until it exits that |
| * state. |
| * |
| * Note that in IWLAN handover scenario, this agent could be transferred to the new |
| * {@link DataConnection} so for a short window of time this object might be accessed by two |
| * different {@link DataConnection}. Thus each method in this class needs to be synchronized. |
| */ |
| public class DcNetworkAgent extends NetworkAgent { |
| private String mTag; |
| |
| private Phone mPhone; |
| |
| private int mTransportType; |
| |
| private NetworkCapabilities mNetworkCapabilities; |
| |
| public final DcKeepaliveTracker keepaliveTracker = new DcKeepaliveTracker(); |
| |
| private DataConnection mDataConnection; |
| |
| private final LocalLog mNetCapsLocalLog = new LocalLog(50); |
| |
| private static AtomicInteger sSerialNumber = new AtomicInteger(0); |
| |
| private DcNetworkAgent(DataConnection dc, String tag, Phone phone, NetworkInfo ni, |
| int score, NetworkMisc misc, int factorySerialNumber, |
| int transportType) { |
| super(dc.getHandler().getLooper(), phone.getContext(), tag, ni, |
| dc.getNetworkCapabilities(), dc.getLinkProperties(), score, misc, |
| factorySerialNumber); |
| mTag = tag; |
| mPhone = phone; |
| mNetworkCapabilities = dc.getNetworkCapabilities(); |
| mTransportType = transportType; |
| mDataConnection = dc; |
| logd(tag + " created for data connection " + dc.getName()); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param dc The data connection owns this network agent. |
| * @param phone The phone object. |
| * @param ni Network info. |
| * @param score Score of the data connection. |
| * @param misc The miscellaneous information of the data connection. |
| * @param factorySerialNumber Serial number of telephony network factory. |
| * @param transportType The transport of the data connection. |
| * @return The network agent |
| */ |
| public static DcNetworkAgent createDcNetworkAgent(DataConnection dc, Phone phone, |
| NetworkInfo ni, int score, NetworkMisc misc, |
| int factorySerialNumber, int transportType) { |
| // Use serial number only. Do not use transport type because it can be transferred to |
| // a different transport. |
| String tag = "DcNetworkAgent-" + sSerialNumber.incrementAndGet(); |
| return new DcNetworkAgent(dc, tag, phone, ni, score, misc, factorySerialNumber, |
| transportType); |
| } |
| |
| /** |
| * Set the data connection that owns this network agent. |
| * |
| * @param dc Data connection owning this network agent. |
| * @param transportType Transport that this data connection is on. |
| */ |
| public synchronized void acquireOwnership(@NonNull DataConnection dc, |
| @TransportType int transportType) { |
| mDataConnection = dc; |
| mTransportType = transportType; |
| logd(dc.getName() + " acquired the ownership of this agent."); |
| } |
| |
| /** |
| * Release the ownership of network agent. |
| */ |
| public synchronized void releaseOwnership(DataConnection dc) { |
| if (mDataConnection == null) { |
| loge("releaseOwnership called on no-owner DcNetworkAgent!"); |
| return; |
| } else if (mDataConnection != dc) { |
| log("releaseOwnership: This agent belongs to " |
| + mDataConnection.getName() + ", ignored the request from " + dc.getName()); |
| return; |
| } |
| logd("Data connection " + mDataConnection.getName() + " released the ownership."); |
| mDataConnection = null; |
| } |
| |
| @Override |
| protected synchronized void unwanted() { |
| if (mDataConnection == null) { |
| loge("Unwanted found called on no-owner DcNetworkAgent!"); |
| return; |
| } |
| |
| logd("unwanted called. Now tear down the data connection " |
| + mDataConnection.getName()); |
| mDataConnection.tearDownAll(Phone.REASON_RELEASED_BY_CONNECTIVITY_SERVICE, |
| DcTracker.RELEASE_TYPE_DETACH, null); |
| } |
| |
| @Override |
| protected synchronized void pollLceData() { |
| if (mDataConnection == null) { |
| loge("pollLceData called on no-owner DcNetworkAgent!"); |
| return; |
| } |
| |
| if (mPhone.getLceStatus() == RILConstants.LCE_ACTIVE // active LCE service |
| && mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { |
| mPhone.mCi.pullLceData(mDataConnection.obtainMessage( |
| DataConnection.EVENT_BW_REFRESH_RESPONSE)); |
| } |
| } |
| |
| @Override |
| protected synchronized void networkStatus(int status, String redirectUrl) { |
| if (mDataConnection == null) { |
| loge("networkStatus called on no-owner DcNetworkAgent!"); |
| return; |
| } |
| |
| logd("validation status: " + status + " with redirection URL: " + redirectUrl); |
| DcTracker dct = mPhone.getDcTracker(mTransportType); |
| if (dct != null) { |
| Message msg = dct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED, |
| status, 0, redirectUrl); |
| msg.sendToTarget(); |
| } |
| } |
| |
| /** |
| * Set the network capabilities. |
| * |
| * @param networkCapabilities The network capabilities. |
| * @param dc The data connection that invokes this method. |
| */ |
| public synchronized void sendNetworkCapabilities(NetworkCapabilities networkCapabilities, |
| DataConnection dc) { |
| if (mDataConnection == null) { |
| loge("sendNetworkCapabilities called on no-owner DcNetworkAgent!"); |
| return; |
| } else if (mDataConnection != dc) { |
| loge("sendNetworkCapabilities: This agent belongs to " |
| + mDataConnection.getName() + ", ignored the request from " + dc.getName()); |
| return; |
| } |
| |
| if (!networkCapabilities.equals(mNetworkCapabilities)) { |
| String logStr = "Changed from " + mNetworkCapabilities + " to " |
| + networkCapabilities + ", Data RAT=" |
| + mPhone.getServiceState().getRilDataRadioTechnology() |
| + ", dc=" + mDataConnection.getName(); |
| logd(logStr); |
| mNetCapsLocalLog.log(logStr); |
| mNetworkCapabilities = networkCapabilities; |
| } |
| sendNetworkCapabilities(networkCapabilities); |
| } |
| |
| /** |
| * Set the link properties |
| * |
| * @param linkProperties The link properties |
| * @param dc The data connection that invokes this method. |
| */ |
| public synchronized void sendLinkProperties(LinkProperties linkProperties, |
| DataConnection dc) { |
| if (mDataConnection == null) { |
| loge("sendLinkProperties called on no-owner DcNetworkAgent!"); |
| return; |
| } else if (mDataConnection != dc) { |
| loge("sendLinkProperties: This agent belongs to " |
| + mDataConnection.getName() + ", ignored the request from " + dc.getName()); |
| return; |
| } |
| sendLinkProperties(linkProperties); |
| } |
| |
| /** |
| * Set the network score. |
| * |
| * @param score The network score. |
| * @param dc The data connection that invokes this method. |
| */ |
| public synchronized void sendNetworkScore(int score, DataConnection dc) { |
| if (mDataConnection == null) { |
| loge("sendNetworkScore called on no-owner DcNetworkAgent!"); |
| return; |
| } else if (mDataConnection != dc) { |
| loge("sendNetworkScore: This agent belongs to " |
| + mDataConnection.getName() + ", ignored the request from " + dc.getName()); |
| return; |
| } |
| sendNetworkScore(score); |
| } |
| |
| /** |
| * Set the network info. |
| * |
| * @param networkInfo The network info. |
| * @param dc The data connection that invokes this method. |
| */ |
| public synchronized void sendNetworkInfo(NetworkInfo networkInfo, DataConnection dc) { |
| if (mDataConnection == null) { |
| loge("sendNetworkInfo called on no-owner DcNetworkAgent!"); |
| return; |
| } else if (mDataConnection != dc) { |
| loge("sendNetworkInfo: This agent belongs to " |
| + mDataConnection.getName() + ", ignored the request from " + dc.getName()); |
| return; |
| } |
| sendNetworkInfo(networkInfo); |
| } |
| |
| @Override |
| protected synchronized void startSocketKeepalive(Message msg) { |
| if (mDataConnection == null) { |
| loge("startSocketKeepalive called on no-owner DcNetworkAgent!"); |
| return; |
| } |
| |
| if (msg.obj instanceof NattKeepalivePacketData) { |
| mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_START_REQUEST, |
| msg.arg1, msg.arg2, msg.obj).sendToTarget(); |
| } else { |
| onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED); |
| } |
| } |
| |
| @Override |
| protected synchronized void stopSocketKeepalive(Message msg) { |
| if (mDataConnection == null) { |
| loge("stopSocketKeepalive called on no-owner DcNetworkAgent!"); |
| return; |
| } |
| |
| mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_STOP_REQUEST, |
| msg.arg1, msg.arg2, msg.obj).sendToTarget(); |
| } |
| |
| @Override |
| public String toString() { |
| return "DcNetworkAgent:" |
| + " mDataConnection=" |
| + ((mDataConnection != null) ? mDataConnection.getName() : null) |
| + " mTransportType=" |
| + AccessNetworkConstants.transportTypeToString(mTransportType) |
| + " mNetworkCapabilities=" + mNetworkCapabilities; |
| } |
| |
| /** |
| * Dump the state of transport manager |
| * |
| * @param fd File descriptor |
| * @param printWriter Print writer |
| * @param args Arguments |
| */ |
| public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { |
| IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); |
| pw.println(toString()); |
| pw.increaseIndent(); |
| pw.println("Net caps logs:"); |
| mNetCapsLocalLog.dump(fd, pw, args); |
| pw.decreaseIndent(); |
| } |
| |
| /** |
| * Log with debug level |
| * |
| * @param s is string log |
| */ |
| private void logd(String s) { |
| Rlog.d(mTag, s); |
| } |
| |
| /** |
| * Log with error level |
| * |
| * @param s is string log |
| */ |
| private void loge(String s) { |
| Rlog.e(mTag, s); |
| } |
| |
| class DcKeepaliveTracker { |
| private class KeepaliveRecord { |
| public int slotId; |
| public int currentStatus; |
| |
| KeepaliveRecord(int slotId, int status) { |
| this.slotId = slotId; |
| this.currentStatus = status; |
| } |
| } |
| |
| private final SparseArray<KeepaliveRecord> mKeepalives = new SparseArray(); |
| |
| int getHandleForSlot(int slotId) { |
| for (int i = 0; i < mKeepalives.size(); i++) { |
| KeepaliveRecord kr = mKeepalives.valueAt(i); |
| if (kr.slotId == slotId) return mKeepalives.keyAt(i); |
| } |
| return -1; |
| } |
| |
| int keepaliveStatusErrorToPacketKeepaliveError(int error) { |
| switch(error) { |
| case KeepaliveStatus.ERROR_NONE: |
| return SocketKeepalive.SUCCESS; |
| case KeepaliveStatus.ERROR_UNSUPPORTED: |
| return SocketKeepalive.ERROR_UNSUPPORTED; |
| case KeepaliveStatus.ERROR_NO_RESOURCES: |
| return SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES; |
| case KeepaliveStatus.ERROR_UNKNOWN: |
| default: |
| return SocketKeepalive.ERROR_HARDWARE_ERROR; |
| } |
| } |
| |
| void handleKeepaliveStarted(final int slot, KeepaliveStatus ks) { |
| switch (ks.statusCode) { |
| case KeepaliveStatus.STATUS_INACTIVE: |
| DcNetworkAgent.this.onSocketKeepaliveEvent(slot, |
| keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode)); |
| break; |
| case KeepaliveStatus.STATUS_ACTIVE: |
| DcNetworkAgent.this.onSocketKeepaliveEvent( |
| slot, SocketKeepalive.SUCCESS); |
| // fall through to add record |
| case KeepaliveStatus.STATUS_PENDING: |
| logd("Adding keepalive handle=" |
| + ks.sessionHandle + " slot = " + slot); |
| mKeepalives.put(ks.sessionHandle, |
| new KeepaliveRecord( |
| slot, ks.statusCode)); |
| break; |
| default: |
| logd("Invalid KeepaliveStatus Code: " + ks.statusCode); |
| break; |
| } |
| } |
| |
| void handleKeepaliveStatus(KeepaliveStatus ks) { |
| final KeepaliveRecord kr; |
| kr = mKeepalives.get(ks.sessionHandle); |
| |
| if (kr == null) { |
| // If there is no slot for the session handle, we received an event |
| // for a different data connection. This is not an error because the |
| // keepalive session events are broadcast to all listeners. |
| loge("Discarding keepalive event for different data connection:" + ks); |
| return; |
| } |
| // Switch on the current state, to see what we do with the status update |
| switch (kr.currentStatus) { |
| case KeepaliveStatus.STATUS_INACTIVE: |
| logd("Inactive Keepalive received status!"); |
| DcNetworkAgent.this.onSocketKeepaliveEvent( |
| kr.slotId, SocketKeepalive.ERROR_HARDWARE_ERROR); |
| break; |
| case KeepaliveStatus.STATUS_PENDING: |
| switch (ks.statusCode) { |
| case KeepaliveStatus.STATUS_INACTIVE: |
| DcNetworkAgent.this.onSocketKeepaliveEvent(kr.slotId, |
| keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode)); |
| kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE; |
| mKeepalives.remove(ks.sessionHandle); |
| break; |
| case KeepaliveStatus.STATUS_ACTIVE: |
| logd("Pending Keepalive received active status!"); |
| kr.currentStatus = KeepaliveStatus.STATUS_ACTIVE; |
| DcNetworkAgent.this.onSocketKeepaliveEvent( |
| kr.slotId, SocketKeepalive.SUCCESS); |
| break; |
| case KeepaliveStatus.STATUS_PENDING: |
| loge("Invalid unsolicied Keepalive Pending Status!"); |
| break; |
| default: |
| loge("Invalid Keepalive Status received, " + ks.statusCode); |
| } |
| break; |
| case KeepaliveStatus.STATUS_ACTIVE: |
| switch (ks.statusCode) { |
| case KeepaliveStatus.STATUS_INACTIVE: |
| logd("Keepalive received stopped status!"); |
| DcNetworkAgent.this.onSocketKeepaliveEvent( |
| kr.slotId, SocketKeepalive.SUCCESS); |
| |
| kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE; |
| mKeepalives.remove(ks.sessionHandle); |
| break; |
| case KeepaliveStatus.STATUS_PENDING: |
| case KeepaliveStatus.STATUS_ACTIVE: |
| loge("Active Keepalive received invalid status!"); |
| break; |
| default: |
| loge("Invalid Keepalive Status received, " + ks.statusCode); |
| } |
| break; |
| default: |
| loge("Invalid Keepalive Status received, " + kr.currentStatus); |
| } |
| } |
| } |
| } |