blob: 47b7d2009041d0c7a5d5c116b619d9c79001540c [file] [log] [blame]
/*
* Copyright (C) 2020 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.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.PersistableBundle;
import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation;
import android.telephony.Annotation.ApnType;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.data.ApnSetting;
import android.telephony.data.ThrottleStatus;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RetryManager;
import com.android.telephony.Rlog;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Data throttler tracks the throttling status of the data network and notifies registrants when
* there are changes. The throttler is per phone and per transport type.
*/
public class DataThrottler extends Handler {
private static final String TAG = DataThrottler.class.getSimpleName();
private static final int EVENT_SET_RETRY_TIME = 1;
private static final int EVENT_CARRIER_CONFIG_CHANGED = 2;
private static final int EVENT_RESET = 3;
private static final int EVENT_AIRPLANE_MODE_CHANGED = 4;
private static final int EVENT_TRACING_AREA_CODE_CHANGED = 5;
private final Phone mPhone;
private final int mSlotIndex;
private final @AccessNetworkConstants.TransportType int mTransportType;
private boolean mResetWhenAreaCodeChanged = false;
/**
* Callbacks that report the apn throttle status.
*/
private final List<DataThrottler.Callback> mCallbacks = new ArrayList<>();
/**
* Keeps track of detailed information of the throttle status that is meant to be
* reported to other components.
*/
private final Map<Integer, ThrottleStatus> mThrottleStatus = new ConcurrentHashMap<>();
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
if (mPhone.getPhoneId() == intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX,
SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
if (intent.getBooleanExtra(
CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
// Ignore the rebroadcast one to prevent multiple carrier config changed
// event during boot up.
return;
}
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
sendEmptyMessage(EVENT_CARRIER_CONFIG_CHANGED);
}
}
}
}
};
public DataThrottler(Phone phone, int transportType) {
super(null, false);
mPhone = phone;
mSlotIndex = phone.getPhoneId();
mTransportType = transportType;
IntentFilter filter = new IntentFilter();
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
mPhone.getContext().registerReceiver(mBroadcastReceiver, filter, null, mPhone);
mPhone.getServiceStateTracker().registerForAirplaneModeChanged(this,
EVENT_AIRPLANE_MODE_CHANGED, null);
mPhone.getServiceStateTracker().registerForAreaCodeChanged(this,
EVENT_TRACING_AREA_CODE_CHANGED, null);
}
@Override
public void handleMessage(Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_SET_RETRY_TIME:
int apnTypes = msg.arg1;
int newRequestType = msg.arg2;
long retryElapsedTime = (long) msg.obj;
setRetryTimeInternal(apnTypes, retryElapsedTime, newRequestType);
break;
case EVENT_CARRIER_CONFIG_CHANGED:
onCarrierConfigChanged();
break;
case EVENT_RESET:
resetInternal();
break;
case EVENT_AIRPLANE_MODE_CHANGED:
ar = (AsyncResult) msg.obj;
if (!(Boolean) ar.result) {
resetInternal();
}
break;
case EVENT_TRACING_AREA_CODE_CHANGED:
if (mResetWhenAreaCodeChanged) {
resetInternal();
}
break;
}
}
@NonNull
private PersistableBundle getCarrierConfig() {
CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configManager != null) {
// If an invalid subId is used, this bundle will contain default values.
PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
if (config != null) {
return config;
}
}
// Return static default defined in CarrierConfigManager.
return CarrierConfigManager.getDefaultConfig();
}
private void onCarrierConfigChanged() {
PersistableBundle config = getCarrierConfig();
mResetWhenAreaCodeChanged = config.getBoolean(
CarrierConfigManager.KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
}
/**
* Set the retry time and handover failure mode for the give APN types.
*
* @param apnTypes APN types
* @param retryElapsedTime The elapsed time that data connection for APN types should not be
* retried. {@link RetryManager#NO_SUGGESTED_RETRY_DELAY} indicates throttling does not exist.
* {@link RetryManager#NO_RETRY} indicates retry should never happen.
*/
public void setRetryTime(@ApnType int apnTypes, @ElapsedRealtimeLong long retryElapsedTime,
@DcTracker.RequestNetworkType int newRequestType) {
sendMessage(obtainMessage(EVENT_SET_RETRY_TIME, apnTypes, newRequestType,
retryElapsedTime));
}
/**
* Set the retry time and handover failure mode for the give APN types. This is running on the
* handler thread.
*
* @param apnTypes APN types
* @param retryElapsedTime The elapsed time that data connection for APN types should not be
* retried. {@link RetryManager#NO_SUGGESTED_RETRY_DELAY} indicates throttling does not exist.
* {@link RetryManager#NO_RETRY} indicates retry should never happen.
*/
private void setRetryTimeInternal(@ApnType int apnTypes, @ElapsedRealtimeLong
long retryElapsedTime, @DcTracker.RequestNetworkType int newRequestType) {
if (retryElapsedTime < 0) {
retryElapsedTime = RetryManager.NO_SUGGESTED_RETRY_DELAY;
}
List<ThrottleStatus> changedStatuses = new ArrayList<>();
while (apnTypes != 0) {
//Extract the least significant bit.
int apnType = apnTypes & -apnTypes;
//Update the apn throttle status
ThrottleStatus newStatus = createStatus(apnType, retryElapsedTime, newRequestType);
ThrottleStatus oldStatus = mThrottleStatus.get(apnType);
//Check to see if there is a change that needs to be applied
if (!newStatus.equals(oldStatus)) {
//Mark as changed status
changedStatuses.add(newStatus);
//Put the new status in the temp space
mThrottleStatus.put(apnType, newStatus);
}
//Remove the least significant bit.
apnTypes &= apnTypes - 1;
}
if (changedStatuses.size() > 0) {
sendThrottleStatusChanged(changedStatuses);
}
}
/**
* Get the earliest retry time for the given APN type. The time is the system's elapse time.
*
* Note the DataThrottler is running phone process's main thread, which is most of the telephony
* components running on. Calling this method from other threads might run into race conditions.
*
* @param apnType APN type
* @return The earliest retry time for APN type. The time is the system's elapse time.
* {@link RetryManager#NO_SUGGESTED_RETRY_DELAY} indicates there is no throttling for given APN
* type, {@link RetryManager#NO_RETRY} indicates retry should never happen.
*/
@ElapsedRealtimeLong
public long getRetryTime(@ApnType int apnType) {
// This is the workaround to handle the mistake that
// ApnSetting.TYPE_DEFAULT = ApnTypes.DEFAULT | ApnTypes.HIPRI.
if (apnType == ApnSetting.TYPE_DEFAULT) {
apnType &= ~(ApnSetting.TYPE_HIPRI);
}
ThrottleStatus status = mThrottleStatus.get(apnType);
if (status != null) {
if (status.getThrottleType() == ThrottleStatus.THROTTLE_TYPE_NONE) {
return RetryManager.NO_SUGGESTED_RETRY_DELAY;
} else {
return status.getThrottleExpiryTimeMillis();
}
}
return RetryManager.NO_SUGGESTED_RETRY_DELAY;
}
/**
* Resets retry times for all APNs to {@link RetryManager.NO_SUGGESTED_RETRY_DELAY}.
*/
public void reset() {
sendEmptyMessage(EVENT_RESET);
}
/**
* Resets retry times for all APNs to {@link RetryManager.NO_SUGGESTED_RETRY_DELAY}.
*/
private void resetInternal() {
final List<Integer> apnTypes = new ArrayList<>();
for (ThrottleStatus throttleStatus : mThrottleStatus.values()) {
apnTypes.add(throttleStatus.getApnType());
}
for (int apnType : apnTypes) {
setRetryTime(apnType, RetryManager.NO_SUGGESTED_RETRY_DELAY,
DcTracker.REQUEST_TYPE_NORMAL);
}
}
private ThrottleStatus createStatus(@Annotation.ApnType int apnType, long retryElapsedTime,
@DcTracker.RequestNetworkType int newRequestType) {
ThrottleStatus.Builder builder = new ThrottleStatus.Builder();
if (retryElapsedTime == RetryManager.NO_SUGGESTED_RETRY_DELAY) {
builder
.setNoThrottle()
.setRetryType(getRetryType(newRequestType));
} else if (retryElapsedTime == RetryManager.NO_RETRY) {
builder
.setThrottleExpiryTimeMillis(RetryManager.NO_RETRY)
.setRetryType(ThrottleStatus.RETRY_TYPE_NONE);
} else {
builder
.setThrottleExpiryTimeMillis(retryElapsedTime)
.setRetryType(getRetryType(newRequestType));
}
return builder
.setSlotIndex(mSlotIndex)
.setTransportType(mTransportType)
.setApnType(apnType)
.build();
}
private static int getRetryType(@DcTracker.RequestNetworkType int newRequestType) {
if (newRequestType == DcTracker.REQUEST_TYPE_NORMAL) {
return ThrottleStatus.RETRY_TYPE_NEW_CONNECTION;
}
if (newRequestType == DcTracker.REQUEST_TYPE_HANDOVER) {
return ThrottleStatus.RETRY_TYPE_HANDOVER;
}
loge("createStatus: Unknown requestType=" + newRequestType);
return ThrottleStatus.RETRY_TYPE_NEW_CONNECTION;
}
private void sendThrottleStatusChanged(List<ThrottleStatus> statuses) {
synchronized (mCallbacks) {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onThrottleStatusChanged(statuses);
}
}
}
private static void loge(String s) {
Rlog.e(TAG, s);
}
/**
* Reports changes to apn throttle statuses.
*
* Note: All statuses are sent when first registered.
*
* @param callback status changes callback
*/
public void registerForThrottleStatusChanges(DataThrottler.Callback callback) {
synchronized (mCallbacks) {
//Only add if it's not there already
if (!mCallbacks.contains(callback)) {
//Report everything the first time
List<ThrottleStatus> throttleStatuses =
new ArrayList<>(mThrottleStatus.values());
callback.onThrottleStatusChanged(throttleStatuses);
mCallbacks.add(callback);
}
}
}
/**
* Unregister the callback
* @param callback the callback to unregister
*/
public void unregisterForThrottleStatusChanges(DataThrottler.Callback callback) {
synchronized (mCallbacks) {
mCallbacks.remove(callback);
}
}
/**
* Callback for when throttle statuses change
*/
public interface Callback {
/**
* Called whenever the throttle status of an APN has changed.
*
* Note: Called with all statuses when first registered.
*
* @param throttleStatuses the status changes
*/
void onThrottleStatusChanged(List<ThrottleStatus> throttleStatuses);
}
}