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