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