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