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