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