not all data call setup will be successful 100%, and if failure encountered, retry is just the normal way DCTs will go.
Dct uses PendingIntents to trigger retry and there are two kinds for the Intents:
INTENT_RECONNECT_ALARM and INTENT_RESTART_TRYSETUP_ALARM
DcTrackerBase.java
protected static final String INTENT_RECONNECT_ALARM =
"com.android.internal.telephony.data-reconnect";
protected static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "reconnect_alarm_extra_type";
protected static final String INTENT_RECONNECT_ALARM_EXTRA_REASON =
"reconnect_alarm_extra_reason";
protected static final String INTENT_RESTART_TRYSETUP_ALARM =
"com.android.internal.telephony.data-restart-trysetup";
protected static final String INTENT_RESTART_TRYSETUP_ALARM_EXTRA_TYPE =
"restart_trysetup_alarm_extra_type";
when Dcts were initialized, each APN type instance will register Receiver for the two
DcTracker.java
public DcTracker(PhoneBase p) {
...
for (ApnContext apnContext : mApnContexts.values()) {
// Register the reconnect and restart actions.
IntentFilter filter = new IntentFilter();
filter.addAction(INTENT_RECONNECT_ALARM + '.' + apnContext.getApnType());
filter.addAction(INTENT_RESTART_TRYSETUP_ALARM + '.' + apnContext.getApnType());
mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
}
and the code that handle the case were:
DcTrackerBase.java
protected BroadcastReceiver mIntentReceiver = new BroadcastReceiver ()
{
@Override
public void onReceive(Context context, Intent intent)
{
...
} else if (action.startsWith(INTENT_RECONNECT_ALARM)) {
if (DBG) log("Reconnect alarm. Previous state was " + mState);
onActionIntentReconnectAlarm(intent);
} else if (action.startsWith(INTENT_RESTART_TRYSETUP_ALARM)) {
if (DBG) log("Restart trySetup alarm");
onActionIntentRestartTrySetupAlarm(intent);
look, the details of the handler were warpped into two functions.
both the functions will send msg to DcT to tell her to setup data call:
protected void onActionIntentReconnectAlarm(Intent intent) {
String reason = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON);
String apnType = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE);
ApnContext apnContext = mApnContexts.get(apnType);
...
if ((apnContext != null) && (apnContext.isEnabled())) {
...
sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, apnContext));
apnContext.setReconnectIntent(null);
}
}
protected void onActionIntentRestartTrySetupAlarm(Intent intent) {
String apnType = intent.getStringExtra(INTENT_RESTART_TRYSETUP_ALARM_EXTRA_TYPE);
ApnContext apnContext = mApnContexts.get(apnType);
...
sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, apnContext));
}
you will find that the latter is more directly, so the scenarios to trigger this kind should be more restricted.
OK, let's back and find where the two types was really triggered.
one is
DcTracker.java
private void startAlarmForReconnect(int delay, ApnContext apnContext) {
String apnType = apnContext.getApnType();
Intent intent = new Intent(INTENT_RECONNECT_ALARM + "." + apnType);
intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, apnContext.getReason());
intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE, apnType);
...
PendingIntent alarmIntent = PendingIntent.getBroadcast (mPhone.getContext(), 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
apnContext.setReconnectIntent(alarmIntent);
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + delay, alarmIntent);
}
and the other
private void startAlarmForRestartTrySetup(int delay, ApnContext apnContext) {
String apnType = apnContext.getApnType();
Intent intent = new Intent(INTENT_RESTART_TRYSETUP_ALARM + "." + apnType);
intent.putExtra(INTENT_RESTART_TRYSETUP_ALARM_EXTRA_TYPE, apnType);
...
PendingIntent alarmIntent = PendingIntent.getBroadcast (mPhone.getContext(), 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
apnContext.setReconnectIntent(alarmIntent);
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + delay, alarmIntent);
}
then comes the question that where the two is called?
let's firstly focus on startAlarmForReconnect()
still DcTracker.java
protected void onDataSetupCompleteError(AsyncResult ar) {
...
ApnContext apnContext = null;
...
if (apnContext.getWaitingApns().isEmpty()) {
...
if (apnContext.getWaitingApnsPermFailCount() == 0) {
...
} else {
startAlarmForRestartTrySetup(APN_DELAY_MILLIS, apnContext);
}
} else {
startAlarmForReconnect(APN_DELAY_MILLIS, apnContext);
}
}
look, the first trigger point is the time that setup data call completed while with error encountered.
and you can find out that this function will be called only if there is waiting APNs that try to setup datacall.
you can easily find our startAlarmForRestartTrySetup() here too! and it is the only place where it is called!
so you should know that this trigger point is just to make requests in order and fair: if current request failed while there is any other request still waiting, then let it go and delay our retry. or it will call the startAlarmForRestartTrySetup() only when getWaitingApnsPermFailCount is not 0(in fact there is refCounter in ApnContext for all waitingApns, and the initialization is by calling ApnContext.setWaitingApns(), which is called in trySetupData() from class DcTracker. once DcTracker received DataSetupComplete event and the permanent error detected, the refCounter decreased and related ApnSettings be removed from ApnContext's waitingApns list).
here the obj in ar.userObj should always be an instance of ApnContext or exceptions occurred.
ApnContext.java
public synchronized void setWaitingApns(ArrayList<ApnSetting> waitingApns) {
mWaitingApns = waitingApns;
mWaitingApnsPermanentFailureCountDown.set(mWaitingApns.size());
}
public int getWaitingApnsPermFailCount() {
return mWaitingApnsPermanentFailureCountDown.get();
}
public void decWaitingApnsPermFailCount() {
mWaitingApnsPermanentFailureCountDown.decrementAndGet();
}
...
public synchronized void removeWaitingApn(ApnSetting apn) {
if (mWaitingApns != null) {
mWaitingApns.remove(apn);
}
}
DcTracker.java
private boolean trySetupData(ApnContext apnContext) {
...
if (apnContext.getState() == DctConstants.State.IDLE) {
ArrayList<ApnSetting> waitingApns = buildWaitingApns(apnContext.getApnType());
if (waitingApns.isEmpty()) {
...
} else {
apnContext.setWaitingApns(waitingApns);
...
@Override
protected void onDataSetupComplete(AsyncResult ar) {
...
if (ar.exception == null) {
...
} else {
cause = (DcFailCause) (ar.result);
...
if (cause.isPermanentFail()) apnContext.decWaitingApnsPermFailCount();
apnContext.removeWaitingApn(apnContext.getApnSetting());
let's back and the other place is
protected void onDisconnectDone(int connId, AsyncResult ar) {
ApnContext apnContext = null;
...
if (apnContext.isReady() && retryAfterDisconnected(apnContext.getReason())) {
startAlarmForReconnect(APN_DELAY_MILLIS, apnContext);
} else {
the two conditions needs are both from ApnContext
ApnContext.java
public boolean isReady() {
return mDataEnabled.get() && mDependencyMet.get();
}
and the latter function of retryAfterDisconnected() will be false if only the reson in ApnContext is
Phone
.
REASON_RADIO_TURNED_OFF.
so unless radio off, mostly retry will be depended on the ready status of the ApnContext.
also as above, here the obj in ar.userObj should always be an instance of ApnContext or exceptions occurred.
seems all covered for retrying now, is it right?
DataConnection.java
private boolean initConnection(ConnectionParams cp) {
...
configureRetry(mApnSetting.canHandleType(PhoneConstants.APN_TYPE_DEFAULT));
mRetryManager.setRetryCount(0);
mRetryManager.setCurMaxRetryCount(mConnectionParams.mInitialMaxRetry);
mRetryManager.setRetryForever(false);
do you notice that in our setup data call flow?
and here is define too
static final int BASE = Protocol.BASE_DATA_CONNECTION;
...
static final int EVENT_RETRY_CONNECTION = BASE + 10;
and the places trigger it is:
1.just setup data call completed and back to DataConnection before notice DcTracker.
private class DcActivatingState extends State {
@Override
public boolean processMessage(Message msg) {
...
case EVENT_SETUP_DATA_CONNECTION_DONE:
ar = (AsyncResult) msg.obj;
cp = (ConnectionParams) ar.userObj;
DataCallResponse.SetupResult result = onSetupConnectionCompleted(ar);
...
switch (result) {
...
case ERR_RilError:
int delay = mDcRetryAlarmController.getSuggestedRetryTime(
DataConnection.this, ar);
...
if (delay >= 0) {
mDcRetryAlarmController.startRetryAlarm(EVENT_RETRY_CONNECTION,
mTag, delay);
transitionTo(mRetryingState);
} else {
...
case EVENT_GET_LAST_FAIL_DONE:
ar = (AsyncResult) msg.obj;
cp = (ConnectionParams) ar.userObj;
if (cp.mTag == mTag) {
...
int retryDelay = mRetryManager.getRetryTimer();
...
if (mRetryManager.isRetryNeeded()) {
mDcRetryAlarmController.startRetryAlarm(EVENT_RETRY_CONNECTION, mTag,
retryDelay);
transitionTo(mRetryingState);
} else {
...
2.dataconnection was connected while now data connection lost detected from lower layer
private class DcActiveState extends State {
..
public boolean processMessage(Message msg) {
...
case EVENT_LOST_CONNECTION: {
...
if (mRetryManager.isRetryNeeded()) {
// We're going to retry
int delayMillis = mRetryManager.getRetryTimer();
mDcRetryAlarmController.startRetryAlarm(EVENT_RETRY_CONNECTION, mTag,
delayMillis);
transitionTo(mRetryingState);
} else {
...
we all know that the retry will be delayed but how do we know the delay should be?
in DcTracker the value is configured as constant while in DataConnection you see that all are coming from RetryManger.
let's put it a little later.
//+++++++++++
//not done
//-------------------
from both the trigger points we start delayed retry by DcRetryAlarmController and switch to RetryingState waiting for the retry time coming.
in class DcRetryAlarmController, it will use PendingIntent to implement the delay and after timer come up, it send retry msg(EVENT_RETRY_CONNECTION) back to DataConection.(code not list here now)
the switching handlers is simple as exit() do nothing in DcActiveState(just wondering if it will be better that some variables cleanup be placed in its exit() handler):
private class DcRetryingState extends State {
...
@Override
public void enter() {
notifyAllOfDisconnectDcRetrying(Phone.REASON_LOST_DATA_CONNECTION);
// Remove ourselves from cid mapping
mDcController.removeActiveDcByCid(DataConnection.this);
mCid = -1;
}
and in the state, it handes as:
switch (msg.what) {
case EVENT_RETRY_CONNECTION: {
if (msg.arg1 == mTag) {
mRetryManager.increaseRetryCount();
...
onConnect(mConnectionParams);
transitionTo(mActivatingState);
just try connect and back to ActivatingState to wait for retry result.
now the question is, why the mechanism will be seperated into two place(or file or layer)?
and there both in DcTracker and DataConnection will retry, will it be redundant and will there be any conflict and timing problem caused?
waiting for your answers.
//+++++++++++
//not done
//-------------------
notice: here i just find another article also talk about the same topic, if you are interested, you can visit the site by