aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-06-08 17:28:48 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-06-08 17:28:48 +0000
commit96cf24ae307f4176940f27de67fb81bed1eac5c0 (patch)
tree8ac8c6db51300ca508a3456b4b08e24276ff1d27
parent22438c86abc66ecd913d6f80347c139813a680e7 (diff)
parenta49a1df0b07b700fe8ae0623d2f41c930e1da406 (diff)
downloadtelephony-android-platform-13.0.0_r18.tar.gz
Change-Id: Ia13a141dfa3547f3a7712fc57bdab4e91cdc3e2f
-rw-r--r--src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java48
-rw-r--r--src/java/com/android/internal/telephony/PhoneSubInfoController.java26
-rw-r--r--src/java/com/android/internal/telephony/data/DataNetwork.java35
-rw-r--r--src/java/com/android/internal/telephony/data/DataNetworkController.java11
-rw-r--r--src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java100
-rw-r--r--src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java53
-rw-r--r--src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java83
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java33
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java80
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java67
10 files changed, 410 insertions, 126 deletions
diff --git a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
index 31442297b1..1e184bb69a 100644
--- a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
@@ -108,6 +108,10 @@ public class CarrierPrivilegesTracker extends Handler {
private static final String SHA_1 = "SHA-1";
private static final String SHA_256 = "SHA-256";
+ private static final int PACKAGE_NOT_PRIVILEGED = 0;
+ private static final int PACKAGE_PRIVILEGED_FROM_CARRIER_CONFIG = 1;
+ private static final int PACKAGE_PRIVILEGED_FROM_SIM = 2;
+
// TODO(b/232273884): Turn feature on when find solution to handle the inter-carriers switching
/**
* Time delay to clear UICC rules after UICC is gone.
@@ -757,23 +761,35 @@ public class CarrierPrivilegesTracker extends Handler {
@NonNull
private PrivilegedPackageInfo getCurrentPrivilegedPackagesForAllUsers() {
+ Set<String> carrierServiceEligiblePackages = new ArraySet<>();
Set<String> privilegedPackageNames = new ArraySet<>();
Set<Integer> privilegedUids = new ArraySet<>();
for (Map.Entry<String, Set<String>> e : mInstalledPackageCerts.entrySet()) {
- if (isPackagePrivileged(e.getKey(), e.getValue())) {
- privilegedPackageNames.add(e.getKey());
- privilegedUids.addAll(getUidsForPackage(e.getKey(), /* invalidateCache= */ false));
+ final int priv = getPackagePrivilegedStatus(e.getKey(), e.getValue());
+ switch (priv) {
+ case PACKAGE_PRIVILEGED_FROM_SIM:
+ carrierServiceEligiblePackages.add(e.getKey());
+ // fallthrough
+ case PACKAGE_PRIVILEGED_FROM_CARRIER_CONFIG:
+ privilegedPackageNames.add(e.getKey());
+ privilegedUids.addAll(
+ getUidsForPackage(e.getKey(), /* invalidateCache= */ false));
}
}
- return new PrivilegedPackageInfo(privilegedPackageNames, privilegedUids,
- getCarrierService(privilegedPackageNames));
+
+ return new PrivilegedPackageInfo(
+ privilegedPackageNames,
+ privilegedUids,
+ getCarrierService(carrierServiceEligiblePackages));
}
/**
- * Returns true iff there is an overlap between the provided certificate hashes and the
- * certificate hashes stored in mTestOverrideRules, mCarrierConfigRules and mUiccRules.
+ * Returns the privilege status of the provided package.
+ *
+ * <p>Returned privilege status depends on whether a package matches the certificates from
+ * carrier config, from test overrides or from certificates stored on the SIM.
*/
- private boolean isPackagePrivileged(@NonNull String pkgName, @NonNull Set<String> certs) {
+ private int getPackagePrivilegedStatus(@NonNull String pkgName, @NonNull Set<String> certs) {
// Double-nested for loops, but each collection should contain at most 2 elements in nearly
// every case.
// TODO(b/184382310) find a way to speed this up
@@ -782,23 +798,23 @@ public class CarrierPrivilegesTracker extends Handler {
if (mTestOverrideRules != null) {
for (UiccAccessRule rule : mTestOverrideRules) {
if (rule.matches(cert, pkgName)) {
- return true;
+ return PACKAGE_PRIVILEGED_FROM_SIM;
}
}
} else {
- for (UiccAccessRule rule : mCarrierConfigRules) {
+ for (UiccAccessRule rule : mUiccRules) {
if (rule.matches(cert, pkgName)) {
- return true;
+ return PACKAGE_PRIVILEGED_FROM_SIM;
}
}
- for (UiccAccessRule rule : mUiccRules) {
+ for (UiccAccessRule rule : mCarrierConfigRules) {
if (rule.matches(cert, pkgName)) {
- return true;
+ return PACKAGE_PRIVILEGED_FROM_CARRIER_CONFIG;
}
}
}
}
- return false;
+ return PACKAGE_NOT_PRIVILEGED;
}
@NonNull
@@ -1067,13 +1083,13 @@ public class CarrierPrivilegesTracker extends Handler {
}
@NonNull
- private Pair<String, Integer> getCarrierService(@NonNull Set<String> privilegedPackageNames) {
+ private Pair<String, Integer> getCarrierService(@NonNull Set<String> simPrivilegedPackages) {
List<ResolveInfo> carrierServiceResolveInfos = mPackageManager.queryIntentServices(
new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), /* flags= */ 0);
String carrierServicePackageName = null;
for (ResolveInfo resolveInfo : carrierServiceResolveInfos) {
String packageName = getPackageName(resolveInfo);
- if (privilegedPackageNames.contains(packageName)) {
+ if (simPrivilegedPackages.contains(packageName)) {
carrierServicePackageName = packageName;
break;
}
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index b1dfa5fb93..3e2e281375 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -31,7 +31,6 @@ import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
import android.os.TelephonyServiceManager.ServiceRegisterer;
-import android.os.UserHandle;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.PhoneNumberUtils;
import android.telephony.SubscriptionManager;
@@ -147,8 +146,7 @@ public class PhoneSubInfoController extends IPhoneSubInfo.Stub {
public String getSubscriberIdForSubscriber(int subId, String callingPackage,
String callingFeatureId) {
String message = "getSubscriberIdForSubscriber";
-
- enforceCallingPackage(callingPackage, Binder.getCallingUid(), message);
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
long identity = Binder.clearCallingIdentity();
boolean isActive;
@@ -316,28 +314,6 @@ public class PhoneSubInfoController extends IPhoneSubInfo.Stub {
"Requires MODIFY_PHONE_STATE");
}
- /**
- * Make sure the caller is the calling package itself
- *
- * @throws SecurityException if the caller is not the calling package
- */
- private void enforceCallingPackage(String callingPackage, int callingUid, String message) {
- int packageUid = -1;
- PackageManager pm = mContext.createContextAsUser(
- UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
- if (pm != null) {
- try {
- packageUid = pm.getPackageUid(callingPackage, 0);
- } catch (PackageManager.NameNotFoundException e) {
- // packageUid is -1
- }
- }
- if (packageUid != callingUid) {
- throw new SecurityException(message + ": Package " + callingPackage
- + " does not belong to " + callingUid);
- }
- }
-
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private int getDefaultSubscription() {
return PhoneFactory.getDefaultSubscription();
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index e6d0be7212..bf3c1d96d6 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -545,6 +545,10 @@ public class DataNetwork extends StateMachine {
/** Data network controller. */
private final @NonNull DataNetworkController mDataNetworkController;
+ /** Data network controller callback. */
+ private final @NonNull DataNetworkController.DataNetworkControllerCallback
+ mDataNetworkControllerCallback;
+
/** Data config manager. */
private final @NonNull DataConfigManager mDataConfigManager;
@@ -870,12 +874,14 @@ public class DataNetwork extends StateMachine {
mAccessNetworksManager = phone.getAccessNetworksManager();
mVcnManager = mPhone.getContext().getSystemService(VcnManager.class);
mDataNetworkController = phone.getDataNetworkController();
+ mDataNetworkControllerCallback = new DataNetworkController.DataNetworkControllerCallback(
+ getHandler()::post) {
+ @Override
+ public void onSubscriptionPlanOverride() {
+ sendMessage(EVENT_SUBSCRIPTION_PLAN_OVERRIDE);
+ }};
mDataNetworkController.registerDataNetworkControllerCallback(
- new DataNetworkController.DataNetworkControllerCallback(getHandler()::post) {
- @Override
- public void onSubscriptionPlanOverride() {
- sendMessage(EVENT_SUBSCRIPTION_PLAN_OVERRIDE);
- }});
+ mDataNetworkControllerCallback);
mDataConfigManager = mDataNetworkController.getDataConfigManager();
mDataCallSessionStats = new DataCallSessionStats(mPhone);
mDataNetworkCallback = callback;
@@ -1562,6 +1568,8 @@ public class DataNetwork extends StateMachine {
}
notifyPreciseDataConnectionState();
mNetworkAgent.unregister();
+ mDataNetworkController.unregisterDataNetworkControllerCallback(
+ mDataNetworkControllerCallback);
mDataCallSessionStats.onDataCallDisconnected(mFailCause);
if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
@@ -2361,7 +2369,7 @@ public class DataNetwork extends StateMachine {
logl("onSetupResponse: resultCode=" + DataServiceCallback.resultCodeToString(resultCode)
+ ", response=" + response);
mFailCause = getFailCauseFromDataCallResponse(resultCode, response);
- validateDataCallResponse(response);
+ validateDataCallResponse(response, true /*isSetupResponse*/);
if (mFailCause == DataFailCause.NONE) {
if (mDataNetworkController.isNetworkInterfaceExisting(response.getInterfaceName())) {
logl("Interface " + response.getInterfaceName() + " already existing. Silently "
@@ -2431,8 +2439,10 @@ public class DataNetwork extends StateMachine {
* If the {@link DataCallResponse} contains invalid info, triggers an anomaly report.
*
* @param response The response to be validated
+ * @param isSetupResponse {@code true} if the response is for initial data call setup
*/
- private void validateDataCallResponse(@Nullable DataCallResponse response) {
+ private void validateDataCallResponse(@Nullable DataCallResponse response,
+ boolean isSetupResponse) {
if (response == null
|| response.getLinkStatus() == DataCallResponse.LINK_STATUS_INACTIVE) return;
int failCause = response.getCause();
@@ -2452,9 +2462,12 @@ public class DataNetwork extends StateMachine {
reportAnomaly("Invalid DataCallResponse detected",
"1f273e9d-b09c-46eb-ad1c-421d01f61164");
}
+ // Check IP for initial setup response
NetworkRegistrationInfo nri = getNetworkRegistrationInfo();
- if (mDataProfile.getApnSetting() != null && nri != null && nri.isInService()) {
- boolean isRoaming = mPhone.getServiceState().getDataRoamingFromRegistration();
+ if (isSetupResponse
+ && mDataProfile.getApnSetting() != null && nri != null && nri.isInService()) {
+ boolean isRoaming = nri.getInitialRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
int protocol = isRoaming ? mDataProfile.getApnSetting().getRoamingProtocol()
: mDataProfile.getApnSetting().getProtocol();
String underlyingDataService = mTransport
@@ -2611,7 +2624,7 @@ public class DataNetwork extends StateMachine {
if (response != null) {
if (!response.equals(mDataCallResponse)) {
log("onDataStateChanged: " + response);
- validateDataCallResponse(response);
+ validateDataCallResponse(response, false /*isSetupResponse*/);
mDataCallResponse = response;
if (response.getLinkStatus() != DataCallResponse.LINK_STATUS_INACTIVE) {
updateDataNetwork(response);
@@ -3189,7 +3202,7 @@ public class DataNetwork extends StateMachine {
logl("onHandoverResponse: resultCode=" + DataServiceCallback.resultCodeToString(resultCode)
+ ", response=" + response);
mFailCause = getFailCauseFromDataCallResponse(resultCode, response);
- validateDataCallResponse(response);
+ validateDataCallResponse(response, false /*isSetupResponse*/);
if (mFailCause == DataFailCause.NONE) {
// Handover succeeded.
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index d00429387c..cdcf258763 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -2626,11 +2626,22 @@ public class DataNetworkController extends Handler {
NetworkRequestList requestList = new NetworkRequestList(
dataSetupRetryEntry.networkRequestList);
requestList.removeIf(request -> !mAllNetworkRequestList.contains(request));
+ // Retrieves the newly added unsatisfied NetworkRequest if all NetworkRequests in the
+ // DataSetupRetryEntry have already been removed.
+ if (requestList.isEmpty()) {
+ List<NetworkRequestList> groupRequestLists = getGroupedUnsatisfiedNetworkRequests();
+ dataSetupRetryEntry.networkRequestList.stream()
+ .filter(request -> groupRequestLists.stream()
+ .anyMatch(groupRequestList -> groupRequestList
+ .get(request.getCapabilities()) != null))
+ .forEach(requestList::add);
+ }
if (requestList.isEmpty()) {
loge("onDataNetworkSetupRetry: Request list is empty. Abort retry.");
dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
return;
}
+ log("onDataNetworkSetupRetry: Request list:" + requestList);
TelephonyNetworkRequest telephonyNetworkRequest = requestList.get(0);
int networkCapability = telephonyNetworkRequest.getApnTypeNetworkCapability();
diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
index e46781595f..7b4e5af0ce 100644
--- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
@@ -151,12 +151,15 @@ public class DataStallRecoveryManager extends Handler {
private @ElapsedRealtimeLong long mTimeLastRecoveryStartMs;
/** Whether current network is good or not */
private boolean mIsValidNetwork;
+ /** Whether data stall recovery is triggered or not */
+ private boolean mRecoveryTriggered = false;
/** Whether data stall happened or not. */
private boolean mDataStalled;
/** Whether the result of last action(RADIO_RESTART) reported. */
private boolean mLastActionReported;
/** The real time for data stall start. */
- private @ElapsedRealtimeLong long mDataStallStartMs;
+ @VisibleForTesting
+ public @ElapsedRealtimeLong long mDataStallStartMs;
/** Last data stall recovery action. */
private @RecoveryAction int mLastAction;
/** Last radio power state. */
@@ -173,6 +176,8 @@ public class DataStallRecoveryManager extends Handler {
private boolean mIsAttemptedAllSteps;
/** Whether internet network connected. */
private boolean mIsInternetNetworkConnected;
+ /** The durations for current recovery action */
+ private @ElapsedRealtimeLong long mTimeElapsedOfCurrentAction;
/** The array for the timers between recovery actions. */
private @NonNull long[] mDataStallRecoveryDelayMillisArray;
@@ -352,6 +357,7 @@ public class DataStallRecoveryManager extends Handler {
*/
private void reset() {
mIsValidNetwork = true;
+ mRecoveryTriggered = false;
mIsAttemptedAllSteps = false;
mRadioStateChangedDuringDataStall = false;
mIsAirPlaneModeEnableDuringDataStall = false;
@@ -373,15 +379,12 @@ public class DataStallRecoveryManager extends Handler {
setNetworkValidationState(isValid);
if (isValid) {
reset();
- } else {
- if (mIsValidNetwork || isRecoveryAlreadyStarted()) {
- mIsValidNetwork = false;
- if (isRecoveryNeeded(true)) {
- log("trigger data stall recovery");
- mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
- sendMessage(obtainMessage(EVENT_DO_RECOVERY));
- }
- }
+ } else if (isRecoveryNeeded(true)) {
+ // Set the network as invalid, because recovery is needed
+ mIsValidNetwork = false;
+ log("trigger data stall recovery");
+ mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
+ sendMessage(obtainMessage(EVENT_DO_RECOVERY));
}
}
@@ -455,7 +458,7 @@ public class DataStallRecoveryManager extends Handler {
* @return {@code true} if recovery already started, {@code false} recovery not started.
*/
private boolean isRecoveryAlreadyStarted() {
- return getRecoveryAction() != RECOVERY_ACTION_GET_DATA_CALL_LIST;
+ return getRecoveryAction() != RECOVERY_ACTION_GET_DATA_CALL_LIST || mRecoveryTriggered;
}
/**
@@ -468,6 +471,15 @@ public class DataStallRecoveryManager extends Handler {
}
/**
+ * Get duration time for current recovery action.
+ *
+ * @return the time duration for current recovery action.
+ */
+ private long getDurationOfCurrentRecoveryMs() {
+ return (SystemClock.elapsedRealtime() - mTimeElapsedOfCurrentAction);
+ }
+
+ /**
* Broadcast intent when data stall occurred.
*
* @param recoveryAction Send the data stall detected intent with RecoveryAction info.
@@ -542,6 +554,12 @@ public class DataStallRecoveryManager extends Handler {
private boolean isRecoveryNeeded(boolean isNeedToCheckTimer) {
logv("enter: isRecoveryNeeded()");
+ // Skip if network is invalid and recovery was not started yet
+ if (!mIsValidNetwork && !isRecoveryAlreadyStarted()) {
+ logl("skip when network still remains invalid and recovery was not started yet");
+ return false;
+ }
+
// Skip recovery if we have already attempted all steps.
if (mIsAttemptedAllSteps) {
logl("skip retrying continue recovery action");
@@ -577,7 +595,6 @@ public class DataStallRecoveryManager extends Handler {
logl("skip data stall recovery as data not connected");
return false;
}
-
return true;
}
@@ -587,39 +604,62 @@ public class DataStallRecoveryManager extends Handler {
* @param isValid true for validation passed & false for validation failed
*/
private void setNetworkValidationState(boolean isValid) {
+ boolean isLogNeeded = false;
+ int timeDuration = 0;
+ int timeDurationOfCurrentAction = 0;
+ boolean isFirstDataStall = false;
+ boolean isFirstValidationAfterDoRecovery = false;
+ @RecoveredReason int reason = getRecoveredReason(isValid);
// Validation status is true and was not data stall.
if (isValid && !mDataStalled) {
return;
}
if (!mDataStalled) {
+ // First data stall
+ isLogNeeded = true;
mDataStalled = true;
+ isFirstDataStall = true;
mDataStallStartMs = SystemClock.elapsedRealtime();
logl("data stall: start time = " + DataUtils.elapsedTimeToString(mDataStallStartMs));
- return;
- }
-
- if (!mLastActionReported) {
- @RecoveredReason int reason = getRecoveredReason(isValid);
- int timeDuration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
- logl(
- "data stall: lastaction = "
- + recoveryActionToString(mLastAction)
- + ", isRecovered = "
- + isValid
- + ", reason = "
- + recoveredReasonToString(reason)
- + ", TimeDuration = "
- + timeDuration);
- DataStallRecoveryStats.onDataStallEvent(
- mLastAction, mPhone, isValid, timeDuration, reason);
+ } else if (!mLastActionReported) {
+ // When the first validation status appears, enter this block.
+ isLogNeeded = true;
+ timeDuration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
mLastActionReported = true;
+ isFirstValidationAfterDoRecovery = true;
}
if (isValid) {
+ // When the validation passed(mobile data resume), enter this block.
+ isLogNeeded = true;
+ timeDuration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
mLastActionReported = false;
mDataStalled = false;
}
+
+ if (isLogNeeded) {
+ timeDurationOfCurrentAction =
+ (isFirstDataStall == true ? 0 : (int) getDurationOfCurrentRecoveryMs());
+ DataStallRecoveryStats.onDataStallEvent(
+ mLastAction, mPhone, isValid, timeDuration, reason,
+ isFirstValidationAfterDoRecovery, timeDurationOfCurrentAction);
+ logl(
+ "data stall: "
+ + (isFirstDataStall == true ? "start" : isValid == false ? "in process" : "end")
+ + ", lastaction="
+ + recoveryActionToString(mLastAction)
+ + ", isRecovered="
+ + isValid
+ + ", reason="
+ + recoveredReasonToString(reason)
+ + ", isFirstValidationAfterDoRecovery="
+ + isFirstValidationAfterDoRecovery
+ + ", TimeDuration="
+ + timeDuration
+ + ", TimeDurationForCurrentRecoveryAction="
+ + timeDurationOfCurrentAction);
+ }
}
/**
@@ -652,6 +692,7 @@ public class DataStallRecoveryManager extends Handler {
private void doRecovery() {
@RecoveryAction final int recoveryAction = getRecoveryAction();
final int signalStrength = mPhone.getSignalStrength().getLevel();
+ mRecoveryTriggered = true;
// DSRM used sendMessageDelayed to process the next event EVENT_DO_RECOVERY, so it need
// to check the condition if DSRM need to process the recovery action.
@@ -668,6 +709,7 @@ public class DataStallRecoveryManager extends Handler {
mLastActionReported = false;
broadcastDataStallDetected(recoveryAction);
mNetworkCheckTimerStarted = false;
+ mTimeElapsedOfCurrentAction = SystemClock.elapsedRealtime();
switch (recoveryAction) {
case RECOVERY_ACTION_GET_DATA_CALL_LIST:
diff --git a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
index 5ade0bb000..0cedb022e7 100644
--- a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
@@ -16,7 +16,10 @@
package com.android.internal.telephony.metrics;
+import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation.NetworkType;
+import android.telephony.CellSignalStrength;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
@@ -61,8 +64,16 @@ public class DataStallRecoveryStats {
boolean isOpportunistic = getIsOpportunistic(phone);
boolean isMultiSim = SimSlotState.getCurrentState().numActiveSims > 1;
- // Not use this field in Android S, so we send RECOVERED_REASON_NONE for default value.
+ // Not use these fields in Android S, so we set below parameter for default value.
int recoveryReason = 0;
+ int otherSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ int otherNetworkRegState = NetworkRegistrationInfo
+ .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+ int phoneNetworkRegState = NetworkRegistrationInfo
+ .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+ boolean isFirstValidation = false;
+ int phoneId = 0;
+ int durationMillisOfCurrentAction = 0;
TelephonyStatsLog.write(
TelephonyStatsLog.DATA_STALL_RECOVERY_REPORTED,
carrierId,
@@ -74,7 +85,13 @@ public class DataStallRecoveryStats {
band,
isRecovered,
durationMillis,
- recoveryReason);
+ recoveryReason,
+ otherSignalStrength,
+ otherNetworkRegState,
+ phoneNetworkRegState,
+ isFirstValidation,
+ phoneId,
+ durationMillisOfCurrentAction);
}
/**
@@ -85,13 +102,16 @@ public class DataStallRecoveryStats {
* @param isRecovered The data stall symptom recovered or not.
* @param durationMillis The duration from data stall symptom occurred.
* @param reason The recovered(data resume) reason.
+ * @param isFirstValidation The validation status if it's the first come after recovery.
*/
public static void onDataStallEvent(
@DataStallRecoveryManager.RecoveryAction int recoveryAction,
Phone phone,
boolean isRecovered,
int durationMillis,
- @DataStallRecoveryManager.RecoveredReason int reason) {
+ @DataStallRecoveryManager.RecoveredReason int reason,
+ boolean isFirstValidation,
+ int durationMillisOfCurrentAction) {
if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
phone = phone.getDefaultPhone();
}
@@ -111,6 +131,25 @@ public class DataStallRecoveryStats {
recoveryAction = RECOVERY_ACTION_RESET_MODEM_MAPPING;
}
+ // collect info of the other device in case of DSDS
+ int otherSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ // the number returned here matches the NetworkRegistrationState enum we have
+ int otherNetworkRegState = NetworkRegistrationInfo
+ .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+ // the number returned here matches the NetworkRegistrationState enum we have
+ int phoneNetworkRegState = NetworkRegistrationInfo
+ .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+
+ NetworkRegistrationInfo phoneRegInfo = phone.getServiceState()
+ .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ if (phoneRegInfo != null) {
+ phoneNetworkRegState = phoneRegInfo.getRegistrationState();
+ }
+
+ // reserve 0 for default value
+ int phoneId = phone.getPhoneId() + 1;
+
TelephonyStatsLog.write(
TelephonyStatsLog.DATA_STALL_RECOVERY_REPORTED,
carrierId,
@@ -122,7 +161,13 @@ public class DataStallRecoveryStats {
band,
isRecovered,
durationMillis,
- reason);
+ reason,
+ otherSignalStrength,
+ otherNetworkRegState,
+ phoneNetworkRegState,
+ isFirstValidation,
+ phoneId,
+ durationMillisOfCurrentAction);
}
/** Returns the RAT used for data (including IWLAN). */
diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
index d700831429..b2227fd8c0 100644
--- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
@@ -56,6 +56,7 @@ import android.telecom.VideoProfile.VideoState;
import android.telephony.Annotation.NetworkType;
import android.telephony.AnomalyReporter;
import android.telephony.DisconnectCause;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsReasonInfo;
@@ -73,6 +74,7 @@ import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec;
@@ -379,9 +381,8 @@ public class VoiceCallSessionStats {
proto.srvccCompleted = true;
proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
// Call RAT may have changed (e.g. IWLAN -> UMTS) due to bearer change
- proto.ratAtEnd =
- ServiceStateStats.getVoiceRat(
- mPhone, mPhone.getServiceState(), proto.bearerAtEnd);
+ updateRatAtEnd(proto, getVoiceRatWithVoNRFix(
+ mPhone, mPhone.getServiceState(), proto.bearerAtEnd));
}
break;
case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED:
@@ -436,8 +437,7 @@ public class VoiceCallSessionStats {
}
int bearer = getBearer(conn);
ServiceState serviceState = getServiceState();
- @NetworkType int rat = ServiceStateStats.getVoiceRat(mPhone, serviceState, bearer);
-
+ @NetworkType int rat = getVoiceRatWithVoNRFix(mPhone, serviceState, bearer);
VoiceCallSession proto = new VoiceCallSession();
proto.bearerAtStart = bearer;
@@ -527,15 +527,7 @@ public class VoiceCallSessionStats {
}
// Update end RAT
- @NetworkType
- int rat = ServiceStateStats.getVoiceRat(mPhone, getServiceState(), proto.bearerAtEnd);
- if (proto.ratAtEnd != rat) {
- proto.ratSwitchCount++;
- proto.ratAtEnd = rat;
- if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
- proto.lastKnownRat = rat;
- }
- }
+ updateRatAtEnd(proto, getVoiceRatWithVoNRFix(mPhone, getServiceState(), proto.bearerAtEnd));
mAtomsStorage.addVoiceCallSession(proto);
@@ -597,8 +589,7 @@ public class VoiceCallSessionStats {
proto.setupFailed = false;
// Track RAT when voice call is connected.
ServiceState serviceState = getServiceState();
- proto.ratAtConnected =
- ServiceStateStats.getVoiceRat(mPhone, serviceState, proto.bearerAtEnd);
+ proto.ratAtConnected = getVoiceRatWithVoNRFix(mPhone, serviceState, proto.bearerAtEnd);
// Reset list of codecs with the last codec at the present time. In this way, we
// track codec quality only after call is connected and not while ringing.
resetCodecList(conn);
@@ -609,19 +600,14 @@ public class VoiceCallSessionStats {
// RAT usage is not broken down by bearer. In case a CS call is made while there is IMS
// voice registration, this may be inaccurate (i.e. there could be multiple RAT in use, but
// we only pick the most feasible one).
- @NetworkType int rat = ServiceStateStats.getVoiceRat(mPhone, state);
+ @NetworkType int rat = getVoiceRatWithVoNRFix(mPhone, state,
+ VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN);
mRatUsage.add(mPhone.getCarrierId(), rat, getTimeMillis(), getConnectionIds());
for (int i = 0; i < mCallProtos.size(); i++) {
VoiceCallSession proto = mCallProtos.valueAt(i);
- rat = ServiceStateStats.getVoiceRat(mPhone, state, proto.bearerAtEnd);
- if (proto.ratAtEnd != rat) {
- proto.ratSwitchCount++;
- proto.ratAtEnd = rat;
- if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
- proto.lastKnownRat = rat;
- }
- }
+ rat = getVoiceRatWithVoNRFix(mPhone, state, proto.bearerAtEnd);
+ updateRatAtEnd(proto, rat);
proto.bandAtEnd = (rat == TelephonyManager.NETWORK_TYPE_IWLAN)
? 0
: ServiceStateStats.getBand(state);
@@ -629,6 +615,16 @@ public class VoiceCallSessionStats {
}
}
+ private void updateRatAtEnd(VoiceCallSession proto, @NetworkType int rat) {
+ if (proto.ratAtEnd != rat) {
+ proto.ratSwitchCount++;
+ proto.ratAtEnd = rat;
+ if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+ proto.lastKnownRat = rat;
+ }
+ }
+ }
+
private void finishImsCall(int id, ImsReasonInfo reasonInfo, long durationMillis) {
VoiceCallSession proto = mCallProtos.get(id);
proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
@@ -695,6 +691,43 @@ public class VoiceCallSessionStats {
return mPhone.getSignalStrength().getLevel();
}
+ /**
+ * This is a copy of ServiceStateStats.getVoiceRat(Phone, ServiceState, int) with minimum fix
+ * required for tracking EPSFB correctly.
+ */
+ @VisibleForTesting private static @NetworkType int getVoiceRatWithVoNRFix(
+ Phone phone, @Nullable ServiceState state, int bearer) {
+ if (state == null) {
+ return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ }
+ ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
+ if (bearer != VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS && imsPhone != null) {
+ @NetworkType int imsVoiceRat = imsPhone.getImsStats().getImsVoiceRadioTech();
+ @NetworkType int wwanPsRat =
+ ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_PS);
+ if (imsVoiceRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+ // If IMS is registered over WWAN but WWAN PS is not in service,
+ // fallback to WWAN CS RAT
+ boolean isImsVoiceRatValid =
+ (imsVoiceRat == TelephonyManager.NETWORK_TYPE_IWLAN
+ || wwanPsRat != TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ if (isImsVoiceRatValid) {
+ // Fix for VoNR and EPSFB, b/277906557
+ @NetworkType int oldRat = ServiceStateStats.getVoiceRat(phone, state, bearer),
+ rat = imsVoiceRat == TelephonyManager.NETWORK_TYPE_IWLAN
+ ? imsVoiceRat : wwanPsRat;
+ logd("getVoiceRatWithVoNRFix: oldRat=%d, newRat=%d", oldRat, rat);
+ return rat;
+ }
+ }
+ }
+ if (bearer == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
+ return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ } else {
+ return ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_CS);
+ }
+ }
+
/** Resets the list of codecs used for the connection with only the codec currently in use. */
private void resetCodecList(Connection conn) {
int id = getConnectionId(conn);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
index 221b2b525e..07011a30e6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
@@ -889,7 +889,7 @@ public class CarrierPrivilegesTrackerTest extends TelephonyTest {
@Test
public void testPackageDisabledAndThenEnabled() throws Exception {
// Start with certs and packages installed
- setupCarrierConfigRules(carrierConfigRuleString(getHash(CERT_1)));
+ setupSimLoadedRules(ruleWithHashOnly(getHash(CERT_1)));
setupInstalledPackages(
new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
@@ -1032,9 +1032,12 @@ public class CarrierPrivilegesTrackerTest extends TelephonyTest {
}
@Test
- public void testGetCarrierService_haveCarrierServiceWithCarrierPrivileges() throws Exception {
- // Only packages with CERT_1 have carrier privileges
- setupCarrierConfigRules(carrierConfigRuleString(getHash(CERT_1)));
+ public void testGetCarrierService_haveCarrierServiceWithSimCarrierPrivileges()
+ throws Exception {
+ // Package 1 has SIM loaded rules, making it eligible for carrier service bindings
+ setupSimLoadedRules(ruleWithHashOnly(getHash(CERT_1)));
+ // Package 2 has only carrier-config based rules, which is insufficient for carrier services
+ setupCarrierConfigRules(carrierConfigRuleString(getHash(CERT_2)));
// Setup all odd packages privileged, even packages not
setupInstalledPackages(
new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
@@ -1061,7 +1064,6 @@ public class CarrierPrivilegesTrackerTest extends TelephonyTest {
assertEquals(PACKAGE_1, carrierServicePackageName);
assertEquals(UID_1, carrierServiceUid);
-
reset(mPackageManager);
// Get CS again
carrierServicePackageName = mCarrierPrivilegesTracker.getCarrierServicePackageName();
@@ -1072,27 +1074,32 @@ public class CarrierPrivilegesTrackerTest extends TelephonyTest {
verify(mPackageManager, never()).queryIntentServices(any(), anyInt());
assertEquals(PACKAGE_1, carrierServicePackageName);
assertEquals(UID_1, carrierServiceUid);
-
}
@Test
- public void testGetCarrierService_haveCarrierServiceWithNoCarrierPrivileges() throws Exception {
- // Only packages with CERT_1 have carrier privileges
- setupCarrierConfigRules(carrierConfigRuleString(getHash(CERT_1)));
+ public void testGetCarrierService_haveCarrierServiceWithoutSimCarrierPrivileges()
+ throws Exception {
+ // Package 1 has no carrier privileges, package 2 has carrier-config based privileges, but
+ // no matching certificate on the SIM.
+ setupCarrierConfigRules(carrierConfigRuleString(getHash(CERT_2)));
// Setup all odd packages privileged, even packages not
setupInstalledPackages(
new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2),
new PackageCertInfo(PACKAGE_3, CERT_1, USER_1, UID_1));
- // One declared CarrierService which has no carrier privileges
- ResolveInfo noPrivilegeService = new ResolveInfoBuilder().setService(PACKAGE_2).build();
+ // Two declared CarrierService, only PACKAGE_1 has carrier privileges
+ ResolveInfo service1 = new ResolveInfoBuilder().setService(PACKAGE_1).build();
+ ResolveInfo service2 = new ResolveInfoBuilder().setService(PACKAGE_2).build();
// Use doReturn instead of when/thenReturn which has NPE with unknown reason
- doReturn(List.of(noPrivilegeService)).when(
- mPackageManager).queryIntentServices(any(), anyInt());
+ doReturn(List.of(service1, service2))
+ .when(mPackageManager)
+ .queryIntentServices(any(), anyInt());
when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
when(mPackageManager.getPackageUid(eq(PACKAGE_3), anyInt())).thenReturn(UID_1);
+ // Verify that neither carrier service (no privileges, or carrier-config based privileges)
+ // are accepted.
mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
String carrierServicePackageName = mCarrierPrivilegesTracker.getCarrierServicePackageName();
int carrierServiceUid = mCarrierPrivilegesTracker.getCarrierServicePackageUid();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
index 399fd9f946..7c7056092d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -1185,16 +1185,32 @@ public class DataNetworkControllerTest extends TelephonyTest {
@Test
public void testDataNetworkControllerCallback() throws Exception {
+ Field field = DataNetworkController.class.getDeclaredField(
+ "mDataNetworkControllerCallbacks");
+ field.setAccessible(true);
+ Set<DataNetworkControllerCallback> dataNetworkControllerCallbacks =
+ (Set<DataNetworkControllerCallback>) field.get(mDataNetworkControllerUT);
+
+ // Verify register callback
mDataNetworkControllerUT.registerDataNetworkControllerCallback(
mMockedDataNetworkControllerCallback);
+ TelephonyNetworkRequest request = createNetworkRequest(
+ NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ mDataNetworkControllerUT.addNetworkRequest(request);
processAllMessages();
- testSetupDataNetwork();
verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(true));
verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkConnected(any());
- mDataNetworkControllerUT.unregisterDataNetworkControllerCallback(
- mMockedDataNetworkControllerCallback);
+ int countOfCallbacks = dataNetworkControllerCallbacks.size();
+
+ // Verify unregister callback
+ mDataNetworkControllerUT.removeNetworkRequest(request);
processAllMessages();
+ getDataNetworks().get(0).tearDown(DataNetwork
+ .TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED);
+ processAllFutureMessages();
+
+ assertEquals(countOfCallbacks - 1, dataNetworkControllerCallbacks.size());
}
@Test
@@ -2587,6 +2603,64 @@ public class DataNetworkControllerTest extends TelephonyTest {
}
@Test
+ public void testSetupDataNetworkRetryFailedNetworkRequestRemovedAndAdded() throws Exception {
+ mDataNetworkControllerUT.getDataRetryManager()
+ .registerCallback(mMockedDataRetryManagerCallback);
+ setFailedSetupDataResponse(mMockedWwanDataServiceManager, DataFailCause.CONGESTION,
+ 10000, false);
+
+ TelephonyNetworkRequest firstTnr = createNetworkRequest(
+ NetworkCapabilities.NET_CAPABILITY_IMS);
+ TelephonyNetworkRequest secondTnr = createNetworkRequest(
+ NetworkCapabilities.NET_CAPABILITY_IMS);
+
+ mDataNetworkControllerUT.addNetworkRequest(firstTnr);
+ processAllMessages();
+
+ mDataNetworkControllerUT.removeNetworkRequest(firstTnr);
+ mDataNetworkControllerUT.addNetworkRequest(secondTnr);
+ processAllFutureMessages();
+
+ verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(),
+ any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+ any(), any(), anyBoolean(), any(Message.class));
+
+ ArgumentCaptor<DataRetryManager.DataSetupRetryEntry> retryEntry =
+ ArgumentCaptor.forClass(DataRetryManager.DataSetupRetryEntry.class);
+ verify(mMockedDataRetryManagerCallback, times(1))
+ .onDataNetworkSetupRetry(retryEntry.capture());
+ assertThat(retryEntry.getValue().getState()).isEqualTo(
+ DataRetryManager.DataRetryEntry.RETRY_STATE_FAILED);
+ assertThat(retryEntry.getValue().networkRequestList.size()).isEqualTo(1);
+ assertThat(retryEntry.getValue().networkRequestList.get(0)).isEqualTo(firstTnr);
+
+ DataRetryManager.DataSetupRetryEntry dataSetupRetryEntry = retryEntry.getValue();
+ logd("DataSetupRetryEntry:" + dataSetupRetryEntry);
+
+ processAllMessages();
+ processAllFutureMessages();
+
+ setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 1);
+ logd("Sending TAC_CHANGED event");
+ mDataNetworkControllerUT.obtainMessage(25/*EVENT_TAC_CHANGED*/).sendToTarget();
+ mDataNetworkControllerUT.getDataRetryManager().obtainMessage(10/*EVENT_TAC_CHANGED*/)
+ .sendToTarget();
+ processAllFutureMessages();
+
+ // TAC changes should clear the already-scheduled retry and throttling.
+ assertThat(mDataNetworkControllerUT.getDataRetryManager().isAnySetupRetryScheduled(
+ mImsCellularDataProfile, AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).isFalse();
+
+ // But DNC should re-evaluate unsatisfied request and setup IMS again.
+ verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_IMS,
+ NetworkCapabilities.NET_CAPABILITY_MMTEL);
+
+ verify(mMockedWwanDataServiceManager, times(2)).setupDataCall(anyInt(),
+ any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+ any(), any(), anyBoolean(), any(Message.class));
+ }
+
+ @Test
public void testSetupDataNetworkPermanentFailure() {
setFailedSetupDataResponse(mMockedWwanDataServiceManager, DataFailCause.PROTOCOL_ERRORS,
DataCallResponse.RETRY_DURATION_UNDEFINED, false);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
index de0998d27d..313240fd30 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
@@ -350,4 +350,71 @@ public class DataStallRecoveryManagerTest extends TelephonyTest {
assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(0);
}
}
+
+ @Test
+ public void testNextRecoveryAfterSkippingUnderPoorSignal() throws Exception {
+ // Test to validate if the next recovery action is performed in good signal
+ // soon after skipping the recovery action under poor signal condition
+ sendOnInternetDataNetworkCallback(true);
+ mDataStallRecoveryManager.setRecoveryAction(1);
+ doReturn(1).when(mSignalStrength).getLevel();
+ doReturn(mSignalStrength).when(mPhone).getSignalStrength();
+ doReturn(PhoneConstants.State.IDLE).when(mPhone).getState();
+
+ logd("Sending validation failed callback");
+ sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+ processAllMessages();
+ moveTimeForward(101);
+
+ // verify skipping recovery action under poor signal condition
+ assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(1);
+
+ // Set the signal condition to good
+ doReturn(3).when(mSignalStrength).getLevel();
+
+ logd("Sending validation failed callback");
+ sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+ processAllMessages();
+ moveTimeForward(101);
+
+ // verify next recovery action is performed under good signal condition
+ assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(3);
+ }
+
+ @Test
+ public void testDoNotRecoveryForAlwaysInvalidNetwork() throws Exception {
+ // Test to verify that recovery action is not performed for always invalid network
+ // In some lab testing scenarios, n/w validation always remain invalid.
+ sendOnInternetDataNetworkCallback(false);
+ doReturn(mSignalStrength).when(mPhone).getSignalStrength();
+ doReturn(PhoneConstants.State.IDLE).when(mPhone).getState();
+ mDataStallRecoveryManager
+ .setRecoveryAction(DataStallRecoveryManager.RECOVERY_ACTION_GET_DATA_CALL_LIST);
+
+ logd("Sending validation failed callback");
+ sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+ processAllFutureMessages();
+ moveTimeForward(101);
+
+ assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(0);
+ }
+
+ @Test
+ public void testStartTimeNotZero() throws Exception {
+ sendOnInternetDataNetworkCallback(false);
+ doReturn(mSignalStrength).when(mPhone).getSignalStrength();
+ doReturn(PhoneConstants.State.IDLE).when(mPhone).getState();
+
+ logd("Sending validation failed callback");
+ sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+ processAllFutureMessages();
+
+ for (int i = 0; i < 2; i++) {
+ sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+ logd("Sending validation failed callback");
+ processAllMessages();
+ moveTimeForward(101);
+ }
+ assertThat(mDataStallRecoveryManager.mDataStallStartMs != 0).isTrue();
+ }
}