1 /*
2  * Copyright (C) 2020 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.qs.carrier;
18 
19 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
20 
21 import android.annotation.MainThread;
22 import android.annotation.NonNull;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.provider.Settings;
29 import android.telephony.SubscriptionManager;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.view.View;
33 import android.widget.TextView;
34 
35 import androidx.annotation.VisibleForTesting;
36 
37 import com.android.keyguard.CarrierTextManager;
38 import com.android.settingslib.AccessibilityContentDescriptions;
39 import com.android.settingslib.mobile.TelephonyIcons;
40 import com.android.systemui.R;
41 import com.android.systemui.dagger.SysUISingleton;
42 import com.android.systemui.dagger.qualifiers.Background;
43 import com.android.systemui.dagger.qualifiers.Main;
44 import com.android.systemui.flags.FeatureFlags;
45 import com.android.systemui.plugins.ActivityStarter;
46 import com.android.systemui.statusbar.connectivity.IconState;
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.util.CarrierConfigTracker;
51 
52 import java.util.function.Consumer;
53 
54 import javax.inject.Inject;
55 
56 public class QSCarrierGroupController {
57     private static final String TAG = "QSCarrierGroup";
58 
59     /**
60      * Support up to 3 slots which is what's supported by {@link TelephonyManager#getPhoneCount}
61      */
62     private static final int SIM_SLOTS = 3;
63 
64     private final ActivityStarter mActivityStarter;
65     private final Handler mBgHandler;
66     private final NetworkController mNetworkController;
67     private final CarrierTextManager mCarrierTextManager;
68     private final TextView mNoSimTextView;
69     // Non final for testing
70     private H mMainHandler;
71     private final Callback mCallback;
72     private boolean mListening;
73     private final CellSignalState[] mInfos =
74             new CellSignalState[SIM_SLOTS];
75     private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
76     private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
77     private int[] mLastSignalLevel = new int[SIM_SLOTS];
78     private String[] mLastSignalLevelDescription = new String[SIM_SLOTS];
79     private final boolean mProviderModel;
80     private final CarrierConfigTracker mCarrierConfigTracker;
81 
82     private boolean mIsSingleCarrier;
83     private OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
84 
85     private final SlotIndexResolver mSlotIndexResolver;
86 
87     private final SignalCallback mSignalCallback = new SignalCallback() {
88                 @Override
89                 public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
90                     if (mProviderModel) {
91                         return;
92                     }
93                     int slotIndex = getSlotIndex(indicators.subId);
94                     if (slotIndex >= SIM_SLOTS) {
95                         Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
96                         return;
97                     }
98                     if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
99                         Log.e(TAG, "Invalid SIM slot index for subscription: " + indicators.subId);
100                         return;
101                     }
102                     mInfos[slotIndex] = new CellSignalState(
103                             indicators.statusIcon.visible,
104                             indicators.statusIcon.icon,
105                             indicators.statusIcon.contentDescription,
106                             indicators.typeContentDescription.toString(),
107                             indicators.roaming,
108                             mProviderModel
109                     );
110                     mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
111                 }
112 
113                 @Override
114                 public void setCallIndicator(@NonNull IconState statusIcon, int subId) {
115                     if (!mProviderModel) {
116                         return;
117                     }
118                     int slotIndex = getSlotIndex(subId);
119                     if (slotIndex >= SIM_SLOTS) {
120                         Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
121                         return;
122                     }
123                     if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
124                         Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
125                         return;
126                     }
127 
128                     boolean displayCallStrengthIcon =
129                             mCarrierConfigTracker.getCallStrengthConfig(subId);
130 
131                     if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
132                         if (statusIcon.visible) {
133                             mInfos[slotIndex] = new CellSignalState(
134                                     true,
135                                     statusIcon.icon,
136                                     statusIcon.contentDescription,
137                                     "",
138                                     false,
139                                     mProviderModel);
140                         } else {
141                             // Whenever the no Calling & SMS state is cleared, switched to the last
142                             // known call strength icon.
143                             if (displayCallStrengthIcon) {
144                                 mInfos[slotIndex] = new CellSignalState(
145                                         true,
146                                         mLastSignalLevel[slotIndex],
147                                         mLastSignalLevelDescription[slotIndex],
148                                         "",
149                                         false,
150                                         mProviderModel);
151                             } else {
152                                 mInfos[slotIndex] = new CellSignalState(
153                                         true,
154                                         R.drawable.ic_qs_sim_card,
155                                         "",
156                                         "",
157                                         false,
158                                         mProviderModel);
159                             }
160                         }
161                         mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
162                     } else {
163                         mLastSignalLevel[slotIndex] = statusIcon.icon;
164                         mLastSignalLevelDescription[slotIndex] = statusIcon.contentDescription;
165                         // Only Shows the call strength icon when the no Calling & SMS icon is not
166                         // shown.
167                         if (mInfos[slotIndex].mobileSignalIconId
168                                 != R.drawable.ic_qs_no_calling_sms) {
169                             if (displayCallStrengthIcon) {
170                                 mInfos[slotIndex] = new CellSignalState(
171                                         true,
172                                         statusIcon.icon,
173                                         statusIcon.contentDescription,
174                                         "",
175                                         false,
176                                         mProviderModel);
177                             } else {
178                                 mInfos[slotIndex] = new CellSignalState(
179                                         true,
180                                         R.drawable.ic_qs_sim_card,
181                                         "",
182                                         "",
183                                         false,
184                                         mProviderModel);
185                             }
186                             mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
187                         }
188                     }
189                 }
190 
191                 @Override
192                 public void setNoSims(boolean hasNoSims, boolean simDetected) {
193                     if (hasNoSims) {
194                         for (int i = 0; i < SIM_SLOTS; i++) {
195                             mInfos[i] = mInfos[i].changeVisibility(false);
196                         }
197                     }
198                     mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
199                 }
200             };
201 
202     private static class Callback implements CarrierTextManager.CarrierTextCallback {
203         private H mHandler;
204 
Callback(H handler)205         Callback(H handler) {
206             mHandler = handler;
207         }
208 
209         @Override
updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info)210         public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
211             mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
212         }
213     }
214 
QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter, @Background Handler bgHandler, @Main Looper mainLooper, NetworkController networkController, CarrierTextManager.Builder carrierTextManagerBuilder, Context context, CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags, SlotIndexResolver slotIndexResolver)215     private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
216             @Background Handler bgHandler, @Main Looper mainLooper,
217             NetworkController networkController,
218             CarrierTextManager.Builder carrierTextManagerBuilder, Context context,
219             CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags,
220             SlotIndexResolver slotIndexResolver) {
221 
222         if (featureFlags.isCombinedStatusBarSignalIconsEnabled()) {
223             mProviderModel = true;
224         } else {
225             mProviderModel = false;
226         }
227         mActivityStarter = activityStarter;
228         mBgHandler = bgHandler;
229         mNetworkController = networkController;
230         mCarrierTextManager = carrierTextManagerBuilder
231                 .setShowAirplaneMode(false)
232                 .setShowMissingSim(false)
233                 .build();
234         mCarrierConfigTracker = carrierConfigTracker;
235         mSlotIndexResolver = slotIndexResolver;
236         View.OnClickListener onClickListener = v -> {
237             if (!v.isVisibleToUser()) {
238                 return;
239             }
240             mActivityStarter.postStartActivityDismissingKeyguard(
241                     new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0);
242         };
243         view.setOnClickListener(onClickListener);
244         mNoSimTextView = view.getNoSimTextView();
245         mNoSimTextView.setOnClickListener(onClickListener);
246         mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState);
247         mCallback = new Callback(mMainHandler);
248 
249         mCarrierGroups[0] = view.getCarrier1View();
250         mCarrierGroups[1] = view.getCarrier2View();
251         mCarrierGroups[2] = view.getCarrier3View();
252 
253         mCarrierDividers[0] = view.getCarrierDivider1();
254         mCarrierDividers[1] = view.getCarrierDivider2();
255 
256         for (int i = 0; i < SIM_SLOTS; i++) {
257             mInfos[i] = new CellSignalState(
258                     true,
259                     R.drawable.ic_qs_no_calling_sms,
260                     context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(),
261                     "",
262                     false,
263                     mProviderModel);
264             mLastSignalLevel[i] = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
265             mLastSignalLevelDescription[i] =
266                     context.getText(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0])
267                             .toString();
268             mCarrierGroups[i].setOnClickListener(onClickListener);
269         }
270         mIsSingleCarrier = computeIsSingleCarrier();
271         view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
272 
273         view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
274             @Override
275             public void onViewAttachedToWindow(View v) {
276             }
277 
278             @Override
279             public void onViewDetachedFromWindow(View v) {
280                 setListening(false);
281             }
282         });
283     }
284 
285     @VisibleForTesting
getSlotIndex(int subscriptionId)286     protected int getSlotIndex(int subscriptionId) {
287         return mSlotIndexResolver.getSlotIndex(subscriptionId);
288     }
289 
290     /**
291      * Sets a {@link OnSingleCarrierChangedListener}.
292      *
293      * This will get notified when the number of carriers changes between 1 and "not one".
294      * @param listener
295      */
setOnSingleCarrierChangedListener(OnSingleCarrierChangedListener listener)296     public void setOnSingleCarrierChangedListener(OnSingleCarrierChangedListener listener) {
297         mOnSingleCarrierChangedListener = listener;
298     }
299 
isSingleCarrier()300     public boolean isSingleCarrier() {
301         return mIsSingleCarrier;
302     }
303 
computeIsSingleCarrier()304     private boolean computeIsSingleCarrier() {
305         int carrierCount = 0;
306         for (int i = 0; i < SIM_SLOTS; i++) {
307 
308             if (mInfos[i].visible) {
309                 carrierCount++;
310             }
311         }
312         return carrierCount == 1;
313     }
314 
setListening(boolean listening)315     public void setListening(boolean listening) {
316         if (listening == mListening) {
317             return;
318         }
319         mListening = listening;
320 
321         mBgHandler.post(this::updateListeners);
322     }
323 
updateListeners()324     private void updateListeners() {
325         if (mListening) {
326             if (mNetworkController.hasVoiceCallingFeature()) {
327                 mNetworkController.addCallback(mSignalCallback);
328             }
329             mCarrierTextManager.setListening(mCallback);
330         } else {
331             mNetworkController.removeCallback(mSignalCallback);
332             mCarrierTextManager.setListening(null);
333         }
334     }
335 
336 
337     @MainThread
handleUpdateState()338     private void handleUpdateState() {
339         if (!mMainHandler.getLooper().isCurrentThread()) {
340             mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
341             return;
342         }
343 
344         boolean singleCarrier = computeIsSingleCarrier();
345 
346         if (singleCarrier) {
347             for (int i = 0; i < SIM_SLOTS; i++) {
348                 if (mInfos[i].visible
349                         && mInfos[i].mobileSignalIconId == R.drawable.ic_qs_sim_card) {
350                     mInfos[i] = new CellSignalState(true, R.drawable.ic_blank, "", "", false,
351                             mProviderModel);
352                 }
353             }
354         }
355 
356         for (int i = 0; i < SIM_SLOTS; i++) {
357             mCarrierGroups[i].updateState(mInfos[i], singleCarrier);
358         }
359 
360         mCarrierDividers[0].setVisibility(
361                 mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE);
362         // This tackles the case of slots 2 being available as well as at least one other.
363         // In that case we show the second divider. Note that if both dividers are visible, it means
364         // all three slots are in use, and that is correct.
365         mCarrierDividers[1].setVisibility(
366                 (mInfos[1].visible && mInfos[2].visible)
367                         || (mInfos[0].visible && mInfos[2].visible) ? View.VISIBLE : View.GONE);
368         if (mIsSingleCarrier != singleCarrier) {
369             mIsSingleCarrier = singleCarrier;
370             if (mOnSingleCarrierChangedListener != null) {
371                 mOnSingleCarrierChangedListener.onSingleCarrierChanged(singleCarrier);
372             }
373         }
374     }
375 
376     @MainThread
handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info)377     private void handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
378         if (!mMainHandler.getLooper().isCurrentThread()) {
379             mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
380             return;
381         }
382 
383         mNoSimTextView.setVisibility(View.GONE);
384         if (!info.airplaneMode && info.anySimReady) {
385             boolean[] slotSeen = new boolean[SIM_SLOTS];
386             if (info.listOfCarriers.length == info.subscriptionIds.length) {
387                 for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
388                     int slot = getSlotIndex(info.subscriptionIds[i]);
389                     if (slot >= SIM_SLOTS) {
390                         Log.w(TAG, "updateInfoCarrier - slot: " + slot);
391                         continue;
392                     }
393                     if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
394                         Log.e(TAG,
395                                 "Invalid SIM slot index for subscription: "
396                                         + info.subscriptionIds[i]);
397                         continue;
398                     }
399                     mInfos[slot] = mInfos[slot].changeVisibility(true);
400                     slotSeen[slot] = true;
401                     mCarrierGroups[slot].setCarrierText(
402                             info.listOfCarriers[i].toString().trim());
403                     mCarrierGroups[slot].setVisibility(View.VISIBLE);
404                 }
405                 for (int i = 0; i < SIM_SLOTS; i++) {
406                     if (!slotSeen[i]) {
407                         mInfos[i] = mInfos[i].changeVisibility(false);
408                         mCarrierGroups[i].setVisibility(View.GONE);
409                     }
410                 }
411             } else {
412                 Log.e(TAG, "Carrier information arrays not of same length");
413             }
414         } else {
415             // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show
416             // info.carrierText in a different view.
417             for (int i = 0; i < SIM_SLOTS; i++) {
418                 mInfos[i] = mInfos[i].changeVisibility(false);
419                 mCarrierGroups[i].setCarrierText("");
420                 mCarrierGroups[i].setVisibility(View.GONE);
421             }
422             mNoSimTextView.setText(info.carrierText);
423             if (!TextUtils.isEmpty(info.carrierText)) {
424                 mNoSimTextView.setVisibility(View.VISIBLE);
425             }
426         }
427         handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread.
428     }
429 
430     private static class H extends Handler {
431         private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo;
432         private Runnable mUpdateState;
433         static final int MSG_UPDATE_CARRIER_INFO = 0;
434         static final int MSG_UPDATE_STATE = 1;
435 
H(Looper looper, Consumer<CarrierTextManager.CarrierTextCallbackInfo> updateCarrierInfo, Runnable updateState)436         H(Looper looper,
437                 Consumer<CarrierTextManager.CarrierTextCallbackInfo> updateCarrierInfo,
438                 Runnable updateState) {
439             super(looper);
440             mUpdateCarrierInfo = updateCarrierInfo;
441             mUpdateState = updateState;
442         }
443 
444         @Override
handleMessage(Message msg)445         public void handleMessage(Message msg) {
446             switch (msg.what) {
447                 case MSG_UPDATE_CARRIER_INFO:
448                     mUpdateCarrierInfo.accept(
449                             (CarrierTextManager.CarrierTextCallbackInfo) msg.obj);
450                     break;
451                 case MSG_UPDATE_STATE:
452                     mUpdateState.run();
453                     break;
454                 default:
455                     super.handleMessage(msg);
456             }
457         }
458     }
459 
460     public static class Builder {
461         private QSCarrierGroup mView;
462         private final ActivityStarter mActivityStarter;
463         private final Handler mHandler;
464         private final Looper mLooper;
465         private final NetworkController mNetworkController;
466         private final CarrierTextManager.Builder mCarrierTextControllerBuilder;
467         private final Context mContext;
468         private final CarrierConfigTracker mCarrierConfigTracker;
469         private final FeatureFlags mFeatureFlags;
470         private final SlotIndexResolver mSlotIndexResolver;
471 
472         @Inject
Builder(ActivityStarter activityStarter, @Background Handler handler, @Main Looper looper, NetworkController networkController, CarrierTextManager.Builder carrierTextControllerBuilder, Context context, CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags, SlotIndexResolver slotIndexResolver)473         public Builder(ActivityStarter activityStarter, @Background Handler handler,
474                 @Main Looper looper, NetworkController networkController,
475                 CarrierTextManager.Builder carrierTextControllerBuilder, Context context,
476                 CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags,
477                 SlotIndexResolver slotIndexResolver) {
478             mActivityStarter = activityStarter;
479             mHandler = handler;
480             mLooper = looper;
481             mNetworkController = networkController;
482             mCarrierTextControllerBuilder = carrierTextControllerBuilder;
483             mContext = context;
484             mCarrierConfigTracker = carrierConfigTracker;
485             mFeatureFlags = featureFlags;
486             mSlotIndexResolver = slotIndexResolver;
487         }
488 
setQSCarrierGroup(QSCarrierGroup view)489         public Builder setQSCarrierGroup(QSCarrierGroup view) {
490             mView = view;
491             return this;
492         }
493 
build()494         public QSCarrierGroupController build() {
495             return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
496                     mNetworkController, mCarrierTextControllerBuilder, mContext,
497                     mCarrierConfigTracker, mFeatureFlags, mSlotIndexResolver);
498         }
499     }
500 
501     /**
502      * Notify when the state changes from 1 carrier to "not one" and viceversa
503      */
504     @FunctionalInterface
505     public interface OnSingleCarrierChangedListener {
onSingleCarrierChanged(boolean isSingleCarrier)506         void onSingleCarrierChanged(boolean isSingleCarrier);
507     }
508 
509     /**
510      * Interface for resolving slot index from subscription ID.
511      */
512     @FunctionalInterface
513     public interface SlotIndexResolver {
514         /**
515          * Get slot index for given sub id.
516          */
getSlotIndex(int subscriptionId)517         int getSlotIndex(int subscriptionId);
518     }
519 
520     /**
521      * Default implementation for {@link SlotIndexResolver}.
522      *
523      * It retrieves the slot index using {@link SubscriptionManager#getSlotIndex}.
524      */
525     @SysUISingleton
526     public static class SubscriptionManagerSlotIndexResolver implements SlotIndexResolver {
527 
528         @Inject
SubscriptionManagerSlotIndexResolver()529         public SubscriptionManagerSlotIndexResolver() {}
530 
531         @Override
getSlotIndex(int subscriptionId)532         public int getSlotIndex(int subscriptionId) {
533             return SubscriptionManager.getSlotIndex(subscriptionId);
534         }
535     }
536 }
537