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