blob: 18e123b0899324a87048eef3363418da2d43a9fe [file] [log] [blame]
/*
* Copyright (C) 2021 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.qns;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.Context;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.telephony.AccessNetworkConstants;
import android.telephony.DataFailCause;
import android.telephony.PreciseDataConnectionState;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.util.TelephonyUtils;
public class DataConnectionStatusTracker {
private static final int EVENT_DATA_CONNECTION_STATE_CHANGED = 11001;
protected final Context mContext;
protected final int mSlotIndex;
private final String LOG_TAG;
private final int mApnType;
@VisibleForTesting protected final Handler mHandler;
private DataConnectionChangedInfo mLastUpdatedDcChangedInfo;
private final QnsTelephonyListener mQnsTelephonyListener;
public static final int STATE_INACTIVE = 0;
public static final int STATE_CONNECTING = 1;
public static final int STATE_CONNECTED = 2;
public static final int STATE_HANDOVER = 3;
@IntDef(
value = {
STATE_INACTIVE,
STATE_CONNECTING,
STATE_CONNECTED,
STATE_HANDOVER,
})
public @interface DataConnectionState {}
public static final int EVENT_DATA_CONNECTION_DISCONNECTED = 0;
public static final int EVENT_DATA_CONNECTION_STARTED = 1;
public static final int EVENT_DATA_CONNECTION_CONNECTED = 2;
public static final int EVENT_DATA_CONNECTION_FAILED = 3;
public static final int EVENT_DATA_CONNECTION_HANDOVER_STARTED = 4;
public static final int EVENT_DATA_CONNECTION_HANDOVER_SUCCESS = 5;
public static final int EVENT_DATA_CONNECTION_HANDOVER_FAILED = 6;
@IntDef(
value = {
EVENT_DATA_CONNECTION_DISCONNECTED,
EVENT_DATA_CONNECTION_STARTED,
EVENT_DATA_CONNECTION_CONNECTED,
EVENT_DATA_CONNECTION_FAILED,
EVENT_DATA_CONNECTION_HANDOVER_STARTED,
EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
EVENT_DATA_CONNECTION_HANDOVER_FAILED,
})
public @interface DataConnectionChangedEvent {}
private final RegistrantList mDataConnectionStatusRegistrants;
private int mState = STATE_INACTIVE;
private int mDataConnectionFailCause;
private int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
private SparseArray<ApnSetting> mApnSettings = new SparseArray<>();
public DataConnectionStatusTracker(
@NonNull Context context, Looper looper, int slotIndex, int apnType) {
LOG_TAG =
QnsConstants.QNS_TAG
+ "_"
+ DataConnectionStatusTracker.class.getSimpleName()
+ "_"
+ slotIndex
+ "_"
+ QnsUtils.getStringApnTypes(apnType);
mContext = context;
mSlotIndex = slotIndex;
mApnType = apnType;
mHandler = new DataConnectionStatusTrackerHandler(looper);
mDataConnectionStatusRegistrants = new RegistrantList();
mQnsTelephonyListener = QnsTelephonyListener.getInstance(context, slotIndex);
mQnsTelephonyListener.registerPreciseDataConnectionStateChanged(
mApnType, mHandler, EVENT_DATA_CONNECTION_STATE_CHANGED, null, true);
}
protected void log(String s) {
Log.d(LOG_TAG, s);
}
public boolean isInactiveState() {
return mState == STATE_INACTIVE;
}
public boolean isActiveState() {
return mState == STATE_CONNECTED || mState == STATE_HANDOVER;
}
public boolean isHandoverState() {
return mState == STATE_HANDOVER;
}
public boolean isConnectionInProgress() {
return mState == STATE_CONNECTING || mState == STATE_HANDOVER;
}
public int getLastTransportType() {
return mTransportType;
}
public int getLastFailCause() {
return mDataConnectionFailCause;
}
/** Returns Latest APN setting for the transport type */
public ApnSetting getLastApnSetting(int transportType) {
try {
return mApnSettings.get(transportType);
} catch (Exception e) {
return null;
}
}
public void registerDataConnectionStatusChanged(Handler h, int what) {
if (h != null) {
mDataConnectionStatusRegistrants.addUnique(h, what, null);
}
if (mLastUpdatedDcChangedInfo != null) {
Registrant r = new Registrant(h, what, null);
r.notifyResult(mLastUpdatedDcChangedInfo);
}
}
public void unRegisterDataConnectionStatusChanged(Handler h) {
if (h != null) {
mDataConnectionStatusRegistrants.remove(h);
}
}
private void onDataConnectionStateChanged(PreciseDataConnectionState status) {
int transportType = status.getTransportType();
int state = status.getState();
mDataConnectionFailCause = status.getLastCauseCode();
log(
"onDataConnectionChanged transportType:"
+ AccessNetworkConstants.transportTypeToString(transportType)
+ " state:"
+ TelephonyUtils.dataStateToString(status.getState())
+ " cause:"
+ DataFailCause.toString(status.getLastCauseCode()));
switch (state) {
case TelephonyManager.DATA_DISCONNECTED:
if (mState == STATE_CONNECTED || mState == STATE_HANDOVER) {
mState = STATE_INACTIVE;
mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
log("Connection Disconnected");
notifyDataConnectionStatusChangedEvent(EVENT_DATA_CONNECTION_DISCONNECTED);
} else {
if (mState == STATE_CONNECTING) {
// Initial connect Failed.
mState = STATE_INACTIVE;
mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
log("Initial connect failed");
notifyDataConnectionFailed(transportType);
}
}
break;
case TelephonyManager.DATA_CONNECTING:
if (mState == STATE_INACTIVE) {
mState = STATE_CONNECTING;
log(
"Initial Connect inited transport: "
+ AccessNetworkConstants.transportTypeToString(transportType));
notifyDataConnectionStarted(transportType);
}
break;
case TelephonyManager.DATA_CONNECTED:
if (mState == STATE_CONNECTING || mState == STATE_INACTIVE) {
mState = STATE_CONNECTED;
mTransportType = transportType;
log(
"Data Connected Transport: "
+ AccessNetworkConstants.transportTypeToString(mTransportType));
notifyDataConnectionStatusChangedEvent(EVENT_DATA_CONNECTION_CONNECTED);
} else if (mState == STATE_HANDOVER && mTransportType != transportType) {
mState = STATE_CONNECTED;
mTransportType = transportType;
log(
"Handover completed to: "
+ AccessNetworkConstants.transportTypeToString(mTransportType));
notifyDataConnectionStatusChangedEvent(EVENT_DATA_CONNECTION_HANDOVER_SUCCESS);
} else if (mState == STATE_HANDOVER && mTransportType == transportType) {
mState = STATE_CONNECTED;
log(
"Handover failed and return to: "
+ AccessNetworkConstants.transportTypeToString(mTransportType));
notifyDataConnectionStatusChangedEvent(EVENT_DATA_CONNECTION_HANDOVER_FAILED);
}
break;
case TelephonyManager.DATA_SUSPENDED:
if (mState == STATE_HANDOVER && mTransportType != transportType) {
mState = STATE_CONNECTED;
mTransportType = transportType;
log(
"QNS assumes Handover completed to: "
+ AccessNetworkConstants.transportTypeToString(mTransportType));
notifyDataConnectionStatusChangedEvent(EVENT_DATA_CONNECTION_HANDOVER_SUCCESS);
}
break;
case TelephonyManager.DATA_HANDOVER_IN_PROGRESS:
if (mState == STATE_CONNECTED && mTransportType == transportType) {
mState = STATE_HANDOVER;
log(
"Handover initiated from "
+ AccessNetworkConstants.transportTypeToString(transportType));
notifyDataConnectionStatusChangedEvent(EVENT_DATA_CONNECTION_HANDOVER_STARTED);
} else {
log(
"Ignore STATE_HANDOVER since request is not for Src TransportType: "
+ AccessNetworkConstants.transportTypeToString(mTransportType));
}
break;
default:
break;
}
mApnSettings.put(mTransportType, status.getApnSetting());
}
private void notifyDataConnectionStarted(int transportType) {
DataConnectionChangedInfo info =
new DataConnectionChangedInfo(EVENT_DATA_CONNECTION_STARTED, mState, transportType);
mLastUpdatedDcChangedInfo = info;
mDataConnectionStatusRegistrants.notifyResult(info);
}
private void notifyDataConnectionFailed(int transportType) {
DataConnectionChangedInfo info =
new DataConnectionChangedInfo(EVENT_DATA_CONNECTION_FAILED, mState, transportType);
mLastUpdatedDcChangedInfo = info;
mDataConnectionStatusRegistrants.notifyResult(info);
}
private void notifyDataConnectionStatusChangedEvent(int event) {
DataConnectionChangedInfo info =
new DataConnectionChangedInfo(event, mState, mTransportType);
mLastUpdatedDcChangedInfo = info;
mDataConnectionStatusRegistrants.notifyResult(info);
}
public void close() {
mQnsTelephonyListener.unregisterPreciseDataConnectionStateChanged(mApnType, mHandler);
mDataConnectionStatusRegistrants.removeAll();
}
public static String stateToString(int state) {
switch (state) {
case STATE_INACTIVE:
return "STATE_INCATIVE";
case STATE_CONNECTING:
return "STATE_CONNCTING";
case STATE_CONNECTED:
return "STATE_CONNECTED";
case STATE_HANDOVER:
return "STATE_HANDOVER";
}
return "INVALID";
}
public static String eventToString(int event) {
switch (event) {
case EVENT_DATA_CONNECTION_DISCONNECTED:
return "EVENT_DATA_CONNECTION_DISCONNECTED";
case EVENT_DATA_CONNECTION_STARTED:
return "EVENT_DATA_CONNECTION_STARTED";
case EVENT_DATA_CONNECTION_CONNECTED:
return "EVENT_DATA_CONNECTION_CONNECTED";
case EVENT_DATA_CONNECTION_FAILED:
return "EVENT_DATA_CONNECTION_FAILED";
case EVENT_DATA_CONNECTION_HANDOVER_STARTED:
return "EVENT_DATA_CONNECTION_HANDOVER_STARTED";
case EVENT_DATA_CONNECTION_HANDOVER_SUCCESS:
return "EVENT_DATA_CONNECTION_HANDOVER_SUCCESS";
case EVENT_DATA_CONNECTION_HANDOVER_FAILED:
return "EVENT_DATA_CONNECTION_HANDOVER_FAILED";
}
return "INVALID";
}
public static class DataConnectionChangedInfo {
private final int mEvent;
private final int mState;
private final int mCurrentTransportType;
@Override
public String toString() {
return "DataConnectionChangedInfo{"
+ "mEvent="
+ eventToString(mEvent)
+ ", mState="
+ stateToString(mState)
+ ", mCurrentTransportType="
+ AccessNetworkConstants.transportTypeToString(mCurrentTransportType)
+ '}';
}
public DataConnectionChangedInfo(
@DataConnectionChangedEvent int event,
@DataConnectionState int state,
@AccessNetworkConstants.TransportType int transportType) {
mEvent = event;
mState = state;
mCurrentTransportType = transportType;
}
public int getState() {
return mState;
}
public int getEvent() {
return mEvent;
}
public int getTransportType() {
return mCurrentTransportType;
}
}
class DataConnectionStatusTrackerHandler extends Handler {
public DataConnectionStatusTrackerHandler(Looper l) {
super(l);
}
@Override
public void handleMessage(Message message) {
log("handleMessage msg=" + message.what);
AsyncResult ar = (AsyncResult) message.obj;
switch (message.what) {
case EVENT_DATA_CONNECTION_STATE_CHANGED:
onDataConnectionStateChanged((PreciseDataConnectionState) ar.result);
break;
default:
log("never reach here msg=" + message.what);
}
}
}
}