/* * 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; import android.annotation.NonNull; import android.os.Handler; import android.os.Message; import android.os.PersistableBundle; import android.os.Registrant; import android.os.RegistrantList; import android.telephony.AnomalyReporter; import android.telephony.CarrierConfigManager; import android.telephony.ServiceState; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; import android.util.IndentingPrintWriter; import android.util.LocalLog; import android.util.Pair; import com.android.internal.telephony.flags.FeatureFlags; import com.android.telephony.Rlog; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Set; import java.util.UUID; import javax.sip.InvalidArgumentException; /** * The DisplayInfoController updates and broadcasts all changes to {@link TelephonyDisplayInfo}. * It manages all the information necessary for display purposes. Clients can register for display * info changes via {@link #registerForTelephonyDisplayInfoChanged} and obtain the current * TelephonyDisplayInfo via {@link #getTelephonyDisplayInfo}. */ public class DisplayInfoController extends Handler { private final String mLogTag; private final LocalLog mLocalLog = new LocalLog(128); private static final Set> VALID_DISPLAY_INFO_SET = Set.of( // LTE Pair.create(TelephonyManager.NETWORK_TYPE_LTE, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA), Pair.create(TelephonyManager.NETWORK_TYPE_LTE, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO), Pair.create(TelephonyManager.NETWORK_TYPE_LTE, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA), Pair.create(TelephonyManager.NETWORK_TYPE_LTE, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED), // NR Pair.create(TelephonyManager.NETWORK_TYPE_NR, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) ); /** Event for service state changed (roaming). */ private static final int EVENT_SERVICE_STATE_CHANGED = 1; /** Event for carrier config changed. */ private static final int EVENT_CARRIER_CONFIG_CHANGED = 2; @NonNull private final Phone mPhone; @NonNull private final NetworkTypeController mNetworkTypeController; @NonNull private final RegistrantList mTelephonyDisplayInfoChangedRegistrants = new RegistrantList(); @NonNull private final FeatureFlags mFeatureFlags; @NonNull private TelephonyDisplayInfo mTelephonyDisplayInfo; @NonNull private ServiceState mServiceState; @NonNull private PersistableBundle mConfigs; public DisplayInfoController(@NonNull Phone phone, @NonNull FeatureFlags featureFlags) { mPhone = phone; mFeatureFlags = featureFlags; mLogTag = "DIC-" + mPhone.getPhoneId(); mServiceState = mPhone.getServiceStateTracker().getServiceState(); mConfigs = new PersistableBundle(); try { mConfigs = mPhone.getContext().getSystemService(CarrierConfigManager.class) .getConfigForSubId(mPhone.getSubId(), CarrierConfigManager.KEY_SHOW_ROAMING_INDICATOR_BOOL); } catch (Exception ignored) { // CarrierConfigLoader might not be available yet. // Once it's available, configs will be updated through the listener. } mPhone.getServiceStateTracker() .registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null); mPhone.getContext().getSystemService(CarrierConfigManager.class) .registerCarrierConfigChangeListener(Runnable::run, (slotIndex, subId, carrierId, specificCarrierId) -> { if (slotIndex == mPhone.getPhoneId()) { obtainMessage(EVENT_CARRIER_CONFIG_CHANGED).sendToTarget(); } }); mTelephonyDisplayInfo = new TelephonyDisplayInfo( TelephonyManager.NETWORK_TYPE_UNKNOWN, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false); mNetworkTypeController = new NetworkTypeController(phone, this, featureFlags); // EVENT_UPDATE will transition from DefaultState to the current state // and update the TelephonyDisplayInfo based on the current state. mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); } /** * @return the current TelephonyDisplayInfo */ @NonNull public TelephonyDisplayInfo getTelephonyDisplayInfo() { return mTelephonyDisplayInfo; } /** * Update TelephonyDisplayInfo based on network type and override network type, received from * NetworkTypeController. */ public void updateTelephonyDisplayInfo() { TelephonyDisplayInfo newDisplayInfo = new TelephonyDisplayInfo( mNetworkTypeController.getDataNetworkType(), mNetworkTypeController.getOverrideNetworkType(), isRoaming()); if (!newDisplayInfo.equals(mTelephonyDisplayInfo)) { logl("TelephonyDisplayInfo changed from " + mTelephonyDisplayInfo + " to " + newDisplayInfo); validateDisplayInfo(newDisplayInfo); mTelephonyDisplayInfo = newDisplayInfo; mTelephonyDisplayInfoChangedRegistrants.notifyRegistrants(); mPhone.notifyDisplayInfoChanged(mTelephonyDisplayInfo); } } /** * Determine the roaming status for icon display only. * If this is {@code true}, the roaming indicator will be shown, and if this is {@code false}, * the roaming indicator will not be shown. * To get the actual roaming status, use {@link ServiceState#getRoaming()} instead. * * @return Whether the device is considered roaming for display purposes. */ private boolean isRoaming() { boolean roaming = mServiceState.getRoaming(); if (roaming && mFeatureFlags.hideRoamingIcon() && !mConfigs.getBoolean(CarrierConfigManager.KEY_SHOW_ROAMING_INDICATOR_BOOL)) { logl("Override roaming for display due to carrier configs."); roaming = false; } return roaming; } /** * Validate the display info and trigger anomaly report if needed. * * @param displayInfo The display info to validate. */ private void validateDisplayInfo(@NonNull TelephonyDisplayInfo displayInfo) { try { if (displayInfo.getNetworkType() == TelephonyManager.NETWORK_TYPE_LTE_CA) { throw new InvalidArgumentException("LTE_CA is not a valid network type."); } if (displayInfo.getNetworkType() < TelephonyManager.NETWORK_TYPE_UNKNOWN && displayInfo.getNetworkType() > TelephonyManager.NETWORK_TYPE_NR) { throw new InvalidArgumentException("Invalid network type " + displayInfo.getNetworkType()); } if (displayInfo.getOverrideNetworkType() != TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE && !VALID_DISPLAY_INFO_SET.contains(Pair.create(displayInfo.getNetworkType(), displayInfo.getOverrideNetworkType()))) { throw new InvalidArgumentException("Invalid network type override " + TelephonyDisplayInfo.overrideNetworkTypeToString( displayInfo.getOverrideNetworkType()) + " for " + TelephonyManager.getNetworkTypeName( displayInfo.getNetworkType())); } } catch (InvalidArgumentException e) { logel(e.getMessage()); AnomalyReporter.reportAnomaly(UUID.fromString("3aa92a2c-94ed-46a0-a744-d6b1dfec2a56"), e.getMessage(), mPhone.getCarrierId()); } } /** * Register for TelephonyDisplayInfo changed. * @param h Handler to notify * @param what msg.what when the message is delivered * @param obj msg.obj when the message is delivered */ public void registerForTelephonyDisplayInfoChanged(Handler h, int what, Object obj) { Registrant r = new Registrant(h, what, obj); mTelephonyDisplayInfoChangedRegistrants.add(r); } /** * Unregister for TelephonyDisplayInfo changed. * @param h Handler to notify */ public void unregisterForTelephonyDisplayInfoChanged(Handler h) { mTelephonyDisplayInfoChangedRegistrants.remove(h); } @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case EVENT_SERVICE_STATE_CHANGED: mServiceState = mPhone.getServiceStateTracker().getServiceState(); log("ServiceState updated, isRoaming=" + mServiceState.getRoaming()); updateTelephonyDisplayInfo(); break; case EVENT_CARRIER_CONFIG_CHANGED: mConfigs = mPhone.getContext().getSystemService(CarrierConfigManager.class) .getConfigForSubId(mPhone.getSubId(), CarrierConfigManager.KEY_SHOW_ROAMING_INDICATOR_BOOL); log("Carrier configs updated: " + mConfigs); updateTelephonyDisplayInfo(); break; } } /** * Log debug messages. * @param s debug messages */ private void log(@NonNull String s) { Rlog.d(mLogTag, s); } /** * Log error messages. * @param s error messages */ private void loge(@NonNull String s) { Rlog.e(mLogTag, s); } /** * Log debug messages and also log into the local log. * @param s debug messages */ private void logl(@NonNull String s) { log(s); mLocalLog.log(s); } /** * Log error messages and also log into the local log. * @param s debug messages */ private void logel(@NonNull String s) { loge(s); mLocalLog.log(s); } /** * Dump the current state. */ public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); pw.println("DisplayInfoController:"); pw.println(" mPhone=" + mPhone.getPhoneName()); pw.println(" mTelephonyDisplayInfo=" + mTelephonyDisplayInfo.toString()); pw.flush(); pw.println("Local logs:"); pw.increaseIndent(); mLocalLog.dump(fd, pw, args); pw.decreaseIndent(); pw.println(" ***************************************"); mNetworkTypeController.dump(fd, pw, args); pw.flush(); } }