| /* |
| * 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.settingslib.mobile; |
| |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.telephony.ServiceState; |
| import android.telephony.SignalStrength; |
| import android.telephony.SubscriptionInfo; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyCallback; |
| import android.telephony.TelephonyDisplayInfo; |
| import android.telephony.TelephonyManager; |
| import android.util.Log; |
| |
| /** |
| * Tracks the mobile signal status for the SysUI and Settings. |
| * |
| * This class is not threadsafe. All the mobile statuses monitored by this class is stored in |
| * MobileStatus. Whoever uses this class should only rely on the MobileStatusTracker#Callback |
| * to get the latest mobile statuses. Do not get mobile statues directly from |
| * MobileStatusTracker#MobileStatus. |
| */ |
| public class MobileStatusTracker { |
| private static final String TAG = "MobileStatusTracker"; |
| private final TelephonyManager mPhone; |
| private final SubscriptionInfo mSubscriptionInfo; |
| private final Callback mCallback; |
| private final MobileStatus mMobileStatus; |
| private final SubscriptionDefaults mDefaults; |
| private final Handler mReceiverHandler; |
| private final MobileTelephonyCallback mTelephonyCallback; |
| |
| private boolean mListening = false; |
| |
| /** |
| * MobileStatusTracker constructors |
| * |
| * @param phone The TelephonyManager which corresponds to the subscription being monitored. |
| * @param receiverLooper The Looper on which the callback will be invoked. |
| * @param info The subscription being monitored. |
| * @param defaults The wrapper of the SubscriptionManager. |
| * @param callback The callback to notify any changes of the mobile status, users should only |
| * use this callback to get the latest mobile status. |
| */ |
| public MobileStatusTracker(TelephonyManager phone, Looper receiverLooper, |
| SubscriptionInfo info, SubscriptionDefaults defaults, Callback callback) { |
| mPhone = phone; |
| mReceiverHandler = new Handler(receiverLooper); |
| mTelephonyCallback = new MobileTelephonyCallback(); |
| mSubscriptionInfo = info; |
| mDefaults = defaults; |
| mCallback = callback; |
| mMobileStatus = new MobileStatus(); |
| updateDataSim(); |
| mReceiverHandler.post(() -> mCallback.onMobileStatusChanged( |
| /* updateTelephony= */false, new MobileStatus(mMobileStatus))); |
| } |
| |
| public MobileTelephonyCallback getTelephonyCallback() { |
| return mTelephonyCallback; |
| } |
| |
| /** |
| * Config the MobileStatusTracker to start or stop monitoring platform signals. |
| */ |
| public void setListening(boolean listening) { |
| if (mListening == listening) { |
| return; |
| } |
| mListening = listening; |
| if (listening) { |
| mPhone.registerTelephonyCallback(mReceiverHandler::post, mTelephonyCallback); |
| } else { |
| mPhone.unregisterTelephonyCallback(mTelephonyCallback); |
| } |
| } |
| |
| public boolean isListening() { |
| return mListening; |
| } |
| |
| private void updateDataSim() { |
| int activeDataSubId = mDefaults.getActiveDataSubId(); |
| if (SubscriptionManager.isValidSubscriptionId(activeDataSubId)) { |
| mMobileStatus.dataSim = activeDataSubId == mSubscriptionInfo.getSubscriptionId(); |
| } else { |
| // There doesn't seem to be a data sim selected, however if |
| // there isn't a MobileSignalController with dataSim set, then |
| // QS won't get any callbacks and will be blank. Instead |
| // lets just assume we are the data sim (which will basically |
| // show one at random) in QS until one is selected. The user |
| // should pick one soon after, so we shouldn't be in this state |
| // for long. |
| mMobileStatus.dataSim = true; |
| } |
| } |
| |
| private void setActivity(int activity) { |
| mMobileStatus.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT |
| || activity == TelephonyManager.DATA_ACTIVITY_IN; |
| mMobileStatus.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT |
| || activity == TelephonyManager.DATA_ACTIVITY_OUT; |
| } |
| |
| public class MobileTelephonyCallback extends TelephonyCallback implements |
| TelephonyCallback.ServiceStateListener, |
| TelephonyCallback.SignalStrengthsListener, |
| TelephonyCallback.DataConnectionStateListener, |
| TelephonyCallback.DataActivityListener, |
| TelephonyCallback.CarrierNetworkListener, |
| TelephonyCallback.ActiveDataSubscriptionIdListener, |
| TelephonyCallback.DisplayInfoListener{ |
| |
| @Override |
| public void onSignalStrengthsChanged(SignalStrength signalStrength) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength |
| + ((signalStrength == null) ? "" |
| : (" level=" + signalStrength.getLevel()))); |
| } |
| mMobileStatus.signalStrength = signalStrength; |
| mCallback.onMobileStatusChanged( |
| /* updateTelephony= */true, new MobileStatus(mMobileStatus)); |
| } |
| |
| @Override |
| public void onServiceStateChanged(ServiceState state) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onServiceStateChanged voiceState=" |
| + (state == null ? "" : state.getState()) |
| + " dataState=" + (state == null ? "" : state.getDataRegistrationState())); |
| } |
| mMobileStatus.serviceState = state; |
| mCallback.onMobileStatusChanged( |
| /* updateTelephony= */true, new MobileStatus(mMobileStatus)); |
| } |
| |
| @Override |
| public void onDataConnectionStateChanged(int state, int networkType) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onDataConnectionStateChanged: state=" + state |
| + " type=" + networkType); |
| } |
| mMobileStatus.dataState = state; |
| mCallback.onMobileStatusChanged( |
| /* updateTelephony= */true, new MobileStatus(mMobileStatus)); |
| } |
| |
| @Override |
| public void onDataActivity(int direction) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onDataActivity: direction=" + direction); |
| } |
| setActivity(direction); |
| mCallback.onMobileStatusChanged( |
| /* updateTelephony= */false, new MobileStatus(mMobileStatus)); |
| } |
| |
| @Override |
| public void onCarrierNetworkChange(boolean active) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onCarrierNetworkChange: active=" + active); |
| } |
| mMobileStatus.carrierNetworkChangeMode = active; |
| mCallback.onMobileStatusChanged( |
| /* updateTelephony= */true, new MobileStatus(mMobileStatus)); |
| } |
| |
| @Override |
| public void onActiveDataSubscriptionIdChanged(int subId) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onActiveDataSubscriptionIdChanged: subId=" + subId); |
| } |
| updateDataSim(); |
| mCallback.onMobileStatusChanged( |
| /* updateTelephony= */true, new MobileStatus(mMobileStatus)); |
| } |
| |
| @Override |
| public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onDisplayInfoChanged: telephonyDisplayInfo=" + telephonyDisplayInfo); |
| } |
| mMobileStatus.telephonyDisplayInfo = telephonyDisplayInfo; |
| mCallback.onMobileStatusChanged( |
| /* updateTelephony= */ true, new MobileStatus(mMobileStatus)); |
| } |
| } |
| |
| /** |
| * Wrapper class of the SubscriptionManager, for mock testing purpose |
| */ |
| public static class SubscriptionDefaults { |
| public int getDefaultVoiceSubId() { |
| return SubscriptionManager.getDefaultVoiceSubscriptionId(); |
| } |
| |
| public int getDefaultDataSubId() { |
| return SubscriptionManager.getDefaultDataSubscriptionId(); |
| } |
| |
| public int getActiveDataSubId() { |
| return SubscriptionManager.getActiveDataSubscriptionId(); |
| } |
| } |
| |
| /** |
| * Wrapper class which contains all the mobile status tracked by MobileStatusTracker. |
| */ |
| public static class MobileStatus { |
| public boolean activityIn; |
| public boolean activityOut; |
| public boolean dataSim; |
| public boolean carrierNetworkChangeMode; |
| public int dataState = TelephonyManager.DATA_DISCONNECTED; |
| public ServiceState serviceState; |
| public SignalStrength signalStrength; |
| public TelephonyDisplayInfo telephonyDisplayInfo = |
| new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN, |
| TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false); |
| |
| /** |
| * Empty constructor |
| */ |
| public MobileStatus() { } |
| |
| /** |
| * Copy constructors |
| * |
| * @param status Source MobileStatus |
| */ |
| public MobileStatus(MobileStatus status) { |
| copyFrom(status); |
| } |
| |
| protected void copyFrom(MobileStatus status) { |
| activityIn = status.activityIn; |
| activityOut = status.activityOut; |
| dataSim = status.dataSim; |
| carrierNetworkChangeMode = status.carrierNetworkChangeMode; |
| dataState = status.dataState; |
| // We don't do deep copy for the below members since they may be Mockito instances. |
| serviceState = status.serviceState; |
| signalStrength = status.signalStrength; |
| telephonyDisplayInfo = status.telephonyDisplayInfo; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| return builder.append("[activityIn=").append(activityIn).append(',') |
| .append("activityOut=").append(activityOut).append(',') |
| .append("dataSim=").append(dataSim).append(',') |
| .append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode).append(',') |
| .append("dataState=").append(dataState).append(',') |
| .append("serviceState=").append(serviceState == null ? "" |
| : "mVoiceRegState=" + serviceState.getState() + "(" |
| + ServiceState.rilServiceStateToString(serviceState.getState()) |
| + ")" + ", mDataRegState=" + serviceState.getDataRegState() + "(" |
| + ServiceState.rilServiceStateToString( |
| serviceState.getDataRegState()) + ")") |
| .append(',') |
| .append("signalStrength=").append(signalStrength == null ? "" |
| : signalStrength.getLevel()).append(',') |
| .append("telephonyDisplayInfo=").append(telephonyDisplayInfo == null ? "" |
| : telephonyDisplayInfo.toString()).append(']').toString(); |
| } |
| } |
| |
| /** |
| * Callback for notifying any changes of the mobile status. |
| * |
| * This callback will always be invoked on the receiverLooper which must be specified when |
| * MobileStatusTracker is constructed. |
| */ |
| public interface Callback { |
| /** |
| * Notify the mobile status has been updated. |
| * |
| * @param updateTelephony Whether needs to update other Telephony related parameters, this |
| * is only used by SysUI. |
| * @param mobileStatus Holds the latest mobile statuses |
| */ |
| void onMobileStatusChanged(boolean updateTelephony, MobileStatus mobileStatus); |
| } |
| } |