| /* |
| * 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); |
| } |
| } |