From deca9ea898151926fdb54b80b04a949b0e6f0e3b Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Mon, 22 Mar 2021 15:29:20 -0400 Subject: [PATCH] Create central TelephonyListenerManager. The TelephonyManager doesn't like have "too many" listeners registered on int at any given time. It will actually throw exceptions when this happens. To lighten the load from the SystemUI side, TelephonyListenerManager now ensures that only one listener is ever subscribed at any point. SystemUI can now use this class instead, piggy-backing on the possibly already subscribed listener to retrieve the events it cares about. Also, use Executors in CarrierTextController instead of Handlers. Bug: 179775696 Test: atest SystemUITests Change-Id: I626e80a91396161022e1fc6387598521f77bf4fc --- .../src/com/android/keyguard/CarrierText.java | 7 +- .../keyguard/CarrierTextController.java | 77 ++++--- .../keyguard/KeyguardUpdateMonitor.java | 15 +- .../src/com/android/systemui/Dependency.java | 3 + .../globalactions/GlobalActionsDialog.java | 11 +- .../policy/NetworkControllerImpl.java | 45 ++-- .../policy/UserSwitcherController.java | 18 +- .../systemui/telephony/TelephonyCallback.java | 93 ++++++++ .../telephony/TelephonyListenerManager.java | 103 +++++++++ .../keyguard/CarrierTextControllerTest.java | 84 +++---- .../keyguard/KeyguardUpdateMonitorTest.java | 5 +- .../GlobalActionsDialogTest.java | 6 +- .../policy/NetworkControllerBaseTest.java | 7 +- .../policy/NetworkControllerDataTest.java | 3 +- .../policy/NetworkControllerSignalTest.java | 15 +- .../telephony/TelephonyCallbackTest.java | 102 +++++++++ .../TelephonyListenerManagerTest.java | 209 ++++++++++++++++++ .../util/concurrency/FakeExecutor.java | 7 + .../util/concurrency/FakeExecutorTest.java | 14 ++ 19 files changed, 690 insertions(+), 134 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java create mode 100644 packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java index f6b03c1fa0131..f1c044c0b5e6c 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java @@ -18,6 +18,7 @@ package com.android.keyguard; import android.content.Context; import android.content.res.TypedArray; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.method.SingleLineTransformationMethod; import android.util.AttributeSet; @@ -26,6 +27,7 @@ import android.widget.TextView; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.telephony.TelephonyListenerManager; import java.util.Locale; @@ -85,7 +87,10 @@ public class CarrierText extends TextView { mSeparator = getResources().getString( com.android.internal.R.string.kg_text_message_separator); mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode, - mShowMissingSim); + mShowMissingSim, mContext.getSystemService(TelephonyManager.class), + Dependency.get(TelephonyListenerManager.class), + Dependency.get(Dependency.MAIN_EXECUTOR), + Dependency.get(Dependency.BACKGROUND_EXECUTOR)); mShouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive(); setSelected(mShouldMarquee); // Allow marquee to work. } diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java index d52a25139ce75..a85d96f133b1d 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java @@ -16,20 +16,15 @@ package com.android.keyguard; -import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; -import static android.telephony.PhoneStateListener.LISTEN_NONE; - import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.wifi.WifiManager; -import android.os.Handler; -import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; @@ -40,11 +35,14 @@ import androidx.annotation.VisibleForTesting; import com.android.settingslib.WirelessUtils; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.telephony.TelephonyListenerManager; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; @@ -59,8 +57,6 @@ public class CarrierTextController { private static final String TAG = "CarrierTextController"; private final boolean mIsEmergencyCallCapable; - private final Handler mMainHandler; - private final Handler mBgHandler; private boolean mTelephonyCapable; private boolean mShowMissingSim; private boolean mShowAirplaneMode; @@ -73,6 +69,10 @@ public class CarrierTextController { @Nullable // Check for nullability before dispatching private CarrierTextCallback mCarrierTextCallback; private Context mContext; + private final TelephonyManager mTelephonyManager; + private final TelephonyListenerManager mTelephonyListenerManager; + private final Executor mMainExecutor; + private final Executor mBgExecutor; private CharSequence mSeparator; private WakefulnessLifecycle mWakefulnessLifecycle; private final WakefulnessLifecycle.Observer mWakefulnessObserver = @@ -129,14 +129,13 @@ public class CarrierTextController { } }; - private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + private final ActiveDataSubscriptionIdListener mPhoneStateListener = + new ActiveDataSubscriptionIdListener() { @Override public void onActiveDataSubscriptionIdChanged(int subId) { - mActiveMobileDataSubscription = subId; - if (mNetworkSupported.get() && mCarrierTextCallback != null) { - updateCarrierText(); - } + if (mNetworkSupported.get() && mCarrierTextCallback != null) { + updateCarrierText(); + } } }; @@ -163,9 +162,15 @@ public class CarrierTextController { * @param separator Separator between different parts of the text */ public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode, - boolean showMissingSim) { + boolean showMissingSim, TelephonyManager telephonyManager, + TelephonyListenerManager telephonyListenerManager, + @Main Executor mainExecutor, @Background Executor bgExecutor) { mContext = context; - mIsEmergencyCallCapable = getTelephonyManager().isVoiceCapable(); + mTelephonyManager = telephonyManager; + mTelephonyListenerManager = telephonyListenerManager; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; + mIsEmergencyCallCapable = mTelephonyManager.isVoiceCapable(); mShowAirplaneMode = showAirplaneMode; mShowMissingSim = showMissingSim; @@ -173,12 +178,10 @@ public class CarrierTextController { mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mSeparator = separator; mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class); - mSimSlotsNumber = getTelephonyManager().getSupportedModemCount(); + mSimSlotsNumber = mTelephonyManager.getSupportedModemCount(); mSimErrorState = new boolean[mSimSlotsNumber]; - mMainHandler = Dependency.get(Dependency.MAIN_HANDLER); - mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); - mBgHandler.post(() -> { + mBgExecutor.execute(() -> { boolean supported = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); if (supported && mNetworkSupported.compareAndSet(false, supported)) { @@ -208,7 +211,7 @@ public class CarrierTextController { CharSequence carrierTextForSimIOError = getCarrierTextForSimState( TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier); // mSimErrorState has the state of each sim indexed by slotID. - for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) { + for (int index = 0; index < mTelephonyManager.getActiveModemCount(); index++) { if (!mSimErrorState[index]) { continue; } @@ -247,26 +250,24 @@ public class CarrierTextController { * This call will always be processed in a background thread. */ private void handleSetListening(CarrierTextCallback callback) { - TelephonyManager telephonyManager = getTelephonyManager(); if (callback != null) { mCarrierTextCallback = callback; if (mNetworkSupported.get()) { // Keyguard update monitor expects callbacks from main thread - mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback)); + mMainExecutor.execute(() -> mKeyguardUpdateMonitor.registerCallback(mCallback)); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); - telephonyManager.listen(mPhoneStateListener, - LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); } else { // Don't listen and clear out the text when the device isn't a phone. - mMainHandler.post(() -> callback.updateCarrierInfo( + mMainExecutor.execute(() -> callback.updateCarrierInfo( new CarrierTextCallbackInfo("", null, false, null) )); } } else { mCarrierTextCallback = null; - mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback)); + mMainExecutor.execute(() -> mKeyguardUpdateMonitor.removeCallback(mCallback)); mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); - telephonyManager.listen(mPhoneStateListener, LISTEN_NONE); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); } } @@ -277,7 +278,7 @@ public class CarrierTextController { * @param callback Callback to provide text updates */ public void setListening(CarrierTextCallback callback) { - mBgHandler.post(() -> handleSetListening(callback)); + mBgExecutor.execute(() -> handleSetListening(callback)); } protected List getSubscriptionInfo() { @@ -400,7 +401,7 @@ public class CarrierTextController { protected void postToCallback(CarrierTextCallbackInfo info) { final CarrierTextCallback callback = mCarrierTextCallback; if (callback != null) { - mMainHandler.post(() -> callback.updateCarrierInfo(info)); + mMainExecutor.execute(() -> callback.updateCarrierInfo(info)); } } @@ -620,14 +621,25 @@ public class CarrierTextController { public static class Builder { private final Context mContext; private final String mSeparator; + private final TelephonyManager mTelephonyManager; + private final TelephonyListenerManager mTelephonyListenerManager; + private final Executor mMainExecutor; + private final Executor mBgExecutor; private boolean mShowAirplaneMode; private boolean mShowMissingSim; @Inject - public Builder(Context context, @Main Resources resources) { + public Builder(Context context, @Main Resources resources, + TelephonyManager telephonyManager, + TelephonyListenerManager telephonyListenerManager, @Main Executor mainExecutor, + @Background Executor bgExecutor) { mContext = context; mSeparator = resources.getString( com.android.internal.R.string.kg_text_message_separator); + mTelephonyManager = telephonyManager; + mTelephonyListenerManager = telephonyListenerManager; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; } @@ -643,7 +655,8 @@ public class CarrierTextController { public CarrierTextController build() { return new CarrierTextController( - mContext, mSeparator, mShowAirplaneMode, mShowMissingSim); + mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mTelephonyManager, + mTelephonyListenerManager, mMainExecutor, mBgExecutor); } } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 69e6ed043172d..9abc1e7adf864 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -22,7 +22,6 @@ import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.ACTION_USER_STOPPED; import static android.content.Intent.ACTION_USER_UNLOCKED; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; -import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; @@ -76,11 +75,11 @@ import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.telephony.CarrierConfigManager; -import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.Log; import android.util.SparseArray; @@ -107,6 +106,7 @@ import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; import com.android.systemui.util.RingerModeTracker; @@ -290,6 +290,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mDeviceInteractive; private boolean mScreenOn; private SubscriptionManager mSubscriptionManager; + private final TelephonyListenerManager mTelephonyListenerManager; private List mSubscriptionInfo; private TrustManager mTrustManager; private UserManager mUserManager; @@ -358,7 +359,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab }; @VisibleForTesting - public PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + public TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener = + new TelephonyCallback.ActiveDataSubscriptionIdListener() { @Override public void onActiveDataSubscriptionIdChanged(int subId) { mActiveMobileDataSubscription = subId; @@ -1614,9 +1616,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab StatusBarStateController statusBarStateController, LockPatternUtils lockPatternUtils, AuthController authController, + TelephonyListenerManager telephonyListenerManager, FeatureFlags featureFlags) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); + mTelephonyListenerManager = telephonyListenerManager; mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged); mBackgroundExecutor = backgroundExecutor; @@ -1865,8 +1869,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if (mTelephonyManager != null) { - mTelephonyManager.listen(mPhoneStateListener, - LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); // Set initial sim states values. for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) { int state = mTelephonyManager.getSimState(slot); @@ -3123,7 +3126,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); if (telephony != null) { - telephony.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); } mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 06b486ec43d0b..a686fc086b401 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -120,6 +120,7 @@ import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; @@ -350,6 +351,7 @@ public class Dependency { @Inject Lazy mMediaOutputDialogFactory; @Inject Lazy mDeviceConfigProxy; @Inject Lazy mNavbarButtonsControllerLazy; + @Inject Lazy mTelephonyListenerManager; @Inject public Dependency() { @@ -545,6 +547,7 @@ public class Dependency { mProviders.put(StatusBar.class, mStatusBar::get); mProviders.put(ProtoTracer.class, mProtoTracer::get); mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get); + mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 553e5a7c6d7dd..18627189f1884 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -73,8 +73,8 @@ import android.provider.Settings; import android.service.dreams.IDreamManager; import android.sysprop.TelephonyProperties; import android.telecom.TelecomManager; -import android.telephony.PhoneStateListener; import android.telephony.ServiceState; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.transition.AutoTransition; import android.transition.TransitionManager; @@ -138,6 +138,7 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.EmergencyDialerConstants; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.leak.RotationUtils; @@ -303,7 +304,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, AudioManager audioManager, IDreamManager iDreamManager, DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, - ConnectivityManager connectivityManager, TelephonyManager telephonyManager, + ConnectivityManager connectivityManager, + TelephonyListenerManager telephonyListenerManager, ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources, ConfigurationController configurationController, ActivityStarter activityStarter, KeyguardStateController keyguardStateController, UserManager userManager, @@ -361,7 +363,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); // get notified of phone state changes - telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); + telephonyListenerManager.addServiceStateListener(mPhoneStateListener); contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, mAirplaneModeObserver); @@ -2049,7 +2051,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } }; - PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + private final TelephonyCallback.ServiceStateListener mPhoneStateListener = + new TelephonyCallback.ServiceStateListener() { @Override public void onServiceStateChanged(ServiceState serviceState) { if (!mHasTelephony) return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 8a86021423631..cfaeb0edec534 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -22,7 +22,6 @@ import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; -import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -44,11 +43,11 @@ import android.os.Looper; import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.telephony.CellSignalStrength; -import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.FeatureFlagUtils; @@ -74,6 +73,7 @@ import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; +import com.android.systemui.telephony.TelephonyListenerManager; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -108,6 +108,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private final Context mContext; private final TelephonyManager mPhone; + private final TelephonyListenerManager mTelephonyListenerManager; private final WifiManager mWifiManager; private final ConnectivityManager mConnectivityManager; private final SubscriptionManager mSubscriptionManager; @@ -121,7 +122,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private final boolean mProviderModel; private Config mConfig; - private PhoneStateListener mPhoneStateListener; + private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; // Subcontrollers. @@ -201,12 +202,14 @@ public class NetworkControllerImpl extends BroadcastReceiver BroadcastDispatcher broadcastDispatcher, ConnectivityManager connectivityManager, TelephonyManager telephonyManager, + TelephonyListenerManager telephonyListenerManager, @Nullable WifiManager wifiManager, NetworkScoreManager networkScoreManager, AccessPointControllerImpl accessPointController, DemoModeController demoModeController) { this(context, connectivityManager, telephonyManager, + telephonyListenerManager, wifiManager, networkScoreManager, SubscriptionManager.from(context), Config.readConfig(context), bgLooper, @@ -222,7 +225,9 @@ public class NetworkControllerImpl extends BroadcastReceiver @VisibleForTesting NetworkControllerImpl(Context context, ConnectivityManager connectivityManager, - TelephonyManager telephonyManager, WifiManager wifiManager, + TelephonyManager telephonyManager, + TelephonyListenerManager telephonyListenerManager, + WifiManager wifiManager, NetworkScoreManager networkScoreManager, SubscriptionManager subManager, Config config, Looper bgLooper, CallbackHandler callbackHandler, @@ -233,6 +238,7 @@ public class NetworkControllerImpl extends BroadcastReceiver BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController) { mContext = context; + mTelephonyListenerManager = telephonyListenerManager; mConfig = config; mReceiverHandler = new Handler(bgLooper); mCallbackHandler = callbackHandler; @@ -372,23 +378,20 @@ public class NetworkControllerImpl extends BroadcastReceiver // exclusively for status bar icons. mConnectivityManager.registerDefaultNetworkCallback(callback, mReceiverHandler); // Register the listener on our bg looper - mPhoneStateListener = new PhoneStateListener(mReceiverHandler::post) { - @Override - public void onActiveDataSubscriptionIdChanged(int subId) { - // For data switching from A to B, we assume B is validated for up to 2 seconds iff: - // 1) A and B are in the same subscription group e.g. CBRS data switch. And - // 2) A was validated before the switch. - // This is to provide smooth transition for UI without showing cross during data - // switch. - if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) { - if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true."); - mForceCellularValidated = true; - mReceiverHandler.removeCallbacks(mClearForceValidated); - mReceiverHandler.postDelayed(mClearForceValidated, 2000); - } - mActiveMobileDataSubscription = subId; - doUpdateMobileControllers(); + mPhoneStateListener = subId -> { + // For data switching from A to B, we assume B is validated for up to 2 seconds iff: + // 1) A and B are in the same subscription group e.g. CBRS data switch. And + // 2) A was validated before the switch. + // This is to provide smooth transition for UI without showing cross during data + // switch. + if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) { + if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true."); + mForceCellularValidated = true; + mReceiverHandler.removeCallbacks(mClearForceValidated); + mReceiverHandler.postDelayed(mClearForceValidated, 2000); } + mActiveMobileDataSubscription = subId; + doUpdateMobileControllers(); }; mDemoModeController.addCallback(this); @@ -428,7 +431,7 @@ public class NetworkControllerImpl extends BroadcastReceiver mSubscriptionListener = new SubListener(); } mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener); - mPhone.listen(mPhoneStateListener, LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); // broadcasts IntentFilter filter = new IntentFilter(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index d4029e64036ed..d05d972a50e6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -42,8 +42,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.telephony.PhoneStateListener; -import android.telephony.TelephonyManager; +import android.telephony.TelephonyCallback; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -69,6 +68,7 @@ import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.qs.tiles.UserDetailView; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.CreateUserActivity; import java.io.FileDescriptor; @@ -105,6 +105,7 @@ public class UserSwitcherController implements Dumpable { protected final Handler mHandler; private final ActivityStarter mActivityStarter; private final BroadcastDispatcher mBroadcastDispatcher; + private final TelephonyListenerManager mTelephonyListenerManager; private final IActivityTaskManager mActivityTaskManager; private ArrayList mUsers = new ArrayList<>(); @@ -127,9 +128,11 @@ public class UserSwitcherController implements Dumpable { public UserSwitcherController(Context context, KeyguardStateController keyguardStateController, @Main Handler handler, ActivityStarter activityStarter, BroadcastDispatcher broadcastDispatcher, UiEventLogger uiEventLogger, + TelephonyListenerManager telephonyListenerManager, IActivityTaskManager activityTaskManager) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; + mTelephonyListenerManager = telephonyListenerManager; mActivityTaskManager = activityTaskManager; mUiEventLogger = uiEventLogger; mUserDetailAdapter = new UserDetailAdapter(this, mContext, mUiEventLogger); @@ -458,18 +461,15 @@ public class UserSwitcherController implements Dumpable { } private void listenForCallState() { - final TelephonyManager tele = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - if (tele != null) { - tele.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); - } + mTelephonyListenerManager.addCallStateListener(mPhoneStateListener); } - private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + private final TelephonyCallback.CallStateListener mPhoneStateListener = + new TelephonyCallback.CallStateListener() { private int mCallState; @Override - public void onCallStateChanged(int state, String incomingNumber) { + public void onCallStateChanged(int state) { if (mCallState == state) return; if (DEBUG) Log.v(TAG, "Call state changed: " + state); mCallState = state; diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java new file mode 100644 index 0000000000000..3bc26322e6cd1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 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.systemui.telephony; + +import android.telephony.ServiceState; +import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import android.telephony.TelephonyCallback.CallStateListener; +import android.telephony.TelephonyCallback.ServiceStateListener; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +class TelephonyCallback extends android.telephony.TelephonyCallback + implements ActiveDataSubscriptionIdListener, CallStateListener, ServiceStateListener { + + private final List mActiveDataSubscriptionIdListeners = + new ArrayList<>(); + private final List mCallStateListeners = new ArrayList<>(); + private final List mServiceStateListeners = new ArrayList<>(); + + @Inject + TelephonyCallback() { + } + + boolean hasAnyListeners() { + return !mActiveDataSubscriptionIdListeners.isEmpty() + || !mCallStateListeners.isEmpty() + || !mServiceStateListeners.isEmpty(); + } + + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + mActiveDataSubscriptionIdListeners.forEach(listener -> { + listener.onActiveDataSubscriptionIdChanged(subId); + }); + } + + void addActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) { + mActiveDataSubscriptionIdListeners.add(listener); + } + + void removeActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) { + mActiveDataSubscriptionIdListeners.remove(listener); + } + + @Override + public void onCallStateChanged(int state) { + mCallStateListeners.forEach(listener -> { + listener.onCallStateChanged(state); + }); + } + + void addCallStateListener(CallStateListener listener) { + mCallStateListeners.add(listener); + } + + void removeCallStateListener(CallStateListener listener) { + mCallStateListeners.remove(listener); + } + + @Override + public void onServiceStateChanged(@NonNull ServiceState serviceState) { + mServiceStateListeners.forEach(listener -> { + listener.onServiceStateChanged(serviceState); + }); + } + + void addServiceStateListener(ServiceStateListener listener) { + mServiceStateListeners.add(listener); + } + + void removeServiceStateListener(ServiceStateListener listener) { + mServiceStateListeners.remove(listener); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java new file mode 100644 index 0000000000000..4e1accacfb7f1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 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.systemui.telephony; + +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import android.telephony.TelephonyCallback.CallStateListener; +import android.telephony.TelephonyCallback.ServiceStateListener; +import android.telephony.TelephonyManager; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; + +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * Wrapper around {@link TelephonyManager#listen(PhoneStateListener, int)}. + * + * The TelephonyManager complains if too many places in code register a listener. This class + * encapsulates SystemUI's usage of this function, reducing it down to a single listener. + * + * See also + * {@link TelephonyManager#registerTelephonyCallback(Executor, android.telephony.TelephonyCallback)} + */ +@SysUISingleton +public class TelephonyListenerManager { + private final TelephonyManager mTelephonyManager; + private final Executor mExecutor; + private final TelephonyCallback mTelephonyCallback; + + private boolean mListening = false; + + @Inject + public TelephonyListenerManager(TelephonyManager telephonyManager, @Main Executor executor, + TelephonyCallback telephonyCallback) { + mTelephonyManager = telephonyManager; + mExecutor = executor; + mTelephonyCallback = telephonyCallback; + } + + /** */ + public void addActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) { + mTelephonyCallback.addActiveDataSubscriptionIdListener(listener); + updateListening(); + } + + /** */ + public void removeActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) { + mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener); + updateListening(); + } + + /** */ + public void addCallStateListener(CallStateListener listener) { + mTelephonyCallback.addCallStateListener(listener); + updateListening(); + } + + /** */ + public void removeCallStateListener(CallStateListener listener) { + mTelephonyCallback.removeCallStateListener(listener); + updateListening(); + } + + /** */ + public void addServiceStateListener(ServiceStateListener listener) { + mTelephonyCallback.addServiceStateListener(listener); + updateListening(); + } + + /** */ + public void removeServiceStateListener(ServiceStateListener listener) { + mTelephonyCallback.removeServiceStateListener(listener); + updateListening(); + } + + + private void updateListening() { + if (!mListening && mTelephonyCallback.hasAnyListeners()) { + mListening = true; + mTelephonyManager.registerTelephonyCallback(mExecutor, mTelephonyCallback); + } else if (mListening && !mTelephonyCallback.hasAnyListeners()) { + mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback); + mListening = false; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java index 6f2c0af5384df..c54620248ceb3 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java @@ -21,12 +21,13 @@ import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.DATA_ROAMING_ENABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -40,10 +41,6 @@ import static org.mockito.Mockito.when; import android.content.pm.PackageManager; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Process; import android.provider.Settings; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; @@ -51,13 +48,14 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; import android.text.TextUtils; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.telephony.TelephonyListenerManager; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -73,7 +71,6 @@ import java.util.List; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper public class CarrierTextControllerTest extends SysuiTestCase { private static final CharSequence SEPARATOR = " \u2014 "; @@ -103,24 +100,25 @@ public class CarrierTextControllerTest extends SysuiTestCase { @Mock private TelephonyManager mTelephonyManager; @Mock + private TelephonyListenerManager mTelephonyListenerManager; + private FakeSystemClock mFakeSystemClock = new FakeSystemClock(); + private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); + private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock); + @Mock private SubscriptionManager mSubscriptionManager; private CarrierTextController.CarrierTextCallbackInfo mCarrierTextCallbackInfo; private CarrierTextController mCarrierTextController; - private TestableLooper mTestableLooper; private Void checkMainThread(InvocationOnMock inv) { - Looper mainLooper = Dependency.get(Dependency.MAIN_HANDLER).getLooper(); - if (!mainLooper.isCurrentThread()) { - fail("This call should be done from the main thread"); - } + assertThat(mMainExecutor.isExecuting()).isTrue(); + assertThat(mBgExecutor.isExecuting()).isFalse(); return null; } @Before public void setUp() { MockitoAnnotations.initMocks(this); - mTestableLooper = TestableLooper.get(this); mContext.addMockSystemService(WifiManager.class, mWifiManager); mContext.addMockSystemService(PackageManager.class, mPackageManager); @@ -132,9 +130,6 @@ public class CarrierTextControllerTest extends SysuiTestCase { mContext.getOrCreateTestableResources().addOverride( R.string.airplane_mode, AIRPLANE_MODE_TEXT); mDependency.injectMockDependency(WakefulnessLifecycle.class); - mDependency.injectTestDependency(Dependency.MAIN_HANDLER, - new Handler(mTestableLooper.getLooper())); - mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor); doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor) @@ -147,30 +142,21 @@ public class CarrierTextControllerTest extends SysuiTestCase { when(mTelephonyManager.getSupportedModemCount()).thenReturn(3); when(mTelephonyManager.getActiveModemCount()).thenReturn(3); - mCarrierTextController = new CarrierTextController(mContext, SEPARATOR, true, true); + mCarrierTextController = new CarrierTextController( + mContext, SEPARATOR, true, true, + mTelephonyManager, mTelephonyListenerManager, mMainExecutor, + mBgExecutor); // This should not start listening on any of the real dependencies but will test that // callbacks in mKeyguardUpdateMonitor are done in the mTestableLooper thread mCarrierTextController.setListening(mCarrierTextCallback); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); } @Test public void testKeyguardUpdateMonitorCalledInMainThread() throws Exception { - // This test will run on the main looper (which is not the same as the looper set as MAIN - // for CarrierTextCallback. This will fail if calls to mKeyguardUpdateMonitor are not done - // through the looper set in the set up - HandlerThread thread = new HandlerThread("testThread", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - TestableLooper testableLooper = new TestableLooper(thread.getLooper()); - Handler h = new Handler(testableLooper.getLooper()); - h.post(() -> { - mCarrierTextController.setListening(null); - mCarrierTextController.setListening(mCarrierTextCallback); - }); - testableLooper.processAllMessages(); - mTestableLooper.processAllMessages(); - thread.quitSafely(); + mCarrierTextController.setListening(null); + mCarrierTextController.setListening(mCarrierTextCallback); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); } @Test @@ -189,7 +175,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { ArgumentCaptor.forClass( CarrierTextController.CarrierTextCallbackInfo.class); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText); } @@ -212,7 +198,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { ArgumentCaptor.forClass( CarrierTextController.CarrierTextCallbackInfo.class); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals("TEST_CARRIER" + SEPARATOR + INVALID_CARD_TEXT, captor.getValue().carrierText); // There's only one subscription in the list @@ -224,7 +210,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { when(mTelephonyManager.getActiveModemCount()).thenReturn(1); // Update carrier text. It should ignore error state of subId 3 in inactive slotId. mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals("TEST_CARRIER", captor.getValue().carrierText); } @@ -260,7 +246,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { mCarrierTextController.mCallback.onSimStateChanged(0, 1, TelephonyManager.SIM_STATE_CARD_IO_ERROR); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo( any(CarrierTextController.CarrierTextCallbackInfo.class)); } @@ -269,7 +255,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { public void testCallback() { reset(mCarrierTextCallback); mCarrierTextController.postToCallback(mCarrierTextCallbackInfo); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); ArgumentCaptor captor = ArgumentCaptor.forClass( @@ -286,7 +272,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { mCarrierTextController.setListening(null); // This shouldn't produce NPE - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(any()); } @@ -306,7 +292,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { CarrierTextController.CarrierTextCallbackInfo.class); mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); CarrierTextController.CarrierTextCallbackInfo info = captor.getValue(); @@ -331,7 +317,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { CarrierTextController.CarrierTextCallbackInfo.class); mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); CarrierTextController.CarrierTextCallbackInfo info = captor.getValue(); @@ -356,7 +342,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { CarrierTextController.CarrierTextCallbackInfo.class); mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertTrue("Carrier text should be empty, instead it's " + captor.getValue().carrierText, @@ -385,7 +371,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { CarrierTextController.CarrierTextCallbackInfo.class); mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertFalse("No SIM should be available", captor.getValue().anySimReady); @@ -412,7 +398,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { CarrierTextController.CarrierTextCallbackInfo.class); mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); CarrierTextController.CarrierTextCallbackInfo info = captor.getValue(); @@ -438,7 +424,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { CarrierTextController.CarrierTextCallbackInfo.class); mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER, @@ -463,7 +449,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { CarrierTextController.CarrierTextCallbackInfo.class); mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(TEST_CARRIER, @@ -488,7 +474,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { CarrierTextController.CarrierTextCallbackInfo.class); mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(TEST_CARRIER, @@ -514,7 +500,7 @@ public class CarrierTextControllerTest extends SysuiTestCase { CarrierTextController.CarrierTextCallbackInfo.class); mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 52e2016d6f0ed..160dae5799644 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -88,6 +88,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.RingerModeTracker; import org.junit.After; @@ -163,6 +164,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private AuthController mAuthController; @Mock + private TelephonyListenerManager mTelephonyListenerManager; + @Mock private FeatureFlags mFeatureFlags; @Captor private ArgumentCaptor mStatusBarStateListenerCaptor; @@ -883,7 +886,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mBroadcastDispatcher, mDumpManager, mRingerModeTracker, mBackgroundExecutor, mStatusBarStateController, mLockPatternUtils, - mAuthController, mFeatureFlags); + mAuthController, mTelephonyListenerManager, mFeatureFlags); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index eedf09936b4d0..8add93032caa5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -43,7 +43,6 @@ import android.os.Handler; import android.os.RemoteException; import android.os.UserManager; import android.service.dreams.IDreamManager; -import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.IWindowManager; @@ -75,6 +74,7 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.settings.SecureSettings; @@ -106,7 +106,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock private LockPatternUtils mLockPatternUtils; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private ConnectivityManager mConnectivityManager; - @Mock private TelephonyManager mTelephonyManager; + @Mock private TelephonyListenerManager mTelephonyListenerManager; @Mock private ContentResolver mContentResolver; @Mock private Resources mResources; @Mock private ConfigurationController mConfigurationController; @@ -167,7 +167,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mLockPatternUtils, mBroadcastDispatcher, mConnectivityManager, - mTelephonyManager, + mTelephonyListenerManager, mContentResolver, null, mResources, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 8f36415d60afa..ef3317288e4ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -76,6 +76,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import com.android.systemui.telephony.TelephonyListenerManager; import org.junit.After; import org.junit.Before; @@ -113,6 +114,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected NetworkScoreManager mMockNsm; protected SubscriptionManager mMockSm; protected TelephonyManager mMockTm; + protected TelephonyListenerManager mTelephonyListenerManager; protected BroadcastDispatcher mMockBd; protected Config mConfig; protected CallbackHandler mCallbackHandler; @@ -164,6 +166,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mDemoModeController = mock(DemoModeController.class); mMockWm = mock(WifiManager.class); mMockTm = mock(TelephonyManager.class); + mTelephonyListenerManager = mock(TelephonyListenerManager.class); mMockSm = mock(SubscriptionManager.class); mMockCm = mock(ConnectivityManager.class); mMockBd = mock(BroadcastDispatcher.class); @@ -213,6 +216,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, + mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, @@ -285,7 +289,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected NetworkControllerImpl setUpNoMobileData() { when(mMockTm.isDataCapable()).thenReturn(false); NetworkControllerImpl networkControllerNoMobile = - new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockNsm, mMockSm, + new NetworkControllerImpl(mContext, mMockCm, mMockTm, mTelephonyListenerManager, + mMockWm, mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index b108dd817bde5..f4ad819acf571 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -106,7 +106,8 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { public void test4gDataIcon() { // Switch to showing 4g icon and re-initialize the NetworkController. mConfig.show4gForLte = true; - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, + mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java index 91e9f0622cbf8..3c5cbb69eef6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java @@ -61,8 +61,9 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // Turn off mobile network support. when(mMockTm.isDataCapable()).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, - mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, + mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController); @@ -80,8 +81,9 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { when(mMockTm.getServiceState()).thenReturn(mServiceState); when(mMockSm.getCompleteActiveSubscriptionInfoList()).thenReturn(Collections.emptyList()); - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, - mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, + mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController); @@ -147,8 +149,9 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // Turn off mobile network support. when(mMockTm.isDataCapable()).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, - mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, + mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java new file mode 100644 index 0000000000000..463b33602c150 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021 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.systemui.telephony; + +import static com.google.common.truth.Truth.assertThat; + +import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import android.telephony.TelephonyCallback.CallStateListener; +import android.telephony.TelephonyCallback.ServiceStateListener; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TelephonyCallbackTest extends SysuiTestCase { + + private TelephonyCallback mTelephonyCallback = new TelephonyCallback(); + + @Test + public void testAddListener_ActiveDataSubscriptionIdListener() { + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + mTelephonyCallback.addActiveDataSubscriptionIdListener(subId -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.addActiveDataSubscriptionIdListener(subId -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + } + + @Test + public void testAddListener_CallStateListener() { + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + mTelephonyCallback.addCallStateListener(state -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.addCallStateListener(state -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + } + + @Test + public void testAddListener_ServiceStateListener() { + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + mTelephonyCallback.addServiceStateListener(serviceState -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.addServiceStateListener(serviceState -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + } + + @Test + public void testRemoveListener_ActiveDataSubscriptionIdListener() { + ActiveDataSubscriptionIdListener listener = subId -> {}; + mTelephonyCallback.addActiveDataSubscriptionIdListener(listener); + mTelephonyCallback.addActiveDataSubscriptionIdListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + } + + @Test + public void testRemoveListener_CallStateListener() { + CallStateListener listener = state -> {}; + mTelephonyCallback.addCallStateListener(listener); + mTelephonyCallback.addCallStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeCallStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeCallStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + } + + @Test + public void testRemoveListener_ServiceStateListener() { + ServiceStateListener listener = serviceState -> {}; + mTelephonyCallback.addServiceStateListener(listener); + mTelephonyCallback.addServiceStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeServiceStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeServiceStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java new file mode 100644 index 0000000000000..0d1ac7b96f0e8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2021 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.systemui.telephony; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import android.telephony.TelephonyCallback.CallStateListener; +import android.telephony.TelephonyCallback.ServiceStateListener; +import android.telephony.TelephonyManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TelephonyListenerManagerTest extends SysuiTestCase { + + @Mock + private TelephonyManager mTelephonyManager; + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + @Mock + private TelephonyCallback mTelephonyCallback; + + TelephonyListenerManager mTelephonyListenerManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTelephonyListenerManager = new TelephonyListenerManager( + mTelephonyManager, mExecutor, mTelephonyCallback); + } + + @Test + public void testAddListenerRegisters_ActiveDataSubscriptionIdListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + } + + @Test + public void testAddListenerRegisters_CallStateListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + mTelephonyListenerManager.addCallStateListener(state -> {}); + mTelephonyListenerManager.addCallStateListener(state -> {}); + mTelephonyListenerManager.addCallStateListener(state -> {}); + mTelephonyListenerManager.addCallStateListener(state -> {}); + + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + } + + @Test + public void testAddListenerRegisters_ServiceStateListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + } + + @Test + public void testAddListenerRegisters_mixed() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + mTelephonyListenerManager.addCallStateListener(state -> {}); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + mTelephonyListenerManager.addCallStateListener(state -> {}); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + } + + @Test + public void testRemoveListenerUnregisters_ActiveDataSubscriptionIdListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + ActiveDataSubscriptionIdListener mListener = subId -> { }; + + // Need to add one to actually register + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mListener); + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + reset(mTelephonyManager); + + when(mTelephonyCallback.hasAnyListeners()).thenReturn(false); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener); + verify(mTelephonyManager, times(1)) + .unregisterTelephonyCallback(mTelephonyCallback); + } + + @Test + public void testRemoveListenerUnregisters_CallStateListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + CallStateListener mListener = state -> { }; + + // Need to add one to actually register + mTelephonyListenerManager.addCallStateListener(mListener); + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + reset(mTelephonyManager); + + when(mTelephonyCallback.hasAnyListeners()).thenReturn(false); + mTelephonyListenerManager.removeCallStateListener(mListener); + mTelephonyListenerManager.removeCallStateListener(mListener); + mTelephonyListenerManager.removeCallStateListener(mListener); + mTelephonyListenerManager.removeCallStateListener(mListener); + verify(mTelephonyManager, times(1)) + .unregisterTelephonyCallback(mTelephonyCallback); + } + + @Test + public void testRemoveListenerUnregisters_ServiceStateListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + ServiceStateListener mListener = serviceState -> { }; + + // Need to add one to actually register + mTelephonyListenerManager.addServiceStateListener(mListener); + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + reset(mTelephonyManager); + + when(mTelephonyCallback.hasAnyListeners()).thenReturn(false); + mTelephonyListenerManager.removeServiceStateListener(mListener); + mTelephonyListenerManager.removeServiceStateListener(mListener); + mTelephonyListenerManager.removeServiceStateListener(mListener); + mTelephonyListenerManager.removeServiceStateListener(mListener); + verify(mTelephonyManager, times(1)) + .unregisterTelephonyCallback(mTelephonyCallback); + } + + @Test + public void testRemoveListenerUnregisters_mixed() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + ActiveDataSubscriptionIdListener mListenerA = subId -> { }; + ServiceStateListener mListenerB = serviceState -> { }; + CallStateListener mListenerC = state -> { }; + + // Need to add one to actually register + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mListenerA); + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + reset(mTelephonyManager); + + when(mTelephonyCallback.hasAnyListeners()).thenReturn(false); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListenerA); + mTelephonyListenerManager.removeServiceStateListener(mListenerB); + mTelephonyListenerManager.removeCallStateListener(mListenerC); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListenerA); + mTelephonyListenerManager.removeServiceStateListener(mListenerB); + mTelephonyListenerManager.removeCallStateListener(mListenerC); + verify(mTelephonyManager, times(1)) + .unregisterTelephonyCallback(mTelephonyCallback); + } + + @Test + public void testAddListener_noDoubleRegister() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + + reset(mTelephonyManager); + + // A second call to add doesn't register another listener. + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + verify(mTelephonyManager, never()).registerTelephonyCallback(mExecutor, mTelephonyCallback); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java index 7c7ad5322d48d..d3d30f242dcff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java @@ -27,6 +27,7 @@ public class FakeExecutor implements DelayableExecutor { private final FakeSystemClock mClock; private PriorityQueue mQueuedRunnables = new PriorityQueue<>(); private boolean mIgnoreClockUpdates; + private boolean mExecuting; /** * Initializes a fake executor. @@ -56,7 +57,9 @@ public class FakeExecutor implements DelayableExecutor { */ public boolean runNextReady() { if (!mQueuedRunnables.isEmpty() && mQueuedRunnables.peek().mWhen <= mClock.uptimeMillis()) { + mExecuting = true; mQueuedRunnables.poll().mRunnable.run(); + mExecuting = false; return true; } @@ -162,6 +165,10 @@ public class FakeExecutor implements DelayableExecutor { executeDelayed(command, 0); } + public boolean isExecuting() { + return mExecuting; + } + /** * Run all Executors in a loop until they all report they have no ready work to do. * diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java index abc283f40b1c2..87206c5b1c80d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java @@ -16,6 +16,8 @@ package com.android.systemui.util.concurrency; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -319,6 +321,18 @@ public class FakeExecutorTest extends SysuiTestCase { assertEquals(1, runnable.mRunCount); } + @Test + public void testIsExecuting() { + FakeSystemClock clock = new FakeSystemClock(); + FakeExecutor fakeExecutor = new FakeExecutor(clock); + + Runnable runnable = () -> assertThat(fakeExecutor.isExecuting()).isTrue(); + + assertThat(fakeExecutor.isExecuting()).isFalse(); + fakeExecutor.execute(runnable); + assertThat(fakeExecutor.isExecuting()).isFalse(); + } + private static class RunnableImpl implements Runnable { int mRunCount;