1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.shade.carrier; 18 19 import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; 20 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; 21 22 import android.annotation.MainThread; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.os.Handler; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.provider.Settings; 31 import android.telephony.SubscriptionManager; 32 import android.text.TextUtils; 33 import android.util.Log; 34 import android.view.View; 35 import android.widget.TextView; 36 37 import androidx.annotation.VisibleForTesting; 38 39 import com.android.keyguard.CarrierTextManager; 40 import com.android.settingslib.AccessibilityContentDescriptions; 41 import com.android.settingslib.mobile.TelephonyIcons; 42 import com.android.systemui.dagger.SysUISingleton; 43 import com.android.systemui.dagger.qualifiers.Background; 44 import com.android.systemui.dagger.qualifiers.Main; 45 import com.android.systemui.plugins.ActivityStarter; 46 import com.android.systemui.res.R; 47 import com.android.systemui.statusbar.connectivity.MobileDataIndicators; 48 import com.android.systemui.statusbar.connectivity.NetworkController; 49 import com.android.systemui.statusbar.connectivity.SignalCallback; 50 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider; 51 import com.android.systemui.statusbar.phone.StatusBarLocation; 52 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags; 53 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter; 54 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder; 55 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView; 56 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel; 57 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel; 58 import com.android.systemui.util.CarrierConfigTracker; 59 60 import java.util.List; 61 import java.util.function.Consumer; 62 63 import javax.inject.Inject; 64 65 public class ShadeCarrierGroupController { 66 private static final String TAG = "ShadeCarrierGroup"; 67 68 /** 69 * Support up to 3 slots which is what's supported by {@link TelephonyManager#getPhoneCount} 70 */ 71 private static final int SIM_SLOTS = 3; 72 73 private final ActivityStarter mActivityStarter; 74 private final Handler mBgHandler; 75 private final Context mContext; 76 private final NetworkController mNetworkController; 77 private final CarrierTextManager mCarrierTextManager; 78 private final TextView mNoSimTextView; 79 // Non final for testing 80 private H mMainHandler; 81 private final Callback mCallback; 82 private final MobileIconsViewModel mMobileIconsViewModel; 83 private final MobileContextProvider mMobileContextProvider; 84 private final StatusBarPipelineFlags mStatusBarPipelineFlags; 85 private boolean mListening; 86 private final CellSignalState[] mInfos = 87 new CellSignalState[SIM_SLOTS]; 88 private View[] mCarrierDividers = new View[SIM_SLOTS - 1]; 89 private ShadeCarrier[] mCarrierGroups = new ShadeCarrier[SIM_SLOTS]; 90 private int[] mLastSignalLevel = new int[SIM_SLOTS]; 91 private String[] mLastSignalLevelDescription = new String[SIM_SLOTS]; 92 private final CarrierConfigTracker mCarrierConfigTracker; 93 94 private boolean mIsSingleCarrier; 95 @Nullable 96 private OnSingleCarrierChangedListener mOnSingleCarrierChangedListener; 97 98 private final SlotIndexResolver mSlotIndexResolver; 99 100 private final ShadeCarrierGroupControllerLogger mLogger; 101 102 private final SignalCallback mSignalCallback = new SignalCallback() { 103 @Override 104 public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) { 105 int slotIndex = getSlotIndex(indicators.subId); 106 if (slotIndex >= SIM_SLOTS) { 107 Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex); 108 return; 109 } 110 if (slotIndex == INVALID_SIM_SLOT_INDEX) { 111 Log.e(TAG, "Invalid SIM slot index for subscription: " + indicators.subId); 112 return; 113 } 114 mInfos[slotIndex] = new CellSignalState( 115 indicators.statusIcon.visible, 116 indicators.statusIcon.icon, 117 indicators.statusIcon.contentDescription, 118 indicators.typeContentDescription.toString(), 119 indicators.roaming 120 ); 121 mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); 122 } 123 124 @Override 125 public void setNoSims(boolean hasNoSims, boolean simDetected) { 126 if (hasNoSims) { 127 for (int i = 0; i < SIM_SLOTS; i++) { 128 mInfos[i] = mInfos[i].changeVisibility(false); 129 } 130 } 131 mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); 132 } 133 }; 134 135 private static class Callback implements CarrierTextManager.CarrierTextCallback { 136 private H mHandler; 137 Callback(H handler)138 Callback(H handler) { 139 mHandler = handler; 140 } 141 142 @Override updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info)143 public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) { 144 mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget(); 145 } 146 } 147 ShadeCarrierGroupController( ShadeCarrierGroup view, ActivityStarter activityStarter, @Background Handler bgHandler, @Main Looper mainLooper, ShadeCarrierGroupControllerLogger logger, NetworkController networkController, CarrierTextManager.Builder carrierTextManagerBuilder, Context context, CarrierConfigTracker carrierConfigTracker, SlotIndexResolver slotIndexResolver, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider, StatusBarPipelineFlags statusBarPipelineFlags )148 private ShadeCarrierGroupController( 149 ShadeCarrierGroup view, 150 ActivityStarter activityStarter, 151 @Background Handler bgHandler, 152 @Main Looper mainLooper, 153 ShadeCarrierGroupControllerLogger logger, 154 NetworkController networkController, 155 CarrierTextManager.Builder carrierTextManagerBuilder, 156 Context context, 157 CarrierConfigTracker carrierConfigTracker, 158 SlotIndexResolver slotIndexResolver, 159 MobileUiAdapter mobileUiAdapter, 160 MobileContextProvider mobileContextProvider, 161 StatusBarPipelineFlags statusBarPipelineFlags 162 ) { 163 mContext = context; 164 mActivityStarter = activityStarter; 165 mBgHandler = bgHandler; 166 mLogger = logger; 167 mNetworkController = networkController; 168 mStatusBarPipelineFlags = statusBarPipelineFlags; 169 mCarrierTextManager = carrierTextManagerBuilder 170 .setShowAirplaneMode(false) 171 .setShowMissingSim(false) 172 .setDebugLocationString("Shade") 173 .build(); 174 mCarrierConfigTracker = carrierConfigTracker; 175 mSlotIndexResolver = slotIndexResolver; 176 View.OnClickListener onClickListener = v -> { 177 if (!v.isVisibleToUser()) { 178 return; 179 } 180 mActivityStarter.postStartActivityDismissingKeyguard( 181 new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0); 182 }; 183 184 mNoSimTextView = view.getNoSimTextView(); 185 mNoSimTextView.setOnClickListener(onClickListener); 186 mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState); 187 mCallback = new Callback(mMainHandler); 188 189 mCarrierGroups[0] = view.getCarrier1View(); 190 mCarrierGroups[1] = view.getCarrier2View(); 191 mCarrierGroups[2] = view.getCarrier3View(); 192 193 mMobileContextProvider = mobileContextProvider; 194 mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel(); 195 196 if (mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()) { 197 mobileUiAdapter.setShadeCarrierGroupController(this); 198 MobileIconsBinder.bind(view, mMobileIconsViewModel); 199 } 200 201 mCarrierDividers[0] = view.getCarrierDivider1(); 202 mCarrierDividers[1] = view.getCarrierDivider2(); 203 204 for (int i = 0; i < SIM_SLOTS; i++) { 205 mInfos[i] = new CellSignalState( 206 false, 207 R.drawable.ic_shade_no_calling_sms, 208 context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(), 209 "", 210 false); 211 mLastSignalLevel[i] = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0]; 212 mLastSignalLevelDescription[i] = 213 context.getText(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0]) 214 .toString(); 215 mCarrierGroups[i].setOnClickListener(onClickListener); 216 } 217 mIsSingleCarrier = computeIsSingleCarrier(); 218 view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 219 220 view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 221 @Override 222 public void onViewAttachedToWindow(View v) { 223 } 224 225 @Override 226 public void onViewDetachedFromWindow(View v) { 227 setListening(false); 228 } 229 }); 230 } 231 232 /** Updates the number of visible mobile icons using the new pipeline. */ updateModernMobileIcons(List<Integer> subIds)233 public void updateModernMobileIcons(List<Integer> subIds) { 234 if (!mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()) { 235 Log.d(TAG, "ignoring new pipeline callback because new mobile icon is disabled"); 236 return; 237 } 238 239 for (ShadeCarrier carrier : mCarrierGroups) { 240 carrier.removeModernMobileView(); 241 } 242 243 List<IconData> iconDataList = processSubIdList(subIds); 244 245 for (IconData iconData : iconDataList) { 246 ShadeCarrier carrier = mCarrierGroups[iconData.slotIndex]; 247 248 Context mobileContext = 249 mMobileContextProvider.getMobileContextForSub(iconData.subId, mContext); 250 ModernShadeCarrierGroupMobileView modernMobileView = ModernShadeCarrierGroupMobileView 251 .constructAndBind( 252 mobileContext, 253 mMobileIconsViewModel.getLogger(), 254 "mobile_carrier_shade_group", 255 (ShadeCarrierGroupMobileIconViewModel) mMobileIconsViewModel 256 .viewModelForSub(iconData.subId, 257 StatusBarLocation.SHADE_CARRIER_GROUP) 258 ); 259 carrier.addModernMobileView(modernMobileView); 260 } 261 } 262 263 @VisibleForTesting processSubIdList(List<Integer> subIds)264 List<IconData> processSubIdList(List<Integer> subIds) { 265 return subIds 266 .stream() 267 .limit(SIM_SLOTS) 268 .map(subId -> new IconData(subId, getSlotIndex(subId))) 269 .filter(iconData -> 270 iconData.slotIndex < SIM_SLOTS 271 && iconData.slotIndex != INVALID_SIM_SLOT_INDEX 272 ) 273 .toList(); 274 } 275 276 @VisibleForTesting getSlotIndex(int subscriptionId)277 protected int getSlotIndex(int subscriptionId) { 278 return mSlotIndexResolver.getSlotIndex(subscriptionId); 279 } 280 281 @VisibleForTesting getShadeCarrierVisibility(int index)282 protected int getShadeCarrierVisibility(int index) { 283 return mCarrierGroups[index].getVisibility(); 284 } 285 286 /** 287 * Sets a {@link OnSingleCarrierChangedListener}. 288 * 289 * This will get notified when the number of carriers changes between 1 and "not one". 290 * @param listener 291 */ setOnSingleCarrierChangedListener( @ullable OnSingleCarrierChangedListener listener)292 public void setOnSingleCarrierChangedListener( 293 @Nullable OnSingleCarrierChangedListener listener) { 294 mOnSingleCarrierChangedListener = listener; 295 } 296 isSingleCarrier()297 public boolean isSingleCarrier() { 298 return mIsSingleCarrier; 299 } 300 computeIsSingleCarrier()301 private boolean computeIsSingleCarrier() { 302 int carrierCount = 0; 303 for (int i = 0; i < SIM_SLOTS; i++) { 304 305 if (mInfos[i].visible) { 306 carrierCount++; 307 } 308 } 309 return carrierCount == 1; 310 } 311 setListening(boolean listening)312 public void setListening(boolean listening) { 313 if (listening == mListening) { 314 return; 315 } 316 mListening = listening; 317 318 mBgHandler.post(this::updateListeners); 319 } 320 updateListeners()321 private void updateListeners() { 322 if (mListening) { 323 if (mNetworkController.hasVoiceCallingFeature()) { 324 mNetworkController.addCallback(mSignalCallback); 325 } 326 mCarrierTextManager.setListening(mCallback); 327 } else { 328 mNetworkController.removeCallback(mSignalCallback); 329 mCarrierTextManager.setListening(null); 330 } 331 } 332 333 334 @MainThread handleUpdateState()335 private void handleUpdateState() { 336 if (!mMainHandler.getLooper().isCurrentThread()) { 337 mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); 338 return; 339 } 340 341 boolean singleCarrier = computeIsSingleCarrier(); 342 343 if (singleCarrier) { 344 for (int i = 0; i < SIM_SLOTS; i++) { 345 if (mInfos[i].visible 346 && mInfos[i].mobileSignalIconId == R.drawable.ic_shade_sim_card) { 347 mInfos[i] = new CellSignalState(true, R.drawable.ic_blank, "", "", false); 348 } 349 } 350 } 351 352 if (!mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()) { 353 for (int i = 0; i < SIM_SLOTS; i++) { 354 mCarrierGroups[i].updateState(mInfos[i], singleCarrier); 355 } 356 } 357 358 mCarrierDividers[0].setVisibility( 359 mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE); 360 // This tackles the case of slots 2 being available as well as at least one other. 361 // In that case we show the second divider. Note that if both dividers are visible, it means 362 // all three slots are in use, and that is correct. 363 mCarrierDividers[1].setVisibility( 364 (mInfos[1].visible && mInfos[2].visible) 365 || (mInfos[0].visible && mInfos[2].visible) ? View.VISIBLE : View.GONE); 366 if (mIsSingleCarrier != singleCarrier) { 367 mIsSingleCarrier = singleCarrier; 368 if (mOnSingleCarrierChangedListener != null) { 369 mOnSingleCarrierChangedListener.onSingleCarrierChanged(singleCarrier); 370 } 371 } 372 } 373 374 @MainThread handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info)375 private void handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) { 376 if (!mMainHandler.getLooper().isCurrentThread()) { 377 mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget(); 378 return; 379 } 380 381 mLogger.logHandleUpdateCarrierInfo(info); 382 383 mNoSimTextView.setVisibility(View.GONE); 384 if (info.isInSatelliteMode) { 385 mLogger.logUsingSatelliteText(info.carrierText); 386 showSingleText(info.carrierText); 387 } else if (!info.airplaneMode && info.anySimReady) { 388 boolean[] slotSeen = new boolean[SIM_SLOTS]; 389 if (info.listOfCarriers.length == info.subscriptionIds.length) { 390 mLogger.logUsingSimViews(); 391 for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) { 392 int slot = getSlotIndex(info.subscriptionIds[i]); 393 if (slot >= SIM_SLOTS) { 394 Log.w(TAG, "updateInfoCarrier - slot: " + slot); 395 continue; 396 } 397 if (slot == INVALID_SIM_SLOT_INDEX) { 398 Log.e(TAG, 399 "Invalid SIM slot index for subscription: " 400 + info.subscriptionIds[i]); 401 continue; 402 } 403 String carrierText = info.listOfCarriers[i].toString().trim(); 404 if (!TextUtils.isEmpty(carrierText)) { 405 mInfos[slot] = mInfos[slot].changeVisibility(true); 406 slotSeen[slot] = true; 407 mCarrierGroups[slot].setCarrierText(carrierText); 408 mCarrierGroups[slot].setVisibility(View.VISIBLE); 409 } 410 } 411 for (int i = 0; i < SIM_SLOTS; i++) { 412 if (!slotSeen[i]) { 413 mInfos[i] = mInfos[i].changeVisibility(false); 414 mCarrierGroups[i].setVisibility(View.GONE); 415 } 416 } 417 } else { 418 mLogger.logInvalidArrayLengths( 419 info.listOfCarriers.length, info.subscriptionIds.length); 420 } 421 } else { 422 // No sims or airplane mode (but not WFC), so just show the main carrier text. 423 mLogger.logUsingNoSimView(info.carrierText); 424 showSingleText(info.carrierText); 425 } 426 handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread. 427 } 428 429 /** 430 * Shows only the given text in a single TextView and hides ShadeCarrierGroup (which would show 431 * individual SIM details). 432 */ showSingleText(CharSequence text)433 private void showSingleText(CharSequence text) { 434 for (int i = 0; i < SIM_SLOTS; i++) { 435 mInfos[i] = mInfos[i].changeVisibility(false); 436 mCarrierGroups[i].setCarrierText(""); 437 mCarrierGroups[i].setVisibility(View.GONE); 438 } 439 // TODO(b/341841138): Re-name this view now that it's being used for more than just the 440 // no-SIM case. 441 mNoSimTextView.setText(text); 442 if (!TextUtils.isEmpty(text)) { 443 mNoSimTextView.setVisibility(View.VISIBLE); 444 } 445 } 446 447 private static class H extends Handler { 448 private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo; 449 private Runnable mUpdateState; 450 static final int MSG_UPDATE_CARRIER_INFO = 0; 451 static final int MSG_UPDATE_STATE = 1; 452 H(Looper looper, Consumer<CarrierTextManager.CarrierTextCallbackInfo> updateCarrierInfo, Runnable updateState)453 H(Looper looper, 454 Consumer<CarrierTextManager.CarrierTextCallbackInfo> updateCarrierInfo, 455 Runnable updateState) { 456 super(looper); 457 mUpdateCarrierInfo = updateCarrierInfo; 458 mUpdateState = updateState; 459 } 460 461 @Override handleMessage(Message msg)462 public void handleMessage(Message msg) { 463 switch (msg.what) { 464 case MSG_UPDATE_CARRIER_INFO: 465 mUpdateCarrierInfo.accept( 466 (CarrierTextManager.CarrierTextCallbackInfo) msg.obj); 467 break; 468 case MSG_UPDATE_STATE: 469 mUpdateState.run(); 470 break; 471 default: 472 super.handleMessage(msg); 473 } 474 } 475 } 476 477 public static class Builder { 478 private ShadeCarrierGroup mView; 479 private final ActivityStarter mActivityStarter; 480 private final Handler mHandler; 481 private final Looper mLooper; 482 private final ShadeCarrierGroupControllerLogger mLogger; 483 private final NetworkController mNetworkController; 484 private final CarrierTextManager.Builder mCarrierTextControllerBuilder; 485 private final Context mContext; 486 private final CarrierConfigTracker mCarrierConfigTracker; 487 private final SlotIndexResolver mSlotIndexResolver; 488 private final MobileUiAdapter mMobileUiAdapter; 489 private final MobileContextProvider mMobileContextProvider; 490 private final StatusBarPipelineFlags mStatusBarPipelineFlags; 491 492 @Inject Builder( ActivityStarter activityStarter, @Background Handler handler, @Main Looper looper, ShadeCarrierGroupControllerLogger logger, NetworkController networkController, CarrierTextManager.Builder carrierTextControllerBuilder, Context context, CarrierConfigTracker carrierConfigTracker, SlotIndexResolver slotIndexResolver, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider, StatusBarPipelineFlags statusBarPipelineFlags )493 public Builder( 494 ActivityStarter activityStarter, 495 @Background Handler handler, 496 @Main Looper looper, 497 ShadeCarrierGroupControllerLogger logger, 498 NetworkController networkController, 499 CarrierTextManager.Builder carrierTextControllerBuilder, 500 Context context, 501 CarrierConfigTracker carrierConfigTracker, 502 SlotIndexResolver slotIndexResolver, 503 MobileUiAdapter mobileUiAdapter, 504 MobileContextProvider mobileContextProvider, 505 StatusBarPipelineFlags statusBarPipelineFlags 506 ) { 507 mActivityStarter = activityStarter; 508 mHandler = handler; 509 mLooper = looper; 510 mLogger = logger; 511 mNetworkController = networkController; 512 mCarrierTextControllerBuilder = carrierTextControllerBuilder; 513 mContext = context; 514 mCarrierConfigTracker = carrierConfigTracker; 515 mSlotIndexResolver = slotIndexResolver; 516 mMobileUiAdapter = mobileUiAdapter; 517 mMobileContextProvider = mobileContextProvider; 518 mStatusBarPipelineFlags = statusBarPipelineFlags; 519 } 520 setShadeCarrierGroup(ShadeCarrierGroup view)521 public Builder setShadeCarrierGroup(ShadeCarrierGroup view) { 522 mView = view; 523 return this; 524 } 525 build()526 public ShadeCarrierGroupController build() { 527 return new ShadeCarrierGroupController( 528 mView, 529 mActivityStarter, 530 mHandler, 531 mLooper, 532 mLogger, 533 mNetworkController, 534 mCarrierTextControllerBuilder, 535 mContext, 536 mCarrierConfigTracker, 537 mSlotIndexResolver, 538 mMobileUiAdapter, 539 mMobileContextProvider, 540 mStatusBarPipelineFlags 541 ); 542 } 543 } 544 545 /** 546 * Notify when the state changes from 1 carrier to "not one" and viceversa 547 */ 548 @FunctionalInterface 549 public interface OnSingleCarrierChangedListener { onSingleCarrierChanged(boolean isSingleCarrier)550 void onSingleCarrierChanged(boolean isSingleCarrier); 551 } 552 553 /** 554 * Interface for resolving slot index from subscription ID. 555 */ 556 @FunctionalInterface 557 public interface SlotIndexResolver { 558 /** 559 * Get slot index for given sub id. 560 */ getSlotIndex(int subscriptionId)561 int getSlotIndex(int subscriptionId); 562 } 563 564 /** 565 * Default implementation for {@link SlotIndexResolver}. 566 * 567 * It retrieves the slot index using {@link SubscriptionManager#getSlotIndex}. 568 */ 569 @SysUISingleton 570 public static class SubscriptionManagerSlotIndexResolver implements SlotIndexResolver { 571 572 @Inject SubscriptionManagerSlotIndexResolver()573 public SubscriptionManagerSlotIndexResolver() {} 574 575 @Override getSlotIndex(int subscriptionId)576 public int getSlotIndex(int subscriptionId) { 577 return SubscriptionManager.getSlotIndex(subscriptionId); 578 } 579 } 580 581 @VisibleForTesting 582 static class IconData { 583 public final int subId; 584 public final int slotIndex; 585 IconData(int subId, int slotIndex)586 IconData(int subId, int slotIndex) { 587 this.subId = subId; 588 this.slotIndex = slotIndex; 589 } 590 } 591 } 592